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
發現這里可以達到直接執行系統命令的操作