本篇文章對parse_url進行一個小結
0x01:parse_url
$url = "/baidu.com:80"; $url1 = "/baidu.com:80a"; $url2 = "//pupiles.com/about:1234"; $url3 = "//baidu.com:80a"; var_dump(parse_url($url)); var_dump(parse_url($url1)); var_dump(parse_url($url2)); var_dump(parse_url($url3));
執行以上代碼:,將得到下面的結果
/home/tr1ple/exp.php:7: bool(false) /home/tr1ple/exp.php:8: array(1) { 'path' => string(14) "/baidu.com:80a" } /home/tr1ple/exp.php:9: array(3) { 'host' => string(11) "pupiles.com" 'port' => int(1234) 'path' => string(11) "/about:1234" } /home/tr1ple/exp.php:10: array(2) { 'host' => string(9) "baidu.com" 'port' => int(80) }
$url4 = "//upload?/test/"; $url5 = "//upload?/1=1&id=1"; $url6 = "///upload?id=1"; var_dump(parse_url($url4)); var_dump(parse_url($url5)); var_dump(parse_url($url6));
將輸出:
/home/tr1ple/exp.php:6: array(2) { 'host' => string(6) "upload" 'query' => string(6) "/test/" } /home/tr1ple/exp.php:7: array(2) { 'host' => string(6) "upload" 'query' => string(9) "/1=1&id=1" } /home/tr1ple/exp.php:8: bool(false)
1.//upload?如果是//,則被解析成host, 后面的內容如果有/,被解析出path,而不是query了
2.如果path部分為///,則解析錯誤
感想:在實際上bypass的時候可以根據自己的目的多測試,去測試程序解析的反應
parse_url一般會用來解析$SERVER變量,其中幾個變量如下所示:
echo $_SERVER['REQUEST_URI']."<br/>"; echo $_SERVER['QUERY_STRING']."<br/>"; echo $_SERVER['HTTP_HOST']."<br/>"; #訪問http://localhost:3000/php/audit/5/parse1.php?url=baidu.com#test >>> /php/audit/5/parse1.php?url=/baidu.com url=/baidu.com localhost:3000
REQUEST_URI 是path+query部分(不包含fragment) QUERY_STRING: 主要是key=value部分 HTTP_HOST 是 netloc+port 部分。
curl和parse_url解析不一致:
curl:
curl里面,如果使用file協議的話,不管host是什么,都會嘗試在本地的path中尋找,所以能用file讀取本地文件時對host檢查是沒用的
完整url: scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment] 這里僅討論url中不含'?'的情況 php parse_url: host: 匹配最后一個@后面符合格式的host libcurl: host:匹配第一個@后面符合格式的host 如: http://u:p@a.com:80@b.com/ php解析結果: schema: http host: b.com user: u pass: p@a.com:80 libcurl解析結果: schema: http host: a.com user: u pass: p port: 80 后面的@b.com/會被忽略掉
tricks:
1.2017swpu的一道web題
<?php error_reporting(0); $_POST=Add_S($_POST); $_GET=Add_S($_GET); $_COOKIE=Add_S($_COOKIE); $_REQUEST=Add_S($_REQUEST); function Add_S($array){ foreach($array as $key=>$value){ if(!is_array($value)){ $check= preg_match('/regexp|like|and|\"|%|insert|update|delete|union|into|load_file|outfile|\/\*/i', $value); if($check) { exit("Stop hacking by using SQL injection!"); } }else{ $array[$key]=Add_S($array[$key]); } } return $array; } function check_url() { $url=parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); $key_word=array("select","from","for","like"); foreach($query as $key) { foreach($key_word as $value) { if(preg_match("/".$value."/",strtolower($key))) { die("Stop hacking by using SQL injection!"); } } } } ?>
我們關注這里的check_url()函數,首先使用parse_url獲取 $_SERVER['REQUEST_URI'], 而正常的注入payload比如:
http://localhost///web/trick1/parse.php?sql=select
將會被檢測到注入,然而parse_url函數在解析url的時候存在bug,通過///x.php?key=value的方式將返回false,此時將不再進入foreach循環進行判斷,
所以可以進行注入,今年的全國大學生信息安全競賽初賽就出過這一個trick,先繞過parse,然后再反序列化==,做題時當時卡到這了,遇到卡住的點可能就是需要去繞過的點!
2.題目來自2016asisctf的一道web題
<?php function waf(){ $INFO = parse_url($_SERVER['REQUEST_URI']); var_dump($INFO); var_dump($_GET); parse_str($INFO['query'], $query); $filter = ["union", "select", "information_schema", "from"]; foreach($query as $q){ foreach($filter as $f){ if (preg_match("/".$f."/i", $q)){ die("attack detected!"); } } } $sql = "select * from ctf where id='".$_GET['id']."'"; var_dump($sql); } waf();
關注點在$_SERVER['REQUEST_URI'],在parse_解析后,要檢測查詢的參數里面是否包含sql查詢關鍵字,那么我們是不是可以構造惡意url使parse按照非預期的進行解析,那么的確bypass的過程就是如此神奇。。
payload1:
http://localhost//exp.php?/1=1&id=1' union select 1,2,3#
此時parse_url解析后的REQUEST_URI為:
php7.2測試:
php5.3測試:
解析的不同還和php的版本有關系,那么在5.3的版本中,此時query將為空,那么將繞過過濾,並且此時$_GET方式傳遞過來的id參數的值正是我們想要的payload,7.2版本是先識別查詢符號?,然后把后面的當作查詢字符串,而5.3版本是先把url分段,//到/為host,/后為path。參數才是前后端交互的橋梁,用戶的不可信數據也正是通過參數進行傳遞,tricks也正是用來保護參數不被過濾嗎,7.2將參數首先提取出來,更注重了參數路徑的安全性,越不可信的數據先處理。
payload2:
http://localhost///exp.php?id=1' union select 1,2,3#
php7.2:
從上面兩幅圖中可以看出,這個payload對5.3和7.2都是適用的,返回false來bypass,id參數中包含的payload依然存在!
3.網鼎杯第三場comein
<?php ini_set("display_errors",0); $uri = $_SERVER['REQUEST_URI']; // 請求的uri var_dump($uri); if(stripos($uri,".")){ // uri中要么不出現“.” 要么以“.”開頭 die("Unkonw URI."); } if(!parse_url($uri,PHP_URL_HOST)){ //嘗試解析uri $uri = "http://".$_SERVER['REMOTE_ADDR'].$_SERVER['REQUEST_URI']; var_dump($uri); } $host = parse_url($uri,PHP_URL_HOST); //再次解析uri var_dump($host); if($host === "c7f.zhuque.com"){ echo "flag sasa"; }
首先要繞過stripos,開頭為.即可繞過,第二處使用parse_url來處理uri,即path+query部分,正常的應該是如下圖所示,然后第二個if條件將調用parse_url函數對$uri變量進行處理,提取出其中的host信息,但是其中
明顯不再包含host頭了,所以會拼接上http://,然后再用parse_url進行host的頭的提取,其中$_SERVER['REQUEST_URI']是可控的,並且第一個字符應該為點.(在BURP中操作),然后第三個if條件中提取host頭部時,
不能用第二個if條件的拼接的host頭,因為后面的部分是我們可以控制的,所以注入一個@符號那么parse_url再次解析的時候將把127.0.0.1.解析成用戶名,@符號后面的將解析成要訪問的網址,然后@后面改為.@c7f.zhuque.com/,但是此時會bad url,因為apache在解析url時出現了問題,因為我們並沒有.@c7f.zhuque.com/這個目錄,所以還需要調整paylaod同時滿足后端PHP判斷和apache的解析,要滿足apache的解析,只需要跳到一個存在的文件即可,比如index.php,即此時繼續拼接payload,為.@c7f.zhuque.com/..//index.php,/后面的將被解析為path,其中..//,先跳到和.@c7f.zhuque.com同級目錄,然后此目錄下的/index.php,此時才能滿足apache對文件路徑的解析,其中../index.php和.././index.php都不行,因為apache拼接出來都找不到index.php這個文件
該漏洞出現的原因是,parse_url函數和apache對地址的解析方式不同。
PHP認為127.0.0.1.是個user,c7f.zhuque.com是真實host
apache認為127.0.0.1是host,.@c7f.zhuque.com/是一個路徑,后邊..//index.php退回根目錄,再訪問index.php
參考:
https://skysec.top/2017/12/15/parse-url%E5%87%BD%E6%95%B0%E5%B0%8F%E8%AE%B0/
https://blog.csdn.net/publicStr/article/details/83004265
https://github.com/jiangsir404/Audit-Learning/blob/master/filter_var%E5%87%BD%E6%95%B0%E7%BC%BA%E9%99%B7.md