前记
最近遇见parse_url()的次数还挺多的,于是记录一下它的一些小trick
实战一
题目来自2017swpu的一道web题1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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()
函数会进行过滤
但是他利用了这样的获取方式1
2$url=parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
而这就导致了我们的攻击点
我们看以下测试:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<?php
$url=parse_url($_SERVER['REQUEST_URI']);
var_dump($url);
parse_str($url['query'],$query);
var_dump($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!");
}
}
}
?>
我们先正常输入:http://localhost/web/trick1/parse.php?sql=select
可以看到,我们被正常的过滤1
2
3
4
5
6
7
8H:\wamp64\www\web\trick1\parse.php:3:
array (size=2)
'path' => string '/web/trick1/parse.php' (length=21)
'query' => string 'sql=select' (length=10)
H:\wamp64\www\web\trick1\parse.php:5:
array (size=1)
'sql' => string 'select' (length=6)
Stop hacking by using SQL injection!
但是如果这样:http://localhost///web/trick1/parse.php?sql=select
则1
2
3
4H:\wamp64\www\web\trick1\parse.php:3:boolean false
H:\wamp64\www\web\trick1\parse.php:5:
array (size=0)
empty
我们就可以绕过过滤,导致注入成功
因为这里用到了parse_url
函数在解析url时存在的bug
通过:///x.php?key=value
的方式可以使其返回False
具体可以看下parse_url()
的源码,关键代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30PHPAPI php_url *php_url_parse_ex(char const *str, size_t length)
{
char port_buf[6];
php_url *ret = ecalloc(1, sizeof(php_url));
char const *s, *e, *p, *pp, *ue;
...snip...
} else if (*s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
s += 2;
} else {
just_path:
ue = s + length;
goto nohost;
}
e = s + strcspn(s, "/?#");
...snip...
} else {
p = e;
}
/* check if we have a valid host, if we don't reject the string as url */
if ((p-s) < 1) {
if (ret->scheme) efree(ret->scheme);
if (ret->user) efree(ret->user);
if (ret->pass) efree(ret->pass);
efree(ret);
return NULL;
}
可以看到,在函数parse_url
内部,如果url是以//
开始,就认为它是相对url
而后认为url的部件从url+2开始。若p-s < 1
也就是如果url为///x.php
,则p = e = s = s + 2
,函数将返回 NULL。
再看PHP_FUNCTION
,line 351:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 /* {{{ proto mixed parse_url(string url, [int url_component])
Parse a URL and return its components */
PHP_FUNCTION(parse_url)
{
char *str;
size_t str_len;
php_url *resource;
zend_long key = -1;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &key) == FAILURE) {
return;
}
resource = php_url_parse_ex(str, str_len);
if (resource == NULL) {
/* @todo Find a method to determine why php_url_parse_ex() failed */
RETURN_FALSE;
}
若php_url_parse_ex
结果为 NULL,函数parse_url
将返回FALSE
实战二
题目来自2016asisctf的一道web题1
2
3
4
5
6
7
8
9
10
11
12
$data = parse_url($_SERVER['REQUEST_URI']);
var_dump($data);
$filter=["cache", "binarycloud"];
foreach($filter as $f)
{
if(preg_match("/".$f."/i", $data['query']))
{
die("Attack Detected");
}
}
这里如果我们输入http://localhost/web/trick1/parse2.php?/home/binarycloud/
就会被waf拦截1
2
3
4
5H:\wamp64\www\web\trick1\parse2.php:3:
array (size=2)
'path' => string '/web/trick1/parse2.php' (length=22)
'query' => string '/home/binarycloud/' (length=18)
Attack Detected
但是如果输入http://localhost/web/trick1//parse2.php?/home/binarycloud/
则会被当做相对url,
此时的parse2.php?/home/binarycloud/
都会被当做是data[‘path’]
而不再是query
,导致可以绕过过滤
但是需要注意的是:
查阅官方手册后:1
2
3
4
5
6
7
8Example #2 A parse_url() example with missing scheme
<?php
$url = '//www.example.com/path?googleguy=googley';
// Prior to 5.4.7 this would show the path as "//www.example.com/path"
var_dump(parse_url($url));
?>
刚漏洞问题只存在于php5.4.7以前