Sky's blog

parse_url函数小记

Word count: 961 / Reading time: 5 min
2017/12/15 Share

前记

最近遇见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
<?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()函数会进行过滤
但是他利用了这样的获取方式

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
8
H:\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
4
H:\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
30
PHPAPI 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
<?php 
$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
5
H:\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
8
Example #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以前

CATALOG
  1. 1. 前记
  2. 2. 实战一
  3. 3. 实战二