Flask SSTI利用方式的探索
一、SSTI簡介&環境搭建
一個統一風格的站點,其大多數頁面樣式都是一致的,只是每個頁面顯示的內容各不相同。要是所有的邏輯都放在前端進行,無疑會影響響應效果和效率,很不現實。把所有的邏輯放在後端,又會導致太過複雜,前輕後重。
模板的誕生是為了將顯示與資料分離,讓前端工作人員專注表現設計,後臺人員注重業務邏輯,同時簡化程式碼的複雜程度。模板技術多種多樣,但其本質是將模板檔案和資料通過模板引擎生成最終的HTML程式碼。
Flask使用Jinja 2作為模板引擎。Jinja的語法很簡單,大致有這麼幾種:
{%....%}語句(Statements)
{f .…H}列印模板輸出的表示式(Expressions)
{#....#}註釋
#...##行語句(Line Statements)
什麼是SSTI,SSTI會導致什麼問題?
SSTI,又稱服務端模板注入攻擊。jinja2模板中使用{{ }}語法表示一個變數,它是一種特殊的佔位符。當利用jinja2進行渲染的時候,它會把這些特殊的佔位符進行填充/替換。但是在進行目標編譯渲染的過程中,執行了使用者插入的惡意內容,因而可能導致了敏感資訊洩露、程式碼執行、GetShell 等問題
測試環境搭建:Ubuntu + Docker
題目:https://github.com/Tiaonmmn/pasecactf_2019_web_honey_shop
微調,增加了SSTI~
二、敏感資訊洩露導致身份偽造
Flask session機制:
通過.隔開的3段內容,第一段其實就是base64 encode後的內容,但去掉了填充用的等號,若decode失敗,自己需要補上1-3個等號補全。中間內容為時間戳,在flask中時間戳若超過31天則視為無效。最後一段則是安全簽名,將session data,時間戳,和flask的secret key通過sha1運算的結果。
SSTI方式
f12 → Application → Cookies
這裡我們只需要找到secret key就可以對其簽名,得到一個有效的valid Signature,對其中內容進行替換
檢視robots.txt,看看能不能得到有效資訊
我們再去訪問一下hello,得到Hello guest
這裡我們嘗試用name對欄位進行替換,說明使用者可以通過name欄位傳資訊到後臺,後臺再將資訊渲染到頁面來
我們再進行測試,發現這裡沒有對使用者輸入進行過濾,而是直接執行
我們通過config可以檢視到一些重要的資訊,這裡我們就拿到了secret key
我們在Linux的終端中利用flask-unsign工具,可以進行session的偽造(以修改金錢為例)
flask-unsign --sign --cookie "{'balance':666666}" --secret "獲取的secret key"
回到我們獲取session的位置,將執行結果替換原來的值,再重新整理一下,發現我們的金額成功被修改為我們設定的值
原題解題方式:
原題中圖籤可以下載,我們獲取URL,發現URL通過download?image指明一個引數進行下載,這裡我們可以審查一下有沒有目錄穿越問題
發現passwd可以被下載,說明網站確實存在目錄穿越問題
我們再去下載環境變數,獲取到檔案裡的secret key
三、Flask PIN碼利用
Flask PIN碼機制:
Flask應用在Debug模式下提供的一種頁面端的互動除錯工具,和我們平時使用的Python命令列是一樣的,也就是給我們提供了一個互動式的web端shell。但是PIN碼的生成規則是有規律可循的,使得獲取PIN碼成為可能,之後能夠利用的方式有很多。(只有在Debug開啟的情況下)
這裡我們在原來輸入config的位置亂寫,然後頁面報錯
我們如何獲得Flask PIN呢?
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb'# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'2485377957894',# str(uuid.getnode()), /sys/class/net/ens33/address
'3c7c60af8484830ab0b1e9615fada4e74d93a8a111baa4afcd949feeab56c320'# get_machine_id(), /etc/machine-id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
————————————————
版權宣告:本文為CSDN博主「火 柴 人」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/weixin_43536759/article/details/105194333
我們可以通過獲取Flask上的六個值來計算出Flask的PIN
1.username:執行當前Flask的使用者名稱(可通過之前下載的passwd檔案獲取)
2.modename:一般預設值為flask.app
3.getatrr函式的值:一般預設值為Flask
4.Flask的路徑(報錯資訊可以直接檢視)
5.網路地址十進位制
檔案裡為十六進位制值,可通過命令轉換為十進位制
int ("十六進位制值去掉冒號",16)
6.Flask執行機器的機器碼
一般環境在etc/machine-id下,如果報錯,可以去proc/self/cgroup嘗試,這裡docker裡面就是我們要找的值
計算出PIN碼後我們可以回到報錯頁面,點選報錯資訊右邊的小視窗,輸入PIN碼後即可得到一個互動視窗,但在這裡只能得到互動命令的返回值,看不到具體輸出
但我們可以通過popen+read將返回的值顯示出來
四、SSTI導致RCE
Python魔法函式+內建函式
所謂魔法函式(Magic Methods),是Python的一種高階語法,允許你在類中自定義函式(函式名格式一般為__xx__),並繫結到類的特殊方法中。比如在類A中自定義__str__()函式,則在呼叫str(A())時,會自動呼叫__str__()(函式,並返回相應的結果。在我們平時的使用中,可能經常使用__init__函式和__del__函式,其實這也是魔法函式的一種。
在python中,輸入可以檢視python內建函式。help(dir(._builtins ))。簡單理解就是Python中自帶的函式,直接拿來用就好了。
這裡我們需要定位到popen的位置,這裡為302,然後就可以直接利用它,再去找到一些可以利用的子類,比如popen和os
發現這裡可以達到直接執行系統命令的操作