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