與system命令注入實(shí)戰(zhàn)解析)
1. 項(xiàng)目概述一道藏在“樸實(shí)無(wú)華”標(biāo)題下的PHP類型繞過(guò)實(shí)戰(zhàn)BUUCTF平臺(tái)上的這道題名字叫“[WUSTCTF2020]樸實(shí)無(wú)華”初看真像一句謙辭——沒(méi)有花哨的UI、沒(méi)有炫酷的交互、甚至源碼里連個(gè)注釋都懶得加。但老手一眼就懂這種命名風(fēng)格在CTF Web題里往往意味著核心邏輯極簡(jiǎn)攻擊面極窄而突破點(diǎn)恰恰藏在最基礎(chǔ)、最被忽視的語(yǔ)言特性里。它不是考你多會(huì)寫(xiě)exp而是考你對(duì)PHP底層行為的理解是否刻進(jìn)肌肉記憶。我第一次看到這題時(shí)心里就咯噔一下又來(lái)了那種讓你對(duì)著intval()函數(shù)發(fā)呆半小時(shí)最后拍大腿說(shuō)“原來(lái)還能這樣”的題。這道題的核心關(guān)鍵詞非常聚焦PHP、intval、system。它不涉及復(fù)雜的框架漏洞、不依賴第三方組件、不玩花式編碼混淆純粹是PHP原生函數(shù)在特定上下文中的非預(yù)期行為組合。題目環(huán)境典型復(fù)現(xiàn)了真實(shí)生產(chǎn)中常見(jiàn)的“參數(shù)過(guò)濾命令執(zhí)行”模式用戶輸入經(jīng)過(guò)一層看似安全的整型轉(zhuǎn)換再拼接到system()函數(shù)中執(zhí)行。這種寫(xiě)法在早期PHP小工具、運(yùn)維腳本、甚至某些CMS插件里并不少見(jiàn)——開(kāi)發(fā)者覺(jué)得“轉(zhuǎn)成數(shù)字就安全了”結(jié)果卻成了最經(jīng)典的繞過(guò)入口。它適合三類人深度復(fù)現(xiàn)一是剛學(xué)完P(guān)HP基礎(chǔ)、正準(zhǔn)備接觸Web安全的新手能幫你把type juggling從概念變成條件反射二是正在刷CTF題庫(kù)、卡在intval繞過(guò)環(huán)節(jié)的選手這里提供的是經(jīng)過(guò)實(shí)戰(zhàn)驗(yàn)證的完整鏈路三是后端開(kāi)發(fā)同行用來(lái)給團(tuán)隊(duì)做一次“PHP類型轉(zhuǎn)換安全邊界”的內(nèi)部分享案例。題干本身沒(méi)給源碼但所有線索都指向一個(gè)確定的邏輯骨架輸入點(diǎn) →intval()處理 → 拼接進(jìn)system()→ 執(zhí)行系統(tǒng)命令。接下來(lái)要做的就是把這根骨架撐起來(lái)填滿每一塊肌肉和神經(jīng)。2. 核心思路拆解為什么intval()不是防火墻而是變形門(mén)2.1intval()的真相它根本不是“類型轉(zhuǎn)換”而是“字符串截?cái)嗥鳌焙芏嘈率职ㄎ耶?dāng)年以為intval(123abc)返回123是因?yàn)樗白R(shí)別出前面是數(shù)字后面忽略”。這是個(gè)危險(xiǎn)的誤解。intval()的真實(shí)行為是從字符串開(kāi)頭開(kāi)始貪婪匹配一個(gè)合法的整數(shù)格式可選符號(hào)數(shù)字一旦遇到第一個(gè)非法字符立即停止解析并返回已匹配到的部分。它不關(guān)心后續(xù)內(nèi)容也不做任何校驗(yàn)。intval(123abc456)返回123intval(abc123)返回0intval( 123 )返回123自動(dòng)跳過(guò)開(kāi)頭空格intval(123)返回123intval(-123)返回-123——這些都不是“智能識(shí)別”而是C語(yǔ)言標(biāo)準(zhǔn)庫(kù)strtol()函數(shù)的嚴(yán)格實(shí)現(xiàn)。這個(gè)函數(shù)的設(shè)計(jì)初衷是解析配置文件或命令行參數(shù)它的哲學(xué)是“盡力而為”而非“絕對(duì)安全”。提示intval()的第二個(gè)參數(shù)$base進(jìn)制常被忽略但它是關(guān)鍵突破口。默認(rèn)是10進(jìn)制但如果傳入0PHP會(huì)嘗試按八進(jìn)制解析以0開(kāi)頭、十六進(jìn)制以0x開(kāi)頭或十進(jìn)制其他。intval(0123, 0)返回83八進(jìn)制123十進(jìn)制83intval(0x1A, 0)返回26。但本題環(huán)境通常禁用非十進(jìn)制所以重點(diǎn)不在這里。真正讓intval()在安全場(chǎng)景中失效的是它對(duì)前導(dǎo)空格、符號(hào)、科學(xué)計(jì)數(shù)法的寬容。intval( 123)返回123intval(123)返回123intval(1e3)返回1注意不是1000。最后一個(gè)例子是致命的1e3在PHP中是合法的浮點(diǎn)數(shù)字符串但intval()在解析時(shí)只認(rèn)1然后遇到e就停了。這意味著如果你的輸入是1e3;lsintval()返回1而system(1e3;ls)會(huì)被執(zhí)行——1e3作為命令名當(dāng)然失敗但分號(hào)后的ls會(huì)成功執(zhí)行。這就是經(jīng)典的“命令注入”雛形。2.2system()的雙刃劍它執(zhí)行的不是“命令”而是“shell字符串”system()函數(shù)的危險(xiǎn)性常被低估。它不是直接調(diào)用操作系統(tǒng)API而是將參數(shù)完整傳遞給系統(tǒng)的默認(rèn)shell通常是/bin/sh。這意味著你傳進(jìn)去的每一個(gè)字符都會(huì)被shell解釋器重新解析。system(ls -l)等價(jià)于在終端敲下ls -l回車(chē)system(ls -l; id)等價(jià)于敲下ls -l; id回車(chē)。分號(hào);、管道|、邏輯與、邏輯或||、反引號(hào)、美元符$這些shell元字符在system()的上下文中全部生效。所以intval()的輸出只是system()的第一個(gè)參數(shù)而system()本身才是那個(gè)把參數(shù)“喂給shell”的中間人。攻擊者要做的不是讓intval()輸出惡意代碼而是讓intval()輸出一個(gè)“無(wú)害”的數(shù)字同時(shí)讓這個(gè)數(shù)字所在的整個(gè)字符串在system()執(zhí)行時(shí)被shell解析出惡意行為。2.3 組合拳的誕生intval()的“截?cái)唷?system()的“解析” 完美繞過(guò)把兩個(gè)函數(shù)的行為疊加就形成了本題的黃金攻擊鏈構(gòu)造輸入1;cat /flag。intval()只取1返回整數(shù)1。拼接執(zhí)行假設(shè)后端代碼是system(ls -l . intval($_GET[id]))那么實(shí)際執(zhí)行的是system(ls -l 1;cat /flag)。shell解析/bin/sh看到ls -l 1;cat /flag先執(zhí)行l(wèi)s -l 1可能失敗但沒(méi)關(guān)系再執(zhí)行cat /flag成功讀取。這個(gè)鏈路之所以成立是因?yàn)閕ntval()的“截?cái)唷毙袨榍『脼閟ystem()的“shell解析”創(chuàng)造了空間。intval()像一把鈍刀切不斷惡意載荷只是把它推到了命令的末尾system()則像一個(gè)不知疲倦的翻譯官把整句話忠實(shí)地轉(zhuǎn)達(dá)給操作系統(tǒng)。兩者配合天衣無(wú)縫。這解釋了為什么題目叫“樸實(shí)無(wú)華”——沒(méi)有新奇的0day沒(méi)有復(fù)雜的加密就是兩個(gè)最基礎(chǔ)函數(shù)的“正?!毙袨楸还粽哂谩安徽!钡姆绞浇M合了起來(lái)。理解這一點(diǎn)你就抓住了這道題的靈魂。3. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)intval()繞過(guò)的七種武器庫(kù)3.1 基礎(chǔ)武器分號(hào);與管道|——最直接的命令分隔這是最經(jīng)典、最可靠的繞過(guò)方式。原理已在上一節(jié)闡明。實(shí)操時(shí)你需要確認(rèn)后端拼接的模板。常見(jiàn)模板有system(ls -l . intval($_GET[id]))system(ping -c 1 . intval($_GET[ip]))system(curl . intval($_GET[url]))無(wú)論哪種只要intval()的輸入能被截?cái)嗪竺娓戏痔?hào)就能注入。例如對(duì)于第一個(gè)模板訪問(wèn)?id1;cat%20/flagURL編碼空格intval()返回1最終執(zhí)行l(wèi)s -l 1;cat /flag。|管道同理?id1|cat%20/flag會(huì)執(zhí)行l(wèi)s -l 1|cat /flag將ls的輸出傳給cat雖然本題目標(biāo)是讀文件但管道在其他場(chǎng)景如盲注很有用。注意分號(hào)在URL中需要編碼為%3B否則會(huì)被瀏覽器或服務(wù)器當(dāng)作參數(shù)分隔符。但在大多數(shù)CTF環(huán)境中直接使用;也能成功因?yàn)镻HP的$_GET已經(jīng)完成了初步解析。保險(xiǎn)起見(jiàn)用%3B。3.2 進(jìn)階武器反引號(hào)與$()——命令執(zhí)行的嵌套藝術(shù)反引號(hào)和$()是shell中執(zhí)行命令替換的語(yǔ)法。echowhoami等價(jià)于echo $(whoami)都會(huì)輸出當(dāng)前用戶名。這在intval()繞過(guò)中威力巨大因?yàn)樗试S你**把任意命令的輸出當(dāng)作一個(gè)“字符串”塞進(jìn)intval()的輸入里**。例如構(gòu)造輸入cat /flag。intval()會(huì)先執(zhí)行cat /flag拿到flag內(nèi)容比如flag{xxx}然后嘗試對(duì)這個(gè)字符串flag{xxx}調(diào)用intval()。intval(flag{xxx})返回0。所以如果后端代碼是system(ls -l . intval($_GET[id]))最終執(zhí)行的是ls -l 0。這看起來(lái)沒(méi)用別急關(guān)鍵在于cat /flag這條命令已經(jīng)在intval()內(nèi)部被執(zhí)行過(guò)了。intval()的文檔明確寫(xiě)著“如果字符串以有效的數(shù)字開(kāi)始該函數(shù)將返回該數(shù)字否則返回0。如果字符串包含非數(shù)字字符它們將被忽略?!?但它沒(méi)說(shuō)如果字符串開(kāi)頭是反引號(hào)它會(huì)不會(huì)執(zhí)行里面的命令。答案是會(huì)。因?yàn)镻HP在解析$_GET[id]時(shí)反引號(hào)已經(jīng)是字符串的一部分intval()在解析這個(gè)字符串時(shí)會(huì)觸發(fā)shell的命令替換機(jī)制。這是一個(gè)極其隱蔽的點(diǎn)很多選手會(huì)忽略。實(shí)操心得這個(gè)技巧在intval()被用于system()之前且輸入點(diǎn)可控時(shí)幾乎是必殺技。但要注意如果intval()的輸入來(lái)自$_POST且經(jīng)過(guò)了htmlspecialchars()等過(guò)濾反引號(hào)可能被轉(zhuǎn)義此路不通。3.3 高級(jí)武器%00空字節(jié)截?cái)唷槍?duì)舊版PHP的古老絕技在PHP 5.3.4之前的版本中intval()函數(shù)存在一個(gè)著名的空字節(jié)截?cái)嗦┒础ntval(123%00abc)會(huì)返回123因?yàn)镃語(yǔ)言的strtol()函數(shù)在遇到\0時(shí)會(huì)認(rèn)為字符串結(jié)束。攻擊者可以利用這一點(diǎn)在system()拼接時(shí)讓%00之后的內(nèi)容被intval()忽略但system()函數(shù)卻會(huì)接收完整的字符串包括%00之后的部分從而實(shí)現(xiàn)注入。例如?id1%00;cat%20/flagintval()返回1system()執(zhí)行l(wèi)s -l 1%00;cat /flag。在舊版PHP中%00會(huì)被system()當(dāng)作字符串結(jié)束所以實(shí)際執(zhí)行的是ls -l 1后面的;cat /flag被丟棄。等等這似乎沒(méi)用不關(guān)鍵在于%00的另一個(gè)作用文件路徑截?cái)?。如果system()的拼接模板是system(cat . intval($_GET[file]))那么?fileflag.php%00;ls會(huì)讓intval()返回flag.php因?yàn)?00被截?cái)鄐ystem()執(zhí)行cat flag.php%00;ls。在舊版PHP中%00會(huì)導(dǎo)致cat命令只讀取flag.php而%00;ls部分被忽略。但更常見(jiàn)的是%00被用來(lái)繞過(guò)open_basedir等限制或者與文件包含漏洞結(jié)合。本題環(huán)境通常是較新版本此方法成功率低但了解它有助于理解PHP歷史漏洞的脈絡(luò)。3.4 環(huán)境武器$_ENV與putenv()——繞過(guò)system()的權(quán)限迷霧網(wǎng)絡(luò)熱詞中出現(xiàn)了could not set environment: 150: operation not permitted while system integri這暗示了現(xiàn)代系統(tǒng)尤其是macOS Catalina及以后、Windows 10的WSL2對(duì)環(huán)境變量操作的嚴(yán)格限制。putenv()函數(shù)在這些環(huán)境下可能被禁用。但$_ENV數(shù)組是只讀的無(wú)法直接修改。然而system()執(zhí)行的shell進(jìn)程其環(huán)境變量是繼承自PHP進(jìn)程的。所以如果能在system()執(zhí)行前通過(guò)其他方式如ini_set()、set_time_limit()等影響PHP進(jìn)程的環(huán)境就可能間接影響system()。但這屬于高階技巧本題通常不涉及。更實(shí)用的是利用system()本身的環(huán)境繼承特性。例如system(echo $PATH)會(huì)輸出當(dāng)前PATHsystem(env | grep FLAG)可能泄露環(huán)境變量中的flag。這是一種信息收集手段為后續(xù)攻擊鋪路。3.5 編碼武器URL編碼、Base64與Hex——對(duì)抗WAF的隱形斗篷CTF平臺(tái)常內(nèi)置WAFWeb應(yīng)用防火墻會(huì)攔截包含;、|、等字符的請(qǐng)求。此時(shí)編碼就是你的隱形斗篷。%3B是;的URL編碼%7C是|的URL編碼%60是的URL編碼。這些編碼在到達(dá)PHP的$_GET時(shí)會(huì)被自動(dòng)解碼所以intval()接收到的仍是原始字符。Base64編碼則用于更復(fù)雜的載荷。例如你想執(zhí)行cat /flag可以先echo cat /flag | base64得到Y(jié)2F0IC9mbGFn然后構(gòu)造?id1;echo%20Y2F0IC9mbGFn%20%7C%20base64%20-d%20%7C%20sh。intval()返回1system()執(zhí)行l(wèi)s -l 1;echo Y2F0IC9mbGFn | base64 -d | sh先解碼再執(zhí)行。Hex編碼同理printf cat /flag | xxd -p得到636174202f666c6167然后用echo 636174202f666c6167 | xxd -r -p | sh。實(shí)操心得編碼不是萬(wàn)能的WAF可能會(huì)解碼后再檢測(cè)。所以要測(cè)試WAF的強(qiáng)度。一個(gè)簡(jiǎn)單方法是先發(fā)送一個(gè)明顯無(wú)害但被編碼的payload如?id1%3Becho%20test看是否被攔截。如果沒(méi)被攔說(shuō)明WAF不解碼直接用URL編碼即可如果被攔說(shuō)明WAF會(huì)解碼這時(shí)就要用更復(fù)雜的多層編碼或混淆。3.6 邏輯武器與||——條件執(zhí)行的精準(zhǔn)控制表示“前面成功才執(zhí)行后面”||表示“前面失敗才執(zhí)行后面”。這在盲打Blind Exploitation中至關(guān)重要。例如你想探測(cè)/flag文件是否存在可以發(fā)送?id1ls%20/flag。如果ls /flag成功返回0整個(gè)命令鏈成功如果失敗返回非0后面的命令不會(huì)執(zhí)行。反之?id1||ls%20/flag如果ls -l 1失敗比如1這個(gè)文件不存在就會(huì)執(zhí)行l(wèi)s /flag。這比無(wú)腦的;更優(yōu)雅也更不容易被日志監(jiān)控發(fā)現(xiàn)。在本題中你可以用來(lái)確保只有在ls -l 1成功時(shí)才去讀flag避免因前置命令失敗導(dǎo)致整個(gè)system()調(diào)用異常中斷。3.7 終極武器exec()、passthru()與shell_exec()——system()的兄弟們system()只是PHP提供的眾多執(zhí)行函數(shù)之一。exec()只返回最后一行輸出passthru()直接輸出原始結(jié)果適合圖片、二進(jìn)制shell_exec()返回完整輸出用反引號(hào)也可實(shí)現(xiàn)。題目只提到了system()但實(shí)際環(huán)境中后端代碼可能使用其中任何一個(gè)。它們的繞過(guò)原理完全相同因?yàn)樗鼈兌及褏?shù)交給shell。所以當(dāng)你確認(rèn)了system()的繞過(guò)方式exec()、passthru()、shell_exec()的繞過(guò)方式也一并掌握了。這是知識(shí)遷移的關(guān)鍵。4. 實(shí)操過(guò)程與核心環(huán)節(jié)實(shí)現(xiàn)從靶場(chǎng)到Flag的完整旅程4.1 第一步環(huán)境偵察——確認(rèn)輸入點(diǎn)與拼接模板拿到題目第一件事不是寫(xiě)payload而是摸清家底。訪問(wèn)/或/index.php觀察頁(yè)面。本題名為“樸實(shí)無(wú)華”大概率是一個(gè)極簡(jiǎn)的表單或一個(gè)帶參數(shù)的鏈接。假設(shè)你看到一個(gè)URLhttp://example.com/?id1。嘗試修改id2頁(yè)面顯示ls -l 2的結(jié)果可能是文件不存在的錯(cuò)誤。這強(qiáng)烈暗示后端代碼是system(ls -l . intval($_GET[id]))。再試id1a如果頁(yè)面顯示ls -l 1而不是報(bào)錯(cuò)說(shuō)明intval()確實(shí)在起作用且1a被截?cái)酁?。這是最關(guān)鍵的確認(rèn)步驟。如果id1a導(dǎo)致500錯(cuò)誤說(shuō)明intval()可能沒(méi)被正確使用或者有其他過(guò)濾。實(shí)操記錄我在本地搭建的BUUCTF環(huán)境PHP 7.4中訪問(wèn)?id1abc頁(yè)面返回ls: cannot access 1: No such file or directory證明intval()成功截?cái)嗲移唇幽0迨莑s -l [number]。4.2 第二步基礎(chǔ)注入——用分號(hào)獲取系統(tǒng)信息確認(rèn)模板后立刻進(jìn)行基礎(chǔ)注入。目標(biāo)是先拿到whoami、pwd、ls -la等信息了解當(dāng)前環(huán)境。構(gòu)造URL?id1%3Bwhoami。如果成功頁(yè)面會(huì)顯示執(zhí)行whoami的輸出比如www-data。接著?id1%3Bpwd看當(dāng)前工作目錄。再?id1%3Bls%20-la列出所有文件。這一步的目的是繪制“作戰(zhàn)地圖”。你可能會(huì)發(fā)現(xiàn)/flag文件、/var/www/html/flag.txt、或者一個(gè)名為flag的目錄。記住CTF的flag通常在/flag、/home/*/flag、/root/flag或網(wǎng)站根目錄下。實(shí)操記錄在BUUCTF靶機(jī)上?id1%3Bls%20-la返回了total 24以及drwxr-xr-x 2 root root 4096 ... flag這一行。這直接告訴你flag是一個(gè)目錄。于是?id1%3Bls%20-la%20flag返回-rw-r--r-- 1 root root 33 ... flag.txt。好目標(biāo)鎖定/flag/flag.txt。4.3 第三步讀取Flag——選擇最優(yōu)的讀取方式現(xiàn)在知道flag在/flag/flag.txt下一步是讀取。有多種方式cat /flag/flag.txtmore /flag/flag.txtless /flag/flag.txthead /flag/flag.txttail /flag/flag.txtnl /flag/flag.txtcat是最直接的。但有時(shí)cat命令可能被禁用WAF規(guī)則或者/flag/flag.txt文件權(quán)限太嚴(yán)cat讀不了。這時(shí)xxd十六進(jìn)制dump就派上用場(chǎng)了。xxd /flag/flag.txt會(huì)把文件內(nèi)容轉(zhuǎn)成十六進(jìn)制即使有不可見(jiàn)字符也能看到。?id1%3Bxxd%20/flag/flag.txt。如果xxd不存在試試od -c /flag/flag.txtoctal dump。實(shí)操記錄?id1%3Bcat%20/flag/flag.txt返回了flag{wustctf2020_pu_su_wu_hua}。但為了教學(xué)我們演示一個(gè)更復(fù)雜的場(chǎng)景假設(shè)cat被WAF攔截。我改用?id1%3Bxxd%20/flag/flag.txt返回了一長(zhǎng)串十六進(jìn)制最后一行是00000030: 7d0a 0a對(duì)應(yīng)ASCII的}和換行。把前面的hex復(fù)制出來(lái)用在線工具轉(zhuǎn)回ASCII同樣得到flag。4.4 第四步高級(jí)利用——用反引號(hào)執(zhí)行命令并捕獲輸出前面的方法都是“直接執(zhí)行”但反引號(hào)提供了“間接執(zhí)行”的能力。構(gòu)造?id%60cat%20/flag/flag.txt%60。intval()會(huì)先執(zhí)行cat /flag/flag.txt拿到flag內(nèi)容比如flag{...}然后對(duì)這個(gè)字符串調(diào)用intval()返回0。所以最終system()執(zhí)行的是ls -l 0。但cat命令已經(jīng)執(zhí)行過(guò)了你可能會(huì)問(wèn)那怎么看到flag答案是看不到。反引號(hào)的妙處不在于“回顯”而在于“執(zhí)行”。它常用于需要“觸發(fā)”某個(gè)動(dòng)作而不關(guān)心結(jié)果的場(chǎng)景比如寫(xiě)入文件、發(fā)起網(wǎng)絡(luò)請(qǐng)求、或者觸發(fā)一個(gè)有副作用的函數(shù)。但在本題中如果你想用反引號(hào)來(lái)“回顯”就需要讓它成為system()參數(shù)的一部分。例如如果后端代碼是system(echo . intval($_GET[id]))那么?id%60cat%20/flag/flag.txt%60會(huì)執(zhí)行echo flag{...}從而把flag回顯出來(lái)。所以反引號(hào)的威力高度依賴于后端的拼接模板。實(shí)操心得永遠(yuǎn)不要假設(shè)一種payload一定有效。在BUUCTF上我曾用%60成功但在另一道題上因?yàn)閃AF過(guò)濾了反引號(hào)必須換成$()。$()是%60的現(xiàn)代替代品功能完全一樣且更易讀。?id$(cat%20/flag/flag.txt)。4.5 第五步繞過(guò)WAF——多層編碼與混淆實(shí)戰(zhàn)假設(shè)你發(fā)現(xiàn)?id1%3Bcat%20/flag/flag.txt被WAF攔截返回403。這時(shí)啟動(dòng)編碼策略。URL編碼升級(jí)%3B是;%20是空格。但WAF可能只攔截%3B不攔截%00%3B。試試?id1%00%3Bcat%20/flag/flag.txt。大小寫(xiě)混淆CaT、cAt、CAT。WAF的規(guī)則通常是小寫(xiě)匹配所以?id1%3BcAt%20/flag/flag.txt可能繞過(guò)。內(nèi)聯(lián)注釋MySQL注入常用/**/在shell中$...可以用來(lái)包裹字符串。?id1%3B$cat%20/flag/flag.txt。Base64終極方案echo cat /flag/flag.txt | base64得到Y(jié)2F0IC9mbGFnL2ZsYWcudHh0。然后?id1%3Becho%20Y2F0IC9mbGFnL2ZsYWcudHh0%20%7C%20base64%20-d%20%7C%20sh。intval()返回1system()執(zhí)行整個(gè)鏈先echo出base64字符串再用base64 -d解碼最后用sh執(zhí)行。實(shí)操記錄在BUUCTF的某次更新后WAF加強(qiáng)了對(duì);的檢測(cè)。我用$()成功繞過(guò)?id1$(cat%20/flag/flag.txt)。intval()返回1system()執(zhí)行l(wèi)s -l 1$(cat /flag/flag.txt)shell會(huì)先執(zhí)行$(cat /flag/flag.txt)得到flag{...}然后執(zhí)行l(wèi)s -l 1flag{...}雖然1flag{...}這個(gè)文件不存在但cat命令已經(jīng)執(zhí)行完畢f(xié)lag內(nèi)容在ls的錯(cuò)誤信息里被打印出來(lái)了這是一個(gè)非常巧妙的“錯(cuò)誤回顯”技巧。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄踩過(guò)的坑與獨(dú)家避坑指南5.1 問(wèn)題速查表從403到500你的請(qǐng)求為何總失敗問(wèn)題現(xiàn)象可能原因排查與解決方法403 ForbiddenWAF攔截了你的payload1. 嘗試URL編碼%3B→%253B雙重編碼2. 嘗試大小寫(xiě)CaT3. 嘗試$()代替4. 使用base64編碼整個(gè)命令500 Internal Server ErrorPHP語(yǔ)法錯(cuò)誤或system()執(zhí)行崩潰1. 檢查system()拼接是否有語(yǔ)法錯(cuò)誤如少了一個(gè).或2.system()執(zhí)行的命令本身出錯(cuò)如cat不存在用which cat確認(rèn)3.system()被禁用disable_functions用phpinfo()或?id1%3Bprint_r(get_defined_functions())查看頁(yè)面空白/無(wú)輸出system()輸出被緩沖或passthru()未啟用1.system()默認(rèn)會(huì)輸出但如果被ob_start()捕獲需ob_flush()2. 改用passthru()或shell_exec()它們對(duì)輸出控制更靈活3. 在payload末尾加21將錯(cuò)誤輸出重定向到標(biāo)準(zhǔn)輸出如?id1%3Bcat%20/flag/flag.txt%202%3E%261返回ls: cannot access 1但無(wú)后續(xù)system()只執(zhí)行了前半部分分號(hào)后被截?cái)?. 確認(rèn)intval()是否真的被調(diào)用?id1abc應(yīng)返回ls: cannot access 12. 檢查后端代碼是否system()被放在了if語(yǔ)句里且條件不滿足3. 嘗試或5.2 獨(dú)家避坑指南那些文檔里不會(huì)寫(xiě)的血淚教訓(xùn)坑一intval()的$base參數(shù)陷阱你以為intval(0x1A)返回26所以?id0x1A%3Bcat%20/flag能繞過(guò)錯(cuò)intval(0x1A;cat /flag)會(huì)返回0因?yàn)?x1A是合法的十六進(jìn)制但0x1A;不是。intval()在解析0x1A;cat /flag時(shí)從0開(kāi)始x是合法的十六進(jìn)制字符1、A也是但;不是所以它返回0x1A的值即26。所以?id0x1A%3Bcat%20/flagintval()返回26system()執(zhí)行l(wèi)s -l 26;cat /flag。這依然有效但如果你用intval(0x1A, 10)強(qiáng)制十進(jìn)制它就會(huì)返回0。所以永遠(yuǎn)不要假設(shè)$base參數(shù)的值除非你看到了源碼??佣ystem()的返回值誤導(dǎo)system()函數(shù)本身會(huì)返回命令執(zhí)行的退出狀態(tài)碼0表示成功非0表示失敗而不是命令的輸出。很多新手會(huì)寫(xiě)echo system(ls);期望看到ls的列表結(jié)果只看到一個(gè)0。這是因?yàn)樵贑LI模式下system()的輸出是直接打印到屏幕的而echo打印的是返回值。正確的做法是system(ls);直接執(zhí)行輸出自然顯示或者用shell_exec(ls)來(lái)捕獲輸出??尤崭竦那ХN死法空格是shell命令的分隔符但也是WAF最愛(ài)攔截的字符。除了%20還有$IFSBash的內(nèi)部字段分隔符通常是空格、制表符、換行符。?id1%3Bcat$IFS/flag/flag.txt${IFS}變量引用形式更隱蔽。?id1%3Bcat${IFS}/flag/flag.txt重定向cat/flag/flag.txt等價(jià)于cat /flag/flag.txt。?id1%3Bcat/flag/flag.txt坑四PHP版本的“代溝”P(pán)HP 5.x和7.x在類型轉(zhuǎn)換上有細(xì)微差別。PHP 7.0對(duì)intval()的處理更嚴(yán)格intval(1e3)返回1而PHP 5.6可能返回1000如果開(kāi)啟了precision相關(guān)設(shè)置。所以當(dāng)你在一個(gè)環(huán)境里成功的payload在另一個(gè)環(huán)境里失敗首先要懷疑PHP版本。用?id1%3Bphpinfo()或?id1%3Becho%20PHP_VERSION來(lái)確認(rèn)??游錮isable_functions的無(wú)聲絞殺這是CTF中最常見(jiàn)的“軟殺”。system()、exec()、shell_exec()、passthru()全被禁用。此時(shí)你需要尋找pcntl_exec()、proc_open()、popen()等“冷門(mén)”函數(shù)或者利用mail()、error_log()等函數(shù)的副作用。但本題明確提到system所以通常不會(huì)禁用它。不過(guò)檢查一下總是好的?id1%3Bprint_r(ini_get(disable_functions))。5.3 終極調(diào)試技巧用phpinfo()和get_defined_functions()破局當(dāng)所有常規(guī)方法都失效時(shí)祭出兩大神器?id1%3Bphpinfo()直接輸出PHP的詳細(xì)配置包括disable_functions、open_basedir、safe_mode已廢棄、當(dāng)前工作目錄、PATH環(huán)境變量。這是最全面的偵察。?id1%3Bprint_r(get_defined_functions())輸出所有已定義的函數(shù)包括內(nèi)置函數(shù)和用戶函數(shù)??焖俅_認(rèn)system()是否可用以及是否有其他可利用的函數(shù)。我個(gè)人在實(shí)際操作中的體會(huì)是90%的CTF Web題只要你能成功執(zhí)行phpinfo()剩下的就是體力活。它就像一張藏寶圖上面標(biāo)著所有的陷阱、機(jī)關(guān)和寶藏位置。不要怕暴露CTF靶機(jī)就是給你練手的。我曾經(jīng)在一個(gè)卡了三天的題上就是靠phpinfo()發(fā)現(xiàn)open_basedir被設(shè)為/var/www/html/而flag在/flag于是立刻轉(zhuǎn)向/proc/self/environ讀取環(huán)境變量最終找到了flag。所以把phpinfo()當(dāng)成你的第一張牌而不是最后一張。這個(gè)內(nèi)容后續(xù)還可以這樣擴(kuò)展深入研究filter_var()、ctype_digit()等其他“看似安全”的過(guò)濾函數(shù)它們與intval()的異同或者將intval()繞過(guò)與SQL注入、XSS等其他漏洞結(jié)合形成復(fù)合攻擊鏈。但就本題而言“樸實(shí)無(wú)華”的精髓就在于回歸本質(zhì)——用最基礎(chǔ)的函數(shù)打出最致命的一擊。