# 極之番『漩渦』教學題文件 ## 觀察 首先,先觀察目前現有的東西,Dockerfile。 ```Dockerfile= FROM php:7.4.33-apache RUN chown -R www-data:www-data /var/www/html && \ chmod -R 555 /var/www/html ARG FLAG RUN echo $FLAG > /flag_`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1` USER www-data ``` 透過這個 Dockerfile,我們可以得知了幾件事情。 - Line1 這題的 php 版本為 7.4.33 - 可以透過此 [網站](https://3v4l.org/#v7.4.33) 來測試不同版本 php 行為 - Line7 通常最終會讓我們能 RCE,也告訴了我們 flag 的位置在根目錄 ## stage1 ```php= <?php include('config.php'); echo '<h1>👻 Stage 1 / 4</h1>'; $A = $_GET['A']; $B = $_GET['B']; highlight_file(__FILE__); echo '<hr>'; if (isset($A) && isset($B)) if ($A != $B) if (strcmp($A, $B) == 0) if (md5($A) === md5($B)) echo "<a href=$stage2>Go to stage2</a>"; else die('ERROR: MD5(A) != MD5(B)'); else die('ERROR: strcmp(A, B) != 0'); else die('ERROR: A == B'); else die('ERROR: A, B should be given'); ``` 可以發現在 Line13 會需要讓 A != B,且在 Line13 會需要 `strcmp($A, $B) == 0`,代表了若 A 和 B 皆為字串時需要兩者相同,到這裡正常行為下就不太可能發生。我們繼續往下看,他還需要讓兩者的 md5 hash 值相同。 這題用到的是 php 中 array parameter 的技巧,php 中很多函式在處理到傳入參數為 array 時,會報 warning,但仍會回傳 NULL。 另外一個會用到的技巧是弱型別比較的技巧,在 php 中使用 == 來比較兩值時,將進行弱型別轉換,下圖範例。   綜合以上兩種特性,傳入 A 和 B 不相同的兩個 array,就能導致通過 Line12 `A!=B` 的檢查,接下來 Line13 由於 `strcmp($A, $B)` 為 `NULL`,因此這個弱型別比較會是 true,最後 Line14 `md5($A)` 與 `md5($B)` 兩者皆為 NULL,因此 `md5($A) === md5($B)` 為 true payload: `stage1.php?A[]=1&B[]=3` ## stage2 ```php= <?php include('config.php'); echo '<h1>👻 Stage 2 / 4</h1>'; $A = $_GET['A']; $B = $_GET['B']; highlight_file(__FILE__); echo '<hr>'; if (isset($A) && isset($B)) if ($A !== $B){ $is_same = md5($A) == 0 and md5($B) === 0; if ($is_same) echo (md5($B) ? "QQ1" : md5($A) == 0 ? "<a href=$stage3?page=swirl.php>Go to stage3</a>" : "QQ2"); else die('ERROR: $is_same is false'); } else die('ERROR: A, B should be given'); ``` 這題比起 stage1 多用到了一個知識點,在 php 中, = 的運算優先度是高於 and 的,若有以下 expression 時,結果會是 true。因此,在 Line13 我們只需要讓前面的為 true 即可。  在 Line15 我們會用到的知識點是,三元運算子的錯誤使用,我們先假設 `md5($B)` 是 false,`md5($A) == 0` 是 true ``` false ? "QQ1" : true ? "<a href=$stage3?page=swirl.php>Go to stage3</a>" : "QQ2" ``` 會先處理前面的 `false ? "QQ1" : true`,因此運算式變這樣。 ``` true ? "<a href=$stage3?page=swirl.php>Go to stage3</a>" : "QQ2" ``` 因此,答案就會正常的被印出。 ## stage3 ```php= <?php include('config.php'); echo '<h1>👻 Stage 3 / 4</h1>'; $page = $_GET['page']; highlight_file(__FILE__); echo '<hr>'; if (isset($page)) { $path = strtolower($_GET['page']); // filter \ _ / if (preg_match("/\\_|\//", $path)) { echo "<p>bad hecker detect! </p>"; }else{ $path = str_replace("..\\", "../", $path); $path = str_replace("..", ".", $path); echo $path; echo '<hr>'; echo file_get_contents("./page/".$path); } } else die('ERROR: page should be given'); ``` 這題的考點是 preg_match 的錯誤使用,雖然看起來傳入了 `/\\_|\//` 但其實 `\` 並不能正常被過濾,第一次的 `\` 被跳脫後,傳入 php 內的 regular expression parser 時只剩下一個 `\` 被拿來跳過 `_`,因此整個 preg_match 變成只有過濾 `_` `/`。 然後下方 Line16 又很好心的將 `..\` 轉為 `../`,我們就有機會 path traversal,要注意 Line17 將 `..` 轉為 `.` 因此我們需要將 payload 的 `..` 變為 `....` 才能將整體變為 `../`。 能 path traversal 後,觀察到一直有個檔案我們很好奇,就是 `config.php`,我們第一個先去看他,即可看到通往 stage4 的檔案名稱了(不要不小心輸入 fake flag lol)。 ## stage4 ```php= <?php echo '<h1>👻 Stage 4 / 4</h1>'; highlight_file(__FILE__); echo '<hr>'; extract($_POST); if (isset($👀)) include($👀); else die('ERROR: 👀 should be given'); ``` 在 Line9 有個任意 include,但沒有$👀被賦值的地方。 這題用到了 [extract](https://www.php.net/manual/en/function.extract.php),而且參數用了 $_POST 因此讓我們能操作 POST 參數,來覆蓋任意變數可以拿來蓋 $👀。 最後就是要 include 什麼,關於這個 LFI2RCE 我們有很多 [招](https://blog.stevenyu.tw/2022/05/07/advanced-local-file-inclusion-2-rce-in-2022/),這邊就選擇 php filter chain,payload 可以參考 [這個](https://github.com/wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT),即可成功 RCE。 ## 結論 這題用到了都是 CTF 中 php 的 feature,也是很久之前就在 CTF 中就出現的利用方式,這次題目把他們出在一題讓大家回味一下(? 希望各位玩的開心 ouob
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up