SSRF学习

基础

SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)

SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。

注释:除了http/https等方式可以造成ssrf,类似tcp connect 方式也可以探测内网一些ip 的端口是否开发服务,只不过危害比较小而已。

SSRF到反射XSS
尝试利用URL访问内部资源并使服务器执行操作

1
2
3
4
file:///
dict://
ftp://
gopher://

我们可以扫描内部网络和端口
如果它在云实例上运行,则尝试获取元数据

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$url = $_GET['url'];
$info = parse_url($url);
//假设www.site.com为白名单网站
if($info['host'] != 'www.baidu.com')
{
echo '目标网址不合法';
exit;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
$data = curl_exec($ch);
curl_close($ch);
echo $data;

上面这段代码我们知道,主要是进行网址过滤,如果用户输入的网址不是公司的网址,则服务器不会发起请求。
这段代码的目的是,防止用户输入其它恶意数据,造成攻击,比如:
http://www.site.com/test.php?url=http://10.0.0.16 –攻击者尝试内网漫游
http://www.site.com/test.php?url=http://www.hack.com –攻击者引导服务器访问不合法网址
新人写代码往往对一些函数并不是了解很深入,这段代码忽视了两个问题:

  1. parse_url原理是什么?
    parse_url只是负责字符串解析,它不保证你的协议真伪,这里我们不用http协议,而使用一个根本不存在的协议abc测试:
    1
    2
    3
    4
    <?php
    $url = 'http://www.baidu.com/test';
    $info = parse_url($url);
    var_dump($info);


1
2
3
4
<?php
$url = 'abc://www.baidu.com/test';
$info = parse_url($url);
var_dump($info);

  1. curl支持哪些协议?
    curl是基于libcurl实现的,支持的协议非常多。
    1
    libcurl supports HTTPS certificates, HTTP POST, HTTP PUT, FTP uploading, Kerberos, SPNEGO, HTTP form based upload, proxies, cookies, user+password authentication, file transfer resume, http proxy tunneling and more

那么问题来了,我们回到最开始那个代码,我们可以发现存在两处问题:

  1. 白名单只是检测了host,而没有检测协议。
  2. curl除了支持http协议,还支持file协议。

http://www.site.com/test.php?url=file://www.site.com/etc/passwd

php curl识别出来这是个file协议,他会忽略www.site.com,而是直接读取文件/etc/passwd。

dict协议应用
dict协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源,但是在SSRF中如果可以使用dict协议那么就可以轻易的获取目标服务器端口上运行的服务版本等信息。

实战

LCTF的一道SSRF签到题

输入baidu.com 返回了源码 可见是调用curl 命令

尝试用file 协议读取文件

后端代码会给我们提交的url内容在最后加一个/,需要通过?或者#的方法截断 需要进行url编码直接传#是会拼接成 /etc/passwd#/ 整体作为一个path

接着读取 flag

随便读一下源码

1
2
3
4
5
6
7
8
9
10
11
12
<?phpif(!$_GET['site']){ echo <<<EOF <html> <body> look source code: <form action='' method='GET'> <input type='submit' name='submit' /><input type='text' name='site' style="width:1000px" value="https://www.baidu.com"/> </form> </body> </html> EOF; die(); }
$url = $_GET['site']; $url_schema = parse_url($url);
$host = $url_schema['host'];
$request_url = $url."/";
if ($host !== 'www.baidu.com'){ die("wrong site"); }
$ci = curl_init();
curl_setopt($ci, CURLOPT_URL, $request_url);
curl_setopt($ci, CURLOPT_RETURNTRANSFER, 1);
$res = curl_exec($ci);
curl_close($ci);
if($res){ echo "<h1>Source Code:</h1>"; echo $request_url; echo "<hr />"; echo htmlentities($res); }
else{ echo "get source failed"; } ?>

平安科技的一道CTF
看到有参数为url 猜测是一道SSRF

尝试访问flag_file.php 弹出

直接使用127.0.0.1 不行,可能做了关键字过滤

把127.0.0.1转成整数或者localhost,成功拿到flag

第二届XCTF
Web400-百度内网漫游
题目给了一篇wooyun文章,通过查看得知是ssrf,随意在搜索框输入,返回百度的内容。
抓包,发现有一个link参数,根据ssrf的特性,确认此处存在ssrf漏洞。
于是我们要做的就是利用这个参数去访问其内网,但是经过测试发现,其过滤了IP,于是在自己的vps上放了一个302跳转:
利用跳转来访问内网

访问这个css文件

得到内网的域名,接下来访问这个内网域名

点了下导航栏下面的图片,直接给跳回去,但是发现地址栏多了一些东西,看到src,以及online几个参数,

通过gopher发送post数据包

通过gopher攻击内网数据库

mysql

2018 ISITDTU 的一道CTF

能够看出来是一道SSRF
于是利用file:// 读一下文件 输入file:///etc/passwd

可以看到后台判断了服务器是否为localhost,所以我们通过file://localhost/etc/passwd即可绕过限制。这里我们尝试读题目源码file://localhost/var/www/html/index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include_once "config.php";
if (isset($_POST['url'])&&!empty($_POST['url']))
{
$url = $_POST['url'];
$content_url = getUrlContent($url);
}
else
{
$content_url = "";
}
if(isset($_GET['debug']))
{
show_source(__FILE__);
}


?>

再读一下config.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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php


$hosts = "localhost";
$dbusername = "ssrf_user";
$dbpasswd = "";
$dbname = "ssrf";
$dbport = 3306;

$conn = mysqli_connect($hosts,$dbusername,$dbpasswd,$dbname,$dbport);

function initdb($conn)
{
$dbinit = "create table if not exists flag(secret varchar(100));";
if(mysqli_query($conn,$dbinit)) return 1;
else return 0;
}

function safe($url)
{
$tmpurl = parse_url($url, PHP_URL_HOST);
if($tmpurl != "localhost" and $tmpurl != "127.0.0.1")
{
var_dump($tmpurl);
die("<h1>Only access to localhost</h1>");
}
return $url;
}

function getUrlContent($url){
$url = safe($url);
$url = escapeshellarg($url);
$pl = "curl ".$url;
echo $pl;
$content = shell_exec($pl);
return $content;
}
initdb($conn);
?>

在数据库中且给出了我们一个空密码的mysql账户
gopher协议的主要功能是可以直接发起socket连接获取数据,而且由于mysql这里给出的密码是空密码,因此可以通过gopher发起sql请求来获取数据。

帮助生成gopher攻击mysql的payload

https://github.com/style-404/mysql_gopher_attack

redis

2016Hctf的一道题

hackme上一道的一道XSS加ssrf

参考

https://xz.aliyun.com/t/4420#toc-4