FTP 服务(vsftpd)

本文合并自:03-FTP服务.md、10-FTP服务.md、1-11-FTP服务部署.md

适用环境:CentOS / RHEL 5、6、7(vsftpd 2.x / 3.x)。文中凡涉及版本差异之处均并列标注,请按自己的系统版本选用。

内容覆盖:FTP 协议与端口、主动/被动模式原理与选择、传输模式、常见 FTP 服务端软件对比、FTP/FTPS/SFTP 区别、vsftpd 安装与 vsftpd.conf 全参数详解、匿名用户/本地用户/虚拟用户三种模式实战、chroot 监牢、用户黑白名单、目录消息与欢迎横幅、xferlog 与 vsftpd.log 日志分析、限速与并发限制、xinetd 托管、TCP Wrapper 与 firewalld/iptables 放行、FTPS 加密、客户端命令与排错清单,并附综合练习与参考答案。


一、FTP 概述与原理

1.1 FTP 的作用

FTP 是 File Transfer Protocol(文件传输协议)的英文简称,中文简称"文传协议",用于在 Internet 上控制文件的双向传输(上传与下载)。

FTP 的特点:

  • 成本低:服务端软件多为开源免费(vsftpd、proftpd 等)。
  • 跨平台:Windows、Linux、macOS、各类嵌入式设备均有成熟客户端。
  • 可跨公网:只要放行相应端口,即可通过互联网传输文件。
  • 协议成熟:RFC 959 定义,浏览器、下载工具、yum/apt 等均原生支持。

常用的 FTP 客户端工具:ftp、lftp、wget、curl、FileZilla、浏览器。

1.2 FTP 的端口

FTP 是一个"双通道"协议:控制连接负责传命令,数据连接负责传文件内容与目录列表。

端口 名称 用途
TCP 21 命令端口 / 控制连接 客户端发送登录、LIST、RETR、STOR 等命令,服务器返回状态码
TCP 20 数据端口(主动模式) 主动模式下由服务器端用 20 端口主动发起数据连接
随机高端口 数据端口(被动模式) 被动模式下服务器监听一个 >1023 的临时端口,由客户端来连
端口类型 默认值 说明
命令端口 21 控制连接,用于发送命令
数据传输端口 受模式影响 被动模式下两端都是随机端口;主动模式下服务器使用 20 端口,客户端使用随机端口

要点:FTP 服务在未传输数据时只监听 21 端口,只有真正传输数据(ls、get、put)时才会打开数据端口。因此用 netstat 观察时,平时看不到 20 端口或高端口。

1.3 主动模式与被动模式

FTP 有两种连接模式,都是针对数据传输而言,并且是从服务器的角度来命名的:服务器主动去连客户端叫主动模式,服务器被动等客户端来连叫被动模式。

模式 控制连接 数据连接
主动模式(PORT) 客户端 → 服务器 21 服务器 20 → 客户端(>1024 随机端口)
被动模式(PASV) 客户端 → 服务器 21 客户端 → 服务器(>1024 随机端口)
模式 控制通道端口 数据通道 连接发起方 数据端口说明
主动模式(Active) 服务器 TCP 21 服务器通过 TCP 20 连接客户端指定端口 服务器主动连接客户端 服务器固定 20 端口,客户端随机端口
被动模式(Passive) 服务器 TCP 21 服务器打开临时端口,客户端主动连接 客户端主动连接服务器 服务器在指定范围内随机端口,客户端随机端口

1.3.1 主动模式(PORT)完整交互过程

客户端提供一个端口用于接收数据,然后由服务器发起数据连接(服务器主动):

  1. FTP 客户端首先用自己的一个随机高端口与服务器的 TCP 21 端口建立连接,这条连接用于发送命令(控制连接)。
  2. 客户端需要接收数据时(例如执行 ls 或 get),在该控制通道上发送 PORT 命令,PORT 命令中包含客户端用于接收数据的 IP 和端口号(一个 >1024 的随机端口)。
  3. 传送数据时,服务器端通过自己的 TCP 20 端口主动连接至客户端在 PORT 命令中指定的那个端口,发送数据。
  4. 也就是说,FTP server 必须与客户端建立一个新的连接来传送数据,这个新连接的发起方是服务器。

图示说明(箭头方向代表 TCP 连接的发起方向):

主动模式(PORT)

        客户端                              服务器
   随机端口 N (>1024)  ──────────────────►  21   (控制连接,客户端发起)
                       ◄──── 220/230 ────
   发送 PORT N+1 命令  ──────────────────►  21
   随机端口 N+1        ◄──────────────────  20   (数据连接,服务器发起!)

1.3.2 被动模式(PASV)完整交互过程

服务器提供一个端口用于传输数据,然后由客户端发起数据连接(服务器被动):

  1. 建立控制通道的过程与主动模式类似:客户端用随机高端口连接服务器 21 端口。但建立连接后客户端发送 PASV(Pasv)命令而不是 PORT 命令。
  2. 服务器收到 PASV 命令后,在本机打开一个临时端口(端口号大于 1023 小于 65535),并通过控制连接把这个端口号通知客户端,表示"请到这个端口上来传数据"。
  3. 客户端主动连接 FTP 服务器的这个端口,FTP 服务器通过该端口传送数据。

图示说明:

被动模式(PASV)

        客户端                              服务器
   随机端口 N (>1024)  ──────────────────►  21   (控制连接,客户端发起)
   发送 PASV 命令      ──────────────────►  21
                       ◄─ 227 Entering Passive Mode (x,x,x,x,p1,p2)
   随机端口 N+1        ──────────────────►  随机端口 P (数据连接,客户端发起)

其中 227 响应里的 p1,p2 换算出实际数据端口:端口 = p1 * 256 + p2。

1.3.3 默认端口总结

用途 默认端口 说明
命令端口 21 传输控制命令
数据端口(主动模式) 20 服务器端固定使用
数据端口(被动模式) 随机高端口 可通过 pasv_min_port/pasv_max_port 配置指定范围

1.4 两种模式在防火墙与 NAT 环境下的差异与选择

哪种模式更好?

  • 主动 FTP 对服务器管理有利,对客户端管理不利:服务端只需放行 21 和 20 两个固定端口即可,非常好管;但服务端要主动连接客户端的高端口,而客户端的个人防火墙默认只拒绝入站包,因此这个入站的数据连接常常被客户端防火墙阻塞。客户端处于 NAT 之后时,PORT 命令里携带的还是内网私有 IP,服务器根本连不上,问题更严重。
  • 被动 FTP 对客户端管理有利,对服务端管理不利:客户端只发起出站连接,任何个人防火墙和 NAT 都能顺利通过;但客户端要主动连接服务端的随机高端口,服务端防火墙如果没放行这一大段端口就会失败。
  • 折衷方案(生产推荐):使用被动模式,并在 vsftpd 中指定一个较小的端口范围(如 30001–30010),只对该范围开放防火墙。这样既照顾了 NAT 后的客户端,也不需要在服务端大开端口。

其他实践结论:

  • vsftpd 默认工作在被动模式。
  • 被动模式在防火墙 / NAT 环境中更常用,需要固定并放行数据端口范围。
  • yum、wget、大多数图形客户端默认使用被动模式。因此如果服务端配置了 pasv_enable=NO(只允许主动模式),yum 通过 FTP 源装软件会失败。
  • 服务端处于 NAT 之后(有公网 IP 映射)时,被动模式还需配置 pasv_address=公网IP,否则 227 响应里回给客户端的是内网地址。

1.5 传输模式:ASCII 与 Binary

FTP 的"传输模式"与"连接模式"是两个不同概念,传输模式指的是文件内容如何编码传输。

传输模式 客户端命令 说明 适用场景
ASCII(文本) ascii 传输过程中会转换行结束符(Windows 的 CRLF 与 Unix 的 LF 互转) 纯文本文件、脚本、配置文件
Binary(二进制) binary 或 bin 原样逐字节传输,不做任何转换 压缩包、图片、可执行程序、数据库文件、RPM 包等

重要提示:传输二进制文件时若误用 ASCII 模式,文件会被破坏(体积改变、无法解压或运行)。实践中除纯文本外,一律先执行 binary。传输日志 xferlog 中第 6 个字段就记录了本次传输用的是 a(ASCII)还是 b(binary)。

1.6 常见 FTP 服务端软件对比

软件 特点 说明
vsftpd very secure FTP daemon,安全性优先,配置简单,性能好 RHEL/CentOS 默认自带并推荐,本文主讲;支持匿名/本地/虚拟用户、chroot、限速、FTPS
proftpd 配置风格类似 Apache(<Directory> 段),功能非常丰富,模块化 适合需要复杂目录级 ACL、虚拟主机的场合,配置相对复杂
pure-ftpd 轻量、安全,虚拟用户支持极佳(可对接 MySQL/LDAP/PostgreSQL) 适合大量虚拟用户的托管环境,参数以命令行/配置目录形式给出

选型建议:RHEL 系发行版做常规文件分发与内网共享,直接用 vsftpd 即可,官方源支持、SELinux 策略齐备。

1.7 FTP、FTPS 与 SFTP 的区别

项目 FTP FTPS SFTP
全称 File Transfer Protocol FTP over SSL/TLS SSH File Transfer Protocol
加密 完全不加密,账号密码与数据都是明文 用 SSL/TLS 加密控制连接和数据连接 用 SSH 加密,整条通道加密
端口 21(控制)+ 20/随机(数据) 显式 FTPS:21 + 数据端口;隐式 FTPS:990 22(复用 sshd 端口)
底层服务 vsftpd / proftpd 等 同 FTP 服务端开启 ssl_enable sshd,与 FTP 服务端毫无关系
是否双通道 是 是(防火墙仍需处理数据端口) 否,单通道,防火墙友好
需要证书 不需要 需要服务端证书 使用 SSH 主机密钥
常见客户端 ftp、lftp、FileZilla lftp、FileZilla(选"显式 TLS") sftp、scp、FileZilla(选 SFTP)

结论:只要装了 sshd,SFTP 是零成本、最省事的加密文件传输方案;若必须保留 FTP 协议语义(例如老客户端、匿名下载),再用 FTPS。明文 FTP 只适合内网或纯匿名公开下载。

1.8 FTP 用户类型

vsftpd 支持三类登录用户,这也是后文实战的三条主线。

用户类型 说明 认证来源 默认登录目录
匿名用户(anonymous) 无需真实账号,任何人可登录,用于公开下载 无(密码任意/为空) /var/ftp(可用 anon_root 改)
本地用户(local) FTP 服务器上真实存在的系统用户 /etc/passwd + /etc/shadow,走 PAM 该用户的家目录(可用 local_root 改)
虚拟用户(virtual) 只用于 FTP 的"假"用户,系统里不存在同名账号 独立的用户库(Berkeley DB / MySQL),映射到一个系统宿主账号 由 local_root 或 user_config_dir 中的个性化配置指定

匿名用户的两个名字:

用户名 系统中是否存在 默认登录目录
ftp 存在(/etc/passwd 中有 ftp 用户) ftp 用户的家目录 /var/ftp
anonymous 不存在(是 ftp 的别名,纯匿名账号) /var/ftp

两者密码均可为任意值或空。

虚拟用户的优势:不在系统中创建真实账号,因此即使 FTP 账号泄露也无法登录系统(无 shell、无邮箱、无 SSH),安全性远高于直接开放本地用户;且可对每个虚拟用户单独设定目录与读写权限。


二、vsftpd 安装与配置文件详解

2.1 安装

# 服务端
yum install vsftpd -y
sudo yum install -y vsftpd

# 客户端
yum install ftp lftp -y

# 一次装齐(练习常用)
sudo yum install vsftpd ftp -y

虚拟用户环节还需要 db4-utils(CentOS 6)或 libdb-utils(CentOS 7),用于 db_load 命令:

# CentOS 6
yum install db4-utils -y
# CentOS 7
yum install libdb-utils -y

2.2 启动、停止与开机自启

CentOS 6(SysV init):

/etc/init.d/vsftpd start
/etc/init.d/vsftpd stop
/etc/init.d/vsftpd restart
/etc/init.d/vsftpd reload      # 修改配置后重新加载,不断开已有连接
chkconfig vsftpd on            # 开机自启
chkconfig --list vsftpd

也可使用 service vsftpd start 等价写法。

CentOS 7(systemd):

systemctl start vsftpd
systemctl stop vsftpd
systemctl restart vsftpd
systemctl reload vsftpd
systemctl enable vsftpd        # 开机自启
systemctl status vsftpd

修改 vsftpd.conf 后必须 reload 或 restart 才生效。CentOS 6 用 /etc/init.d/vsftpd reload,CentOS 7 用 systemctl reload vsftpd。

2.3 验证监听端口

netstat -ntl | grep :21
# tcp  0  0 0.0.0.0:21  0.0.0.0:*  LISTEN

netstat -ntlup | grep :21      # 带进程名

ss -ntl | grep :21             # CentOS 7 推荐用 ss

lsof -i:21
# COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
# vsftpd  3321 root    3u  IPv4  21400      0t0  TCP *:ftp (LISTEN)

被动模式在未传输数据时只监听 21 端口,数据传输时才打开高端口。

2.4 目录与文件结构

路径 作用
/etc/vsftpd/vsftpd.conf 主配置文件,所有参数都写在这里
/etc/vsftpd/ftpusers PAM 级黑名单,此文件中的用户一律禁止登录 FTP(由 /etc/pam.d/vsftpd 中的 pam_listfile 模块读取),默认含 root 及各系统账号
/etc/vsftpd/user_list 用户名单文件,配合 userlist_enable / userlist_deny 作黑名单或白名单,路径由 userlist_file 指定
/etc/vsftpd/chroot_list chroot 名单文件(默认不存在,需手动创建),路径由 chroot_list_file 指定
/etc/pam.d/vsftpd vsftpd 的 PAM 认证配置,虚拟用户改造的关键文件
/etc/logrotate.d/vsftpd vsftpd 日志轮转配置
/var/ftp 匿名用户默认根目录(即系统 ftp 用户的家目录)
/var/ftp/pub 匿名用户公共子目录,通常在此赋写权限做上传区
/var/log/xferlog 标准 xferlog 格式传输日志
/var/log/vsftpd.log vsftpd 自有格式日志
/usr/sbin/vsftpd 主程序
/etc/xinetd.d/vsftpd 由 xinetd 托管时的配置文件(CentOS 6 及之前,需手动创建)

2.5 查看有效配置

配置文件里注释很多,用下面命令只看生效的行:

grep -v ^# /etc/vsftpd/vsftpd.conf | grep -v ^$

完整参数说明查 man 文档:

man 5 vsftpd.conf

2.6 vsftpd.conf 参数逐项详解(分类)

配置文件语法:参数=值,等号两边不能有空格,一行一条,# 开头为注释。布尔值写 YES/NO(大写)。

2.6.1 匿名用户相关

参数 默认值 说明
anonymous_enable=YES YES(CentOS 6)/ 视版本 是否允许匿名用户(ftp、anonymous)登录
anon_upload_enable=YES NO 是否允许匿名用户上传文件(还需 write_enable=YES 且目录可写)
anon_mkdir_write_enable=YES NO 是否允许匿名用户创建目录
anon_other_write_enable=YES NO 是否允许匿名用户执行删除、重命名、覆盖等其他写操作(delete、rename)
anon_umask=022 077 匿名用户上传文件时使用的 umask。022 → 文件 644;0222 → 文件 444;0777 → 文件 000
anon_root=/var/ftp /var/ftp 匿名用户登录后的初始(根)目录
anon_world_readable_only=YES YES 匿名用户只能下载"所有人可读"的文件。设为 NO 可下载非 world-readable 文件
anon_max_rate=100000 0(不限) 匿名用户最大传输速率,单位字节/秒
chown_uploads=YES NO 是否修改匿名用户上传文件的所有者;开启后上传文件权限受 chown_upload_mode 影响
chown_username=nobody root 与 chown_uploads 配合,指定匿名上传文件的新所有者用户名
chown_upload_mode=0644 0600 与 chown_uploads 配合,指定被改属主后文件的权限模式
no_anon_password=YES NO 匿名登录时不询问密码,直接进入
deny_email_enable=YES NO 启用后可拒绝以 banned_email_file 中的邮箱作为匿名口令的登录
banned_email_file=/etc/vsftpd/banned_emails — 配合 deny_email_enable 的被禁邮箱列表

2.6.2 本地用户相关

参数 默认值 说明
local_enable=YES YES 是否允许本地(系统)用户登录。本地用户即 FTP 服务器上的系统用户,密码就是系统登录密码
write_enable=YES NO(编译默认)/ 配置文件里给 YES 写操作的总开关,上传、删除、创建目录、重命名等一切写操作都必须先开这个
download_enable=YES YES 是否允许下载。设为 NO 则任何用户都不能 get
local_umask=022 077 本地用户新建文件/目录的权限掩码。022 → 文件 644、目录 755;0066 → 文件 600
local_root=/ftpdata 用户家目录 统一指定所有本地用户登录后的初始目录;不影响用户 SSH 登录系统时的家目录
local_max_rate=100000 0(不限) 本地用户最大传输速率,单位字节/秒
guest_enable=YES NO 启用虚拟用户(guest)模式,把所有非匿名登录都映射成 guest_username 指定的系统账号
guest_username=virtual ftp 虚拟用户映射到的系统宿主账号
user_config_dir=/etc/vsftpd/vusers_dir — 用户个性化配置目录:目录中以用户名为文件名,可为每个(虚拟)用户单独设定参数
virtual_use_local_privs=YES NO 让虚拟用户拥有与本地用户相同的权限(受 write_enable 控制),而不是匿名用户级别的权限

local_root 相比"逐个修改 /etc/passwd 中所有用户的家目录"更安全、副作用更小,推荐使用。

2.6.3 权限与限制(chroot 监牢)

参数 默认值 说明
chroot_local_user=YES NO 把所有本地用户禁锢(chroot)在其初始目录内,不能 cd 到上层目录
chroot_list_enable=YES NO 启用 chroot 名单功能
chroot_list_file=/etc/vsftpd/chroot_list /etc/vsftpd/chroot_list chroot 名单文件路径,一行一个用户名(文件默认不存在,需手动创建)
allow_writeable_chroot=YES NO vsftpd 2.3.5+/CentOS 7 必需:允许被 chroot 的根目录本身可写,否则报 500 OOPS: vsftpd: refusing to run with writable root inside chroot
secure_chroot_dir=/usr/share/empty — vsftpd 内部使用的空目录,必须存在且不可写,不要改

chroot_local_user 与 chroot_list_enable 的组合逻辑(重要):

chroot_local_user chroot_list_enable chroot_list 的含义
NO YES 名单中的用户被 chroot(黑名单式:只锁这几个人)
YES YES 名单中的用户不被 chroot(例外名单:除这几个人外都锁)
YES NO 所有本地用户都被 chroot
NO NO 所有本地用户都不被 chroot(默认,可随意浏览整个文件系统)

匿名用户默认就在 chroot 环境中,无需额外配置。

概念补充:chroot(change root)用于把用户活动范围锁定在登录目录内,登录目录成为该用户视角下的 /,增强安全性。注意在 shell 里直接 chroot /mnt/ 会因目标目录缺少 bash 及其依赖库而报错,只能 chroot 一个包含完整运行环境的目录;而 vsftpd 的 chroot 不需要目标目录里有 shell,因为 FTP 传输不依赖目标目录内的可执行文件。

2.6.4 连接与性能

参数 默认值 说明
listen=YES YES 是否以 standalone(独立守护进程)方式运行 IPv4 监听。由 xinetd 托管时必须改为 NO
listen_ipv6=NO NO 是否以 standalone 方式监听 IPv6。listen 与 listen_ipv6 不能同时为 YES,否则服务启动失败(CentOS 7 默认模板里 listen=NO、listen_ipv6=YES)
listen_address=10.0.0.61 — 只在指定 IP 上监听(多网卡服务器常用)
listen_port=21 21 控制连接监听端口,可改为非标准端口
max_clients=300 0(不限) 服务器允许的总并发连接数,仅 standalone 模式有效
max_per_ip=3 0(不限) 单个客户端 IP 的最大连接数,仅 standalone 模式有效
idle_session_timeout=300 300 控制连接空闲多少秒后断开(单位秒)
data_connection_timeout=300 300 数据连接建立/传输超时时间(单位秒)
accept_timeout=60 60 被动模式下等待客户端来连接数据端口的超时
connect_timeout=60 60 主动模式下连接客户端数据端口的超时
connect_from_port_20=YES YES 主动模式的数据连接是否从服务器 20 端口发出(即是否支持标准主动模式)
pasv_enable=YES YES 是否启用被动模式。设为 NO 则只允许主动模式
pasv_min_port=30001 0(随机) 被动模式数据端口范围下限
pasv_max_port=30010 0(随机) 被动模式数据端口范围上限
pasv_address=1.2.3.4 — 服务器在 NAT 之后时,227 响应中回给客户端的公网地址
pam_service_name=vsftpd vsftpd PAM 服务名,对应 /etc/pam.d/ 下的文件名。虚拟用户时改为自定义文件名
nopriv_user=nobody nobody vsftpd 以非特权身份运行时使用的账号
session_support=NO NO 是否记录 utmp/wtmp 会话(开启后 who 能看到 FTP 会话,但有性能开销)
ls_recurse_enable=YES NO 是否允许 ls -R 递归列目录(大目录下会消耗大量资源)
ascii_upload_enable=YES NO 是否允许 ASCII 模式上传
ascii_download_enable=YES NO 是否允许 ASCII 模式下载
utf8_filesystem=YES NO 文件系统使用 UTF-8 编码,用于解决中文文件名乱码

2.6.5 日志相关

参数 默认值 说明
xferlog_enable=YES YES 是否打开传输日志功能(记录上传/下载)
xferlog_file=/var/log/xferlog /var/log/xferlog 标准 xferlog 格式日志文件路径
xferlog_std_format=YES YES 使用 wu-ftpd 标准 xferlog 格式;改为 NO 则改用 vsftpd 自有格式并写入 vsftpd_log_file
dual_log_enable=YES NO 同时产生 xferlog 和 vsftpd.log 两份日志
vsftpd_log_file=/var/log/vsftpd.log /var/log/vsftpd.log vsftpd 自有格式日志文件路径
log_ftp_protocol=YES NO 记录所有 FTP 协议交互命令与响应,调试排错利器(需 xferlog_std_format=NO)
syslog_enable=YES NO 把日志送到系统 syslog 而不是自有文件

日志组合速记:

  • 只要 xferlog:xferlog_enable=YES + xferlog_std_format=YES
  • 只要 vsftpd.log:xferlog_enable=YES + xferlog_std_format=NO
  • 两份都要:xferlog_enable=YES + dual_log_enable=YES

2.6.6 用户访问控制

参数 默认值 说明
userlist_enable=YES YES 是否启用用户名单功能
userlist_deny=YES YES 关键参数:YES 表示 user_list 是黑名单(名单内禁止登录);NO 表示是白名单(只有名单内允许登录)
userlist_file=/etc/vsftpd/user_list /etc/vsftpd/user_list 名单数据文件路径,一行一个用户名
tcp_wrappers=YES YES 是否支持 TCP Wrappers(/etc/hosts.allow、/etc/hosts.deny)作 IP 级访问控制
cmds_allowed=ABOR,CWD,LIST,... — 白名单式限制客户端可执行的 FTP 协议命令
cmds_denied=DELE,RMD — 黑名单式禁止某些 FTP 协议命令
deny_file={*.mp3,*.exe} — 拒绝访问匹配的文件名模式
hide_file={*.log,.private} — 隐藏匹配的文件(仍可访问,只是 ls 不显示)

userlist_enable 决定"名单功能是否有效",userlist_deny 决定"名单是允许还是拒绝",两者必须配合理解。

2.6.7 欢迎信息与目录消息

参数 默认值 说明
ftpd_banner=Welcome to my ftp server !! — 直接以字符串形式设定登录欢迎横幅(220 响应)
banner_file=/etc/vsftpd/banner — 从文件读取欢迎横幅,可写多行内容甚至字符画。与 ftpd_banner 同时存在时 banner_file 优先
dirmessage_enable=YES YES 是否启用目录消息:进入某目录时显示该目录下 message_file 的内容
message_file=.message .message 目录消息文件名

类似的消息提示机制在多数服务中都有:系统登录用 /etc/motd,sshd 登录前用 Banner /etc/ssh_banner。

2.7 常用配置指令速览(带注释的配置片段)

anonymous_enable=YES                # 启用匿名登录
local_enable=YES                    # 启用本地用户登录(本地用户即 FTP 服务器的系统用户)
write_enable=YES                    # 写操作的总开关
download_enable=NO                  # 是否允许下载,默认 YES
local_umask=022                     # 本地用户创建文件时使用的 umask
anon_umask=0222                     # 匿名用户创建文件时使用的 umask
anon_upload_enable=YES              # 是否允许匿名用户上传
anon_mkdir_write_enable=YES         # 是否允许匿名用户创建目录
chown_uploads=YES                   # 是否修改匿名用户上传文件的所有者
                                    # 修改后匿名用户上传文件的权限将受 chown_upload_mode 影响
                                    # 此选项与 chown_username 配合
# chown_username=nobody             # 与 chown_uploads 配合,指定新的所有者用户名
chroot_local_user=YES               # 把本地用户限制在其初始目录里
local_root=/ftp                     # 定义本地用户的初始目录
anon_root=/ftp                      # 定义匿名用户的初始目录

userlist_enable=YES                 # 启用用户名单
userlist_deny=no                    # 定义用户名单类型(白名单/黑名单)
pasv_min_port=33301                 # 被动模式的最小端口
pasv_max_port=33310                 # 被动模式的最大端口
anon_max_rate=102400                # 限制匿名用户最大传输速率,单位为字节
local_max_rate=102400               # 限制本地用户最大传输速率,单位为字节
max_clients=1000                    # 最多允许的并发连接数
max_per_ip=10                       # 每个 IP 的最大连接数

三、三种用户模式实战

3.0 权限判断的"两道门原则"

两道门原则:任何服务的权限控制都要同时经过"服务本身的权限配置"和"操作系统的文件权限",两者都允许才真正有权限;有防火墙时还要加上"防火墙放行"这第三道门。SELinux 开启时则还有第四道门。

排查 FTP 权限问题时,请依次检查:vsftpd.conf 参数 → 文件/目录属主与权限 → SELinux 布尔值与文件上下文 → 防火墙。

3.1 匿名用户访问

3.1.1 禁止匿名登录

vim /etc/vsftpd/vsftpd.conf
anonymous_enable=NO
# CentOS 6
/etc/init.d/vsftpd reload
# CentOS 7
systemctl reload vsftpd

此时 ftp 和 anonymous 均无法登录。

3.1.2 匿名用户的下载

匿名用户默认登录到 /var/ftp(即 /etc/passwd 中 ftp 用户的家目录)。能否下载取决于两层权限:

  1. 服务配置(anonymous_enable=YES);
  2. 操作系统权限。

目录权限对匿名访问的影响(实验数据):

/var/ftp 权限 能否 ls 能否 get 说明
755(默认) 能 能 有 rx 权限
750 不能 不能 其他用户无任何权限,不能进入目录
754 不能 不能 只有 r 没有 x,不能 cd 进入目录
751 不能 能 只有 x 没有 r,可 cd 但不能列目录;知道文件名仍可下载

对文件本身还需要 r 权限才能下载。

结论:匿名用户对登录目录需有 rx 权限、对目录内文件需有 r 权限,即可下载。

匿名下载的完整最小配置:

anonymous_enable=YES
anon_root=/var/ftp
download_enable=YES
# 放几个测试文件
cp /etc/fstab /etc/inittab /var/ftp/pub/
chmod 644 /var/ftp/pub/fstab /var/ftp/pub/inittab
chmod 755 /var/ftp /var/ftp/pub

测试:

ftp 10.0.0.61
# Name: ftp      密码:直接回车
ftp> cd pub
ftp> ls
ftp> get fstab
ftp> bye

3.1.3 匿名用户的上传

默认不允许匿名上传。直接对 /var/ftp 赋 777 会导致服务拒绝启动或登录失败:

500 OOPS: vsftpd: refusing to run with writable anonymous root

因为 vsftpd 出于安全考虑不允许匿名根目录本身可写。正确做法是在登录目录下再建一个子目录用于上传:

chmod 755 /var/ftp          # 根目录保持不可写
mkdir -p /var/ftp/pub
chmod 777 /var/ftp/pub      # 只让子目录可写

更规范的做法是把上传目录的属主给 ftp 用户,而不是给 777:

mkdir -p /var/ftp/upload
chown ftp:ftp /var/ftp/upload
chmod 755 /var/ftp/upload

同时在配置中开启服务端的写权限:

anonymous_enable=YES
write_enable=YES             # 写操作总开关,必须开
anon_upload_enable=YES       # 允许匿名上传文件
anon_mkdir_write_enable=YES  # 允许匿名创建目录
# CentOS 6
/etc/init.d/vsftpd reload
# CentOS 7
systemctl reload vsftpd

结论:匿名用户只能对登录目录内的子目录上传,且该子目录需要写权限(或属主为 ftp),并需开启 write_enable、anon_upload_enable 与 anon_mkdir_write_enable。

3.1.4 匿名用户删除、重命名、覆盖

anon_other_write_enable=YES

开启后匿名用户可执行 delete、rename、mkdir 等操作。在 RHEL 6 下实测:家目录(/var/ftp)里的文件不能删(因为根目录本身不可写),但可写子目录里的文件可以删。

生产环境不建议开启此参数,包括允许匿名写,安全风险很大——任何人都能删改文件。

3.1.5 允许下载刚上传的文件(anon_umask 与 anon_world_readable_only)

匿名上传的文件默认权限为 600,而 anon_world_readable_only=YES(默认)规定匿名用户只能下载"所有人可读"的文件,所以 600 权限的文件上传后自己都下载不了。

-rw-------  1 14  50   845 fstab      不可以下载
-rw-r--r--  1 0   0   1666 inittab    可以下载

(注意 14 50 即 uid=14、gid=50,正是 ftp 用户,说明是匿名上传的文件。)

方法一:修改上传文件的 umask

anon_umask=022

上传后文件权限变为 644,可下载。若希望上传文件权限为 444(只读,防覆盖):

anon_umask=0222

若希望上传文件权限为 000(谁都读不了,只上传不下载的"投递箱"):

anon_umask=0777

方法二:允许下载非所有人可读的文件

anon_world_readable_only=NO

方法三:改上传文件的属主

chown_uploads=YES
chown_username=nobody
chown_upload_mode=0644

3.1.6 SELinux 相关设置(重要)

CentOS 6/7 默认开启 SELinux,即使 Linux 权限完全正确,匿名上传/读取仍可能失败(表现为 553 Could not create file. 或看不到文件)。

查看 FTP 相关布尔值:

getsebool -a | grep ftp
# allow_ftpd_anon_write --> off
# allow_ftpd_full_access --> off
# ftp_home_dir --> off
# ftpd_anon_write --> off        # CentOS 7 命名
# ftpd_full_access --> off       # CentOS 7 命名

开启(-P 表示永久生效,重启不丢):

# 允许 FTP 匿名写
setsebool -P allow_ftpd_anon_write on      # CentOS 6
setsebool -P ftpd_anon_write on            # CentOS 7

# 允许 FTP 完全访问(读写整个可访问文件系统,最省事但最宽松)
setsebool -P allow_ftpd_full_access on     # CentOS 6
setsebool -P ftpd_full_access on           # CentOS 7

# 允许本地用户访问自己的家目录(本地用户模式必需)
setsebool -P ftp_home_dir on

还需保证目录的 SELinux 上下文正确:

ls -Zd /var/ftp/pub
# drwxrwxrwx. root root system_u:object_r:public_content_rw_t:s0  /var/ftp/pub

# 若上下文不对,设置为可读写的公共内容类型
semanage fcontext -a -t public_content_rw_t "/var/ftp/upload(/.*)?"
restorecon -Rv /var/ftp/upload

教学/实验环境也可以直接临时关闭 SELinux(生产不推荐):

setenforce 0                 # 临时关闭,重启失效
getenforce                   # 查看当前状态

# 永久关闭:编辑 /etc/selinux/config
# SELINUX=disabled

3.2 本地用户访问

3.2.1 本地用户登录

普通系统用户默认即允许登录 FTP,登录后进入自己的家目录,密码就是系统登录密码。

useradd zhangsan
echo '123456' | passwd --stdin zhangsan
local_enable=YES
write_enable=YES
local_umask=022

测试:

ftp 10.0.0.61
# Name: zhangsan
# Password: 123456
# 230 Login successful.
ftp> pwd            # 257 "/home/zhangsan"

3.2.2 禁止本地用户登录的三种方法

方法一:服务参数(一刀切)

local_enable=NO

这会禁止所有本地用户,无法针对特定用户控制。

方法二:修改用户 shell

/etc/passwd 最后一列指定用户 shell,不同 shell 的效果:

shell 登录系统 登录 FTP 收邮件
/bin/bash 可以 可以 可以
/sbin/nologin 不可以 可以 可以
/bin/false 不可以 不可以 可以
usermod -s /sbin/nologin ftpuser1     # 只能用 FTP,不能登录系统(推荐)
usermod -s /bin/false ftpuser2        # 连 FTP 也不能用

这是一个非常实用的技巧:给纯 FTP 账号设置 /sbin/nologin,既能传文件又不能 SSH 登录系统。

方法三:用户名单(黑白名单,见下节)

3.2.3 用户列表黑白名单

通过 user_list 文件和 userlist_deny 参数控制:

userlist_enable=YES
userlist_file=/etc/vsftpd/user_list
userlist_deny user_list 文件作用
YES(默认) 黑名单:文件中的用户禁止登录
NO 白名单:只有文件中的用户允许登录
vim /etc/vsftpd/user_list
# 一行一个用户名

启用白名单:

userlist_enable=YES
userlist_deny=NO

启用黑名单:

userlist_enable=YES
userlist_deny=YES

参数含义再强调一次:

  • userlist_enable:用户列表功能是否有效;
  • userlist_deny:列表是"允许"还是"拒绝"。

白名单陷阱:使用白名单时,如果还要允许匿名登录,必须把 ftp 和 anonymous 也写进 user_list,否则匿名用户(尤其是 lftp、yum)会登录失败。

3.2.4 允许 root 用户登录 FTP

PAM 默认会禁用 root 和 UID 小于 500(CentOS 7 是 1000)的系统用户,名单在 /etc/vsftpd/ftpusers。要让 root 登录:

  1. 注释(或删除)/etc/vsftpd/ftpusers 文件中的 root 那一行:

     vim /etc/vsftpd/ftpusers
     # root      ← 注释掉
  2. 处理 user_list:若启用了黑名单,要确保 root 不在黑名单中(默认 user_list 里有 root,需注释掉);若启用了白名单,要确保 root 在白名单中。

  3. 必要时修改 /etc/pam.d/vsftpd,把 pam_listfile 那行的 sense=deny 改为 sense=allow(或直接注释该行):

     auth required pam_listfile.so item=user sense=deny file=/etc/vsftpd/ftpusers onerr=succeed

生产环境不建议开放 root 登录 FTP,密码明文传输一旦被嗅探,整台服务器沦陷。

3.2.5 本地用户的下载和上传

本地用户默认允许下载和上传。上传需要同时满足:

  1. 系统目录对该用户有写权限;
  2. 配置中 write_enable=YES。

3.2.6 统一本地用户登录目录

让所有本地用户登录后统一进入 /ftpdata/:

local_root=/ftpdata

此参数不影响用户登录系统(SSH/终端)时进入自己的家目录。

mkdir /ftpdata
chmod 757 /ftpdata          # 其他人可读写执行
chmod o+t /ftpdata          # 加粘滞位:用户只能删除自己的文件
ls -ld /ftpdata
# drwxr-xrwt 2 root root 4096 ... /ftpdata

粘滞位(sticky bit,o+t)是实现"多用户共享上传目录,但互相不能删对方文件"的关键,与 /tmp 同理。

3.2.7 家目录 chroot 禁锢实操

对指定用户启用 chroot:

chroot_list_enable=YES
chroot_list_file=/etc/vsftpd/chroot_list

手动创建名单文件(默认不存在):

vim /etc/vsftpd/chroot_list
# 一行一个用户名,例如:
# zhangsan
# lisi
# CentOS 6
/etc/init.d/vsftpd reload
# CentOS 7
systemctl reload vsftpd

测试发现,被锁定的用户执行 cd .. 后仍停留在登录目录,pwd 永远显示 /,无法跳出。

对所有本地用户启用 chroot:

chroot_local_user=YES

匿名用户默认就是 chroot 环境。

3.2.8 解决 500 OOPS: vsftpd: refusing to run with writable root inside chroot

现象:在 CentOS 7(vsftpd 3.x)或 vsftpd 2.3.5 及以上版本中,开启 chroot_local_user=YES 后,本地用户登录报错:

500 OOPS: vsftpd: refusing to run with writable root inside chroot ()

原因:从 vsftpd 2.3.5 起,出于安全考虑,被 chroot 的根目录本身不允许有写权限(因为 chroot 根可写存在提权风险)。而用户家目录默认属主是自己且有 w 权限,因此触发拒绝。

解决方法一(推荐,vsftpd ≥ 3.0):在 vsftpd.conf 中显式允许可写的 chroot 根:

chroot_local_user=YES
allow_writeable_chroot=YES
systemctl restart vsftpd

解决方法二:去掉家目录的写权限,另建可写子目录用于上传(更安全,与匿名用户的思路一致):

chmod a-w /home/zhangsan            # 家目录去掉写权限,作为只读 chroot 根
mkdir /home/zhangsan/upload         # 建可写子目录
chown zhangsan:zhangsan /home/zhangsan/upload
chmod 755 /home/zhangsan/upload

解决方法三:不用家目录,改用 local_root 指向一个不可写的父目录:

chroot_local_user=YES
local_root=/ftpdata
chmod 555 /ftpdata                  # 根不可写
mkdir /ftpdata/upload && chmod 777 /ftpdata/upload

注意:CentOS 6 自带的 vsftpd 2.2.2 没有 allow_writeable_chroot 参数,写进配置文件会导致服务启动失败并报 unrecognised variable。CentOS 6 环境请使用方法二或方法三。

3.3 虚拟用户完整配置

虚拟用户在系统中不存在同名账号,全部映射到一个系统宿主账号上,安全性最高,是生产环境的首选方案。

3.3.1 第一步:建立虚拟用户文本库

mkdir -p /etc/vsftpd/vuser
vim /etc/vsftpd/vuser/vusers.txt

文件格式为奇数行用户名、偶数行密码,严格交替:

zhangsan
pass123
lisi
pass456
web01
web@2024

3.3.2 第二步:用 db_load 生成 db 数据库文件

# 确保安装了 db_load 工具
# CentOS 6: yum install db4-utils -y
# CentOS 7: yum install libdb-utils -y

cd /etc/vsftpd/vuser
db_load -T -t hash -f vusers.txt vusers.db

参数说明:

参数 说明
-T 允许应用程序(非 Berkeley DB 工具)使用该数据库
-t hash 指定数据库类型为 hash
-f vusers.txt 指定源文本文件

保护数据库文件权限(内含明文密码):

chmod 600 /etc/vsftpd/vuser/vusers.db
chmod 600 /etc/vsftpd/vuser/vusers.txt

每次修改 vusers.txt(增删用户或改密码)后,必须重新执行 db_load 才生效。

3.3.3 第三步:创建 PAM 认证文件

vim /etc/pam.d/vsftpd.vu

CentOS 6(32 位用 pam_userdb.so,64 位路径同名,模块由 PAM 自动查找):

auth       required     pam_userdb.so db=/etc/vsftpd/vuser/vusers
account    required     pam_userdb.so db=/etc/vsftpd/vuser/vusers

CentOS 7:

auth       required     pam_userdb.so db=/etc/vsftpd/vuser/vusers
account    required     pam_userdb.so db=/etc/vsftpd/vuser/vusers

关键点:db= 后面的路径不要写 .db 后缀,PAM 会自动补上。

CentOS 7 部分版本 pam_userdb.so 由 pam 包提供,若报找不到模块,检查 /usr/lib64/security/pam_userdb.so 是否存在。

3.3.4 第四步:创建系统宿主账号

所有虚拟用户都映射到这一个系统账号,由它的身份进行文件读写:

useradd -d /var/ftproot -s /sbin/nologin virtual
chmod 755 /var/ftproot            # 注意:作为 chroot 根不要给写权限
mkdir -p /var/ftproot/upload
chown virtual:virtual /var/ftproot/upload
chmod 755 /var/ftproot/upload

3.3.5 第五步:修改 vsftpd.conf

# 基础
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022

# 虚拟用户核心四项
guest_enable=YES                          # 启用虚拟用户模式
guest_username=virtual                    # 映射到的系统宿主账号
pam_service_name=vsftpd.vu                # 指向 /etc/pam.d/vsftpd.vu
user_config_dir=/etc/vsftpd/vusers_dir    # 每个虚拟用户的独立配置目录

# 让虚拟用户拥有本地用户级权限(否则只有匿名级权限,无法上传删除)
virtual_use_local_privs=YES

# 禁锢
chroot_local_user=YES
allow_writeable_chroot=YES                # 仅 vsftpd >= 3.0 / CentOS 7

# 目录
local_root=/var/ftproot

# 其他
listen=YES
listen_ipv6=NO
pasv_enable=YES
pasv_min_port=30001
pasv_max_port=30010
xferlog_enable=YES

virtual_use_local_privs=YES 时,虚拟用户的写权限由 write_enable 控制;若设为 NO,则虚拟用户按匿名用户规则处理,需要用 anon_upload_enable、anon_mkdir_write_enable、anon_other_write_enable 来放权。两种方式都可用,但混用容易搞错。

3.3.6 第六步:为不同虚拟用户设置独立权限与目录

mkdir -p /etc/vsftpd/vusers_dir

给 zhangsan 只读权限,目录 /var/ftproot/zhangsan:

mkdir -p /var/ftproot/zhangsan
chown virtual:virtual /var/ftproot/zhangsan
vim /etc/vsftpd/vusers_dir/zhangsan
local_root=/var/ftproot/zhangsan
write_enable=NO
download_enable=YES
local_max_rate=102400

给 lisi 完整读写权限,目录 /var/ftproot/lisi:

mkdir -p /var/ftproot/lisi
chown virtual:virtual /var/ftproot/lisi
chmod 755 /var/ftproot/lisi
vim /etc/vsftpd/vusers_dir/lisi
local_root=/var/ftproot/lisi
write_enable=YES
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_other_write_enable=YES
local_max_rate=512000

给 web01 独立站点目录,只允许上传不允许删除:

mkdir -p /var/www/html
chown virtual:virtual /var/www/html
vim /etc/vsftpd/vusers_dir/web01
local_root=/var/www/html
write_enable=YES
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_other_write_enable=NO

要点:user_config_dir 中的文件名必须与虚拟用户名完全一致(大小写敏感),文件内可写任何 vsftpd.conf 支持的参数,会覆盖主配置文件中的同名参数。

3.3.7 第七步:重启与测试验证

# CentOS 6
/etc/init.d/vsftpd restart
# CentOS 7
systemctl restart vsftpd

# SELinux 放行
setsebool -P ftpd_full_access on

测试 zhangsan(应该只能下载,不能上传):

ftp 10.0.0.61
# Name: zhangsan
# Password: pass123
# 230 Login successful.
ftp> pwd                    # 257 "/",说明已被 chroot
ftp> ls
ftp> get somefile           # 成功
ftp> put /etc/hosts         # 550 Permission denied.(write_enable=NO 生效)
ftp> bye

测试 lisi(应该可以上传、建目录、删除):

ftp 10.0.0.61
# Name: lisi
# Password: pass456
ftp> put /etc/hosts         # 226 Transfer complete.
ftp> mkdir testdir          # 257 created
ftp> delete hosts           # 250 Delete operation successful.
ftp> bye

验证系统中确实没有这些账号:

id zhangsan
# id: zhangsan: no such user      ← 说明是虚拟用户
grep zhangsan /etc/passwd
# (无输出)

验证上传文件的实际属主是宿主账号:

ls -l /var/ftproot/lisi/
# -rw-r--r-- 1 virtual virtual 158 ... hosts     ← 属主是 virtual

3.3.8 虚拟用户排错要点

现象 可能原因
530 Login incorrect. vusers.db 未生成或路径写错;pam_service_name 与 PAM 文件名不一致;vusers.txt 奇偶行错位;改密码后忘记重新 db_load
登录成功但无法上传 write_enable=NO;virtual_use_local_privs=NO 且未开 anon_upload_enable;目录属主不是 guest_username
500 OOPS: refusing to run with writable root inside chroot 加 allow_writeable_chroot=YES,或去掉 local_root 目录的写权限
个性化配置不生效 user_config_dir 目录不存在;文件名与用户名不一致;文件里有语法错误(等号旁有空格)
553 Could not create file SELinux 未放行,执行 setsebool -P ftpd_full_access on

四、客户端使用与运维

4.1 主动模式验证实验

在服务器上同时作为客户端连接自己:

ftp 10.0.0.61
# Connected to 10.0.0.61.
# 220 (vsFTPd 2.0.5)
Name (10.0.0.61:root): ftp        # 输入匿名用户 ftp
331 Please specify the password.
Password:                          # 密码任意或空
230 Login successful.

ftp> ?                             # 查看可用命令
ftp> help passive                  # 查看 passive 命令帮助
ftp> passive                       # 切换被动模式开关,此处关闭被动模式
Passive mode off.
ftp> ls                            # 触发数据连接

打开第二个终端观察连接:

netstat -nt | grep TIME_
# tcp 0 0 10.0.0.61:20  10.0.0.61:56227  TIME_WAIT

可以看到服务器使用 20 端口连接客户端大于 1024 的随机端口,这就是主动模式的数据连接特征。

被动模式下再观察一次(passive 切回 on 后执行 ls),会看到数据连接的服务器端端口落在 pasv_min_port–pasv_max_port 范围内。

4.2 被动模式端口范围验证

为便于配置防火墙,指定被动模式数据传输使用的端口范围:

pasv_enable=YES             # 默认开启,可不写
pasv_min_port=3000
pasv_max_port=3005

FTP 登录后执行 ls 等触发数据传输的命令,用 netstat -nt 观察,数据连接的服务器端端口一定落在 3000–3005 范围内。

netstat -nt | grep 10.0.0.61
# tcp 0 0 10.0.0.61:3002  10.0.0.185:41235  ESTABLISHED

端口范围要留够:每个并发数据传输占用一个端口,范围大小应 ≥ 预期并发传输数。

4.3 命令行 ftp 客户端全部常用命令

ftp 192.168.1.130            # 连接并登录
ftp -i 192.168.1.130         # 关闭交互确认(等价于登录后执行 prompt)
ftp -n 192.168.1.130         # 不自动登录,需手动 user
命令 作用 示例
open 在 ftp 交互环境内连接一台服务器 open 192.168.1.130
user 输入用户名与密码进行认证 user zhangsan
close 断开与当前服务器的连接但不退出客户端 close
pwd 查看远程当前目录 pwd
ls / dir 列出远程当前目录内容 ls、ls -l、ls upload
cd 进入远程目录,回上层为 cd .. cd upload
lcd 切换本地目录(不带参数则显示本地当前目录),回上层为 lcd .. lcd /tmp
get 下载单个文件 get bash、get bash /tmp/bash2、get bash bash3
mget 批量下载,支持通配符 mget *.txt
put 上传单个文件 put /etc/passwd、put /etc/passwd ./passwd2
mput 批量上传,支持通配符 mput *.log
binary / bin 切换为二进制传输类型 binary
ascii 切换为 ASCII 文本传输类型 ascii
passive 切换被动模式开关(开↔关),显示 Passive mode on/off passive
prompt 切换批量操作(mget/mput)的逐个确认提示,关掉后才能无人值守批量传 prompt
delete 删除远程单个文件 delete hosts
mdelete 批量删除远程文件 mdelete *.tmp
mkdir 在远程创建目录 mkdir testdir
rmdir 删除远程空目录 rmdir testdir
rename 重命名远程文件 rename a.txt b.txt
size 查看远程文件大小 size bigfile
hash 开启/关闭传输进度 # 号显示 hash
debug 开启/关闭调试信息,debug 1 开、debug 0 关 debug 1
verbose 切换详细输出模式 verbose
status 显示当前连接与各开关状态 status
! 在本地执行 shell 命令 !ls -l /tmp/bash*
? / help 列出所有可用命令 ?、help、help debug
bye / quit / exit 退出客户端(也可用 Ctrl+D) bye

常用 FTP 子命令小结:get(下载)、put(上传)、ls、cd、mget、mput、delete、rename 等。

4.4 lftp 客户端

lftp 功能远强于 ftp,支持目录镜像、断点续传、后台任务、书签。

# 匿名登录
lftp 10.0.0.61

# 指定用户登录(命令行给出用户名和密码)
lftp -u zhangsan,pass123 10.0.0.61
lftp -u zhangsan,pass123 ftp://10.0.0.61

# 直接执行命令后退出
lftp -u a,password -e "ls; quit" ftp://10.0.0.2

lftp 交互环境内常用命令:

命令 说明
ls / cd / lcd / pwd 与 ftp 同义
get / put 单文件下载/上传
mget / mput 批量下载/上传
mirror 远程目录 本地目录 递归镜像下载整个目录树
mirror -R 本地目录 远程目录 反向镜像,即递归上传
pget -n 5 文件 多线程分段下载
set ftp:passive-mode on/off 切换被动模式
bookmark add name 添加书签
quit / exit 退出

定时镜像备份示例(每天 18:00 把服务器 /ftp 全部内容下载到本地 /backup):

crontab -e
# 0 18 * * * lftp -u a,password -e "mirror /ftp /backup; quit" ftp://10.0.0.2

lftp 支持在命令行中直接指定用户名和密码,详见 man lftp。生产环境建议把密码写入 ~/.netrc 并 chmod 600,避免出现在进程列表中。

~/.netrc 示例:

machine 10.0.0.2
login a
password password

4.5 wget 访问 FTP

# 匿名下载
wget ftp://10.1.1.1/pub/50m

# 带用户名密码下载
wget ftp://u9:abc@10.1.1.1/50m
wget --ftp-user=u9 --ftp-password=abc ftp://10.1.1.1/50m

# 递归下载整个目录
wget -r ftp://10.1.1.1/pub/

# 限速下载(用于对比服务端限速效果)
wget --limit-rate=50k ftp://10.1.1.1/pub/50m

# 断点续传
wget -c ftp://10.1.1.1/pub/50m

wget 默认使用被动模式,可用 --no-passive-ftp 强制主动模式。

4.6 curl 访问 FTP

# 列出目录
curl ftp://10.0.0.61/pub/

# 下载(-O 保留原文件名)
curl -O ftp://10.0.0.61/pub/fstab
curl -u zhangsan:pass123 -O ftp://10.0.0.61/data.tar.gz

# 上传
curl -T /etc/hosts -u lisi:pass456 ftp://10.0.0.61/upload/

# 强制主动模式(默认被动)
curl -P - ftp://10.0.0.61/pub/

# FTPS(显式 TLS)
curl --ssl-reqd -u zhangsan:pass123 ftp://10.0.0.61/pub/fstab -O
curl -k --ssl-reqd -u zhangsan:pass123 ftp://10.0.0.61/pub/    # 忽略自签名证书校验

4.7 Windows 下访问

FileZilla(图形客户端,推荐):

  1. 打开"站点管理器"→"新站点"。
  2. 协议选择:
    • 明文 FTP:FTP - 文件传输协议,加密选"只使用明文 FTP"。
    • FTPS:协议选 FTP,加密选"要求显式的 FTP over TLS"。
    • SFTP:协议选 SFTP - SSH File Transfer Protocol(端口 22)。
  3. 主机填服务器 IP,端口 21(SFTP 为 22)。
  4. 登录类型:匿名 / 正常(填用户名密码)。
  5. 在"传输设置"中选择"被动"(推荐)或"主动"。

Windows 资源管理器 / 浏览器:

ftp://192.168.1.130/            浏览器地址栏,仅支持匿名下载(新版 Chrome/Edge 已移除 FTP 支持)
ftp://zhangsan@192.168.1.130/   会弹出密码框

在"此电脑"地址栏输入 ftp://192.168.1.130/ 可以像本地文件夹一样浏览、拖拽上传下载(需在 IE 兼容设置中启用"被动 FTP")。

Windows 自带命令行 ftp:

C:\> ftp 192.168.1.130
ftp> binary
ftp> get file.zip
ftp> quit

注意:Windows 自带的 ftp.exe 只支持主动模式,在 NAT 环境下常常无法列目录,建议改用 FileZilla 或 PowerShell。

4.8 用 FTP 搭建 yum 源

服务端:把光盘内容放到 FTP 目录下。

mount /dev/cdrom /mnt
mkdir -p /var/ftp/yum
cp -a /mnt/* /var/ftp/yum/
# 或直接把光盘挂到 FTP 目录
# mount /dev/cdrom /var/ftp/yum
createrepo /var/ftp/yum          # 若无 repodata 需自己生成

客户端:修改 yum 仓库配置,只需把 baseurl 一行改为 FTP 格式。

vim /etc/yum.repos.d/ftp.repo
[ftp-base]
name=FTP Base Repo
baseurl=ftp://192.168.0.183/yum
enabled=1
gpgcheck=0
yum clean all
yum repolist
yum install gimp -y

重要陷阱:yum 默认使用被动模式。若服务端配置了 pasv_enable=NO(只允许主动模式),yum 将无法使用 FTP 源。同理,若启用了白名单(userlist_deny=NO),必须把 ftp 和 anonymous 都加入 user_list,否则 yum 与 lftp 匿名登录都会失败。

4.9 目录消息与欢迎横幅

4.9.1 目录消息

dirmessage_enable=YES
message_file=.message

在各目录下创建 .message 文件:

vim /home/a/.message
vim /var/ftp/pub/.message
====================================
 欢迎进入公共下载区,请遵守使用规范
====================================

任何能登录或 cd 到该目录的用户都会看到该消息。

4.9.2 全局登录横幅

方法一:直接配置字符串

ftpd_banner=Welcome to my ftp server !!

方法二:从文件读取(支持多行)

banner_file=/etc/vsftpd/banner
vim /etc/vsftpd/banner

可写入多行字符画等内容,连接时作为 220 响应显示。

类似的消息提示机制在多数服务中都有:系统登录 /etc/motd,sshd 登录前 Banner /etc/ssh_banner。安全建议:不要在 banner 中暴露 vsftpd 版本号等信息。

4.10 传输日志分析

4.10.1 xferlog 格式

xferlog_enable=YES
xferlog_std_format=YES
xferlog_file=/var/log/xferlog
# CentOS 6
/etc/init.d/vsftpd reload
# CentOS 7
systemctl reload vsftpd

日志示例:

Tue May  3 06:24:57 2011 1 10.0.0.185 660 /etc/fstab b _ o r a ftp 0 * c
Tue May  3 06:36:24 2011 1 10.0.0.132 660 /etc/fstab b _ i r a ftp 0 * i

字段解析(以第一条为例,共 14 个字段):

位置 示例值 含义
1 Tue May 3 06:24:57 2011 访问时间
2 1 传输耗时(秒)
3 10.0.0.185 远程主机名/IP
4 660 传输字节数
5 /etc/fstab 操作文件名
6 b 传输类型:a=ASCII,b=binary
7 _ 特殊处理标志:_=无,C=压缩,U=解压,T=tar
8 o 方向:i=上传(incoming),o=下载(outgoing),d=删除
9 r 访问模式:a=匿名,g=guest(虚拟用户),r=real(本地真实用户)
10 a 用户名
11 ftp 服务名
12 0 认证方式:0=none,1=RFC931
13 * 认证用户 ID(不可用时为 *)
14 c 完成状态:c=完整(complete),i=不完整(incomplete)

对照第二条日志:方向为 i(上传)、状态为 i(不完整),说明这是一次中断的上传。

日志分析常用命令:

# 统计下载量最大的文件
awk '$8=="o"{print $9}' /var/log/xferlog | sort | uniq -c | sort -rn | head

# 统计各客户端 IP 的传输次数
awk '{print $7}' /var/log/xferlog | sort | uniq -c | sort -rn

# 找出所有传输不完整的记录
awk '$NF=="i"' /var/log/xferlog

# 统计总传输字节数
awk '{s+=$8} END{print s/1024/1024 " MB"}' /var/log/xferlog

4.10.2 vsftpd 自有日志

dual_log_enable=YES        # xferlog 和 vsftpd.log 都产生
xferlog_std_format=NO      # 设为 NO 时只产生 vsftpd.log
vsftpd_log_file=/var/log/vsftpd.log
log_ftp_protocol=YES       # 额外记录所有协议交互,排错用
# CentOS 6
/etc/init.d/vsftpd reload
# CentOS 7
systemctl reload vsftpd

ls -l /var/log/vsftpd.log
tail -f /var/log/vsftpd.log

vsftpd.log 会清晰记录登录成功/失败、上传下载成功/失败等信息,示例:

Mon Aug 18 10:22:31 2025 [pid 2] CONNECT: Client "10.0.0.185"
Mon Aug 18 10:22:35 2025 [pid 1] [zhangsan] OK LOGIN: Client "10.0.0.185"
Mon Aug 18 10:22:40 2025 [pid 3] [zhangsan] OK UPLOAD: Client "10.0.0.185", "/upload/hosts", 158 bytes, 12.34Kbyte/sec
Mon Aug 18 10:23:02 2025 [pid 3] [lisi] FAIL LOGIN: Client "10.0.0.185"

排查登录失败:

grep 'FAIL LOGIN' /var/log/vsftpd.log
grep 'OK LOGIN' /var/log/vsftpd.log | awk '{print $NF}' | sort | uniq -c

4.10.3 日志轮转

轮转配置在 /etc/logrotate.d/vsftpd:

vim /etc/logrotate.d/vsftpd
/var/log/vsftpd.log {
    nocompress
    missingok
    monthly
    rotate 2
}

各指令含义:nocompress 不压缩、missingok 文件不存在也不报错、monthly 每月轮转、rotate 2 最多保留 2 个历史副本。

logrotate -d /etc/logrotate.d/vsftpd     # 调试模式,只演示不执行
logrotate -f /etc/logrotate.d/vsftpd     # 强制立即轮转

版本差异:RHEL 5 下这两种日志使用的是格林威治时间(比北京时间晚 8 小时);RHEL 6 及之后已修正为本地时间。

4.11 限速与并发限制的验证方法

4.11.1 下载限速配置

anon_max_rate=100000      # 匿名用户最大传输率(字节/秒),约 100KB/s
local_max_rate=100000     # 本地用户最大传输率(字节/秒),约 100KB/s

# 另一组常用取值
anon_max_rate=102400      # 精确 100KB/s
local_max_rate=204800     # 精确 200KB/s
# CentOS 6
/etc/init.d/vsftpd reload
# CentOS 7
systemctl reload vsftpd

vsftpd 的限速是近似值,实际速率会在设定值的 80%~120% 之间浮动,属正常现象。限速是针对单个连接的,不是全局带宽。

4.11.2 限速验证

方法一:用 wget 直接看速度

# 准备一个大文件
dd if=/dev/zero of=/var/ftp/pub/50m bs=1M count=50

# 匿名下载测速
wget ftp://10.1.1.1/pub/50m

# 本地用户下载测速
wget ftp://u9:abc@10.1.1.1/50m

wget 输出的末尾会给出平均速率,与配置值对比即可。

方法二:用 watch 观察本地文件增长

使用本地用户登录并下载一个较大文件,同时在另一终端观察:

watch ls -lh              # 默认每 2 秒刷新一次
watch -n 1 ls -lh /tmp    # 每 1 秒刷新

注意:watch 不支持 ll 等 shell 别名,必须写完整命令 ls -lh。

方法三:用 ftp 客户端的 hash 观察

ftp> hash
ftp> get 50m

4.11.3 连接数限制

max_clients=300           # 服务器允许的总并发连接数
max_per_ip=3              # 单个客户端 IP 的最大连接数

超过单 IP 连接数限制时会报错:

421 There are too many connections from your internet address.

超过总连接数限制时会报错:

421 There are too many connected users, please try later.

这两个参数只在 standalone 模式(listen=YES)下有效,由 xinetd 托管时无效,需改用 xinetd 的 instances 与 per_source 参数。

4.11.4 并发限制验证

# 同时开 4 个 ftp 会话(max_per_ip=3),第 4 个应被拒绝
for i in 1 2 3 4; do
    (echo -e "user zhangsan pass123\nls\nsleep 30\nbye" | ftp -n 10.0.0.61 &) 
done

# 服务器端观察当前连接数
netstat -nt | grep ':21 ' | grep ESTABLISHED | wc -l
ss -nt state established '( dport = :21 or sport = :21 )'

# 观察 vsftpd 进程数
ps -ef | grep vsftpd | grep -v grep | wc -l

4.12 查看服务器上与客户端交互的 FTP 进程

排查权限或进程身份问题时,可按以下步骤定位服务器上对应的 FTP 进程:

# 1. 在客户端找出自己的 ftp 客户端进程号
ps -C ftp
# 示例输出进程号:10292

# 2. 根据进程号找出客户端使用的端口号
netstat -tpn | grep 10292
# 示例找到:3.3.3.7:34330

# 3. 在服务器上根据远程端口号和 IP 地址找出对应的服务端进程
netstat -tpn | grep 3.3.3.7:34330
# 示例找到进程号:8161

# 4. 查看该进程的有效用户 ID(euid),由此确认进程以谁的身份运行
ps -o pid,euid -p 8161
# 示例输出:nobody

这一手法可以验证:匿名会话的 euid 是 ftp/nobody;虚拟用户会话的 euid 是 guest_username 指定的账号;本地用户会话的 euid 是该用户本身。

4.13 命令速查表

操作 命令
安装 vsftpd sudo yum install -y vsftpd
安装客户端 yum install ftp lftp -y
启动服务(CentOS 6) /etc/init.d/vsftpd start
启动服务(CentOS 7) systemctl start vsftpd
重载配置(CentOS 6) /etc/init.d/vsftpd reload
重载配置(CentOS 7) systemctl reload vsftpd
开机自启(CentOS 6) chkconfig vsftpd on
开机自启(CentOS 7) systemctl enable vsftpd
查看有效配置 grep -v ^# /etc/vsftpd/vsftpd.conf \| grep -v ^$
查看监听端口 netstat -ntl \| grep :21 / lsof -i:21 / ss -ntl
查看 vsftpd 是否支持 TCP Wrapper ldd /usr/sbin/vsftpd \| grep wrap
查看 vsftpd 配置 man 文档 man 5 vsftpd.conf
生成虚拟用户库 db_load -T -t hash -f vusers.txt vusers.db
测试匿名下载 wget ftp://IP/pub/file
测试用户认证下载 wget ftp://user:pass@IP/file
查找 FTP 进程 ps -C ftp
查看进程网络连接 netstat -tpn \| grep PID
查看进程有效用户 ps -o pid,euid -p PID
查看 SELinux FTP 布尔值 getsebool -a \| grep ftp

五、访问控制与防火墙

5.1 TCP Wrapper 访问控制

TCP Wrappers 是一个第三方访问控制工具,可对支持它的 TCP 服务进行 IP 级过滤。

5.1.1 判断服务是否支持

ldd /usr/sbin/sshd | grep wrap
# libwrap.so.0 => /lib/libwrap.so.0 (0x00d39000)

ldd /usr/sbin/vsftpd | grep wrap
# libwrap.so.0 => /lib64/libwrap.so.0 (0x00007fe2acf7c000)

链接了 libwrap.so 即表示支持(有输出即支持)。并非所有服务都支持,很多软件编译时需要加 --enable-libwrap。vsftpd 默认支持,并需在配置中开启:

tcp_wrappers=YES

版本差异:CentOS 7.x 后期版本及 CentOS 8 起,tcp_wrappers 已被弃用/移除(systemd 不再链接 libwrap),请改用 firewalld 或 iptables 做 IP 级控制。CentOS 6 与 CentOS 7 早期版本仍可正常使用。

5.1.2 配置文件与匹配顺序

文件 作用
/etc/hosts.allow 允许规则
/etc/hosts.deny 拒绝规则

系统按以下顺序查找两个文件,找到第一个匹配的规则后立即停止:

  1. 若在 /etc/hosts.allow 中匹配,则允许。
  2. 若在 /etc/hosts.deny 中匹配,则拒绝。
  3. 若两个文件均无匹配,则允许(默认放行)。

若文件不存在,视为空文件,即默认允许访问。

hosts.allow 优先级高于 hosts.deny,这是最常考也最常写错的一点。

5.1.3 配置语法

服务进程名: 客户端地址列表 [: 动作]
vsftpd: 10.1.1.15,10.1.1.96,10.1.1.0/255.255.255.0
  • 左侧 vsftpd:服务的进程名(不是端口名,也不是服务单元名)。
  • 右侧:客户端地址,可指定单个 IP、多个 IP(逗号或空格分隔)或网段。

参考 man 文档:

man 5 hosts_options
man 5 hosts_access

5.1.4 规则示例(以 sshd 为例,vsftpd 同理只需换服务名)

# /etc/hosts.deny

# 拒绝所有
sshd: all

# 只允许 10.0.0.104
sshd: all EXCEPT 10.0.0.104

# 允许多个 IP
sshd: all EXCEPT 10.0.0.104 10.0.0.105

# 拒绝 10.0.0.0/24 网段,除了 10.0.0.104
sshd: 10.0.0.0/255.255.255.0 EXCEPT 10.0.0.104

# 拒绝并记录日志
sshd: all : spawn echo "`date`" %c %d >> /var/log/tcpwrapperdeny.log

# 拒绝并向所有在线终端广播
sshd: all : spawn wall %c "login denied"

sshd: all EXCEPT 10.0.0.61 : spawn wall %c "login denied"

注意:子网掩码不支持 /24 简写,必须写完整掩码 255.255.255.0。%c 表示客户端信息(IP/主机名/用户),%d 表示服务名(daemon),%h 表示客户端主机名,%a 表示客户端地址。

5.1.5 vsftpd 使用 TCP Wrapper 的完整示例

只需将规则中的服务名改为 vsftpd:

# /etc/hosts.deny —— 拒绝所有 FTP 访问
vsftpd: all

示例一:只允许某网段,排除某 IP,并记录被拒绝的访问

/etc/hosts.allow:

vsftpd: 10.0.0.0/255.255.255.0 EXCEPT 10.0.0.10 : spawn echo "`date`" %c >> /var/log/ftp_denyip.log

示例二:allow 白名单 + deny 兜底并记录日志(更清晰的写法)

/etc/hosts.allow:

vsftpd: 10.1.1.0/255.255.255.0 EXCEPT 10.1.1.3

/etc/hosts.deny:

vsftpd: * :spawn echo "`date`" %c >> /var/log/ftp_deny.log

这样:10.1.1.0/24 网段(除 10.1.1.3)在 allow 中匹配被放行;其余所有来源在 deny 中匹配被拒绝并写入日志。

5.2 firewalld 放行(CentOS 7)

5.2.1 放行 FTP 服务(自动处理主动模式与 conntrack)

# 查看当前区域与已放行服务
firewall-cmd --get-default-zone
firewall-cmd --list-all

# 放行 ftp 服务(等价于放行 21 端口并加载 nf_conntrack_ftp 连接跟踪)
firewall-cmd --permanent --add-service=ftp
firewall-cmd --reload
firewall-cmd --list-services

5.2.2 放行被动模式端口范围(完整规则)

服务端配置:

pasv_enable=YES
pasv_min_port=30001
pasv_max_port=30010

防火墙规则:

# 放行控制端口 21
firewall-cmd --permanent --add-port=21/tcp

# 放行主动模式数据端口 20(如启用 connect_from_port_20)
firewall-cmd --permanent --add-port=20/tcp

# 放行被动模式端口范围
firewall-cmd --permanent --add-port=30001-30010/tcp

# 生效并验证
firewall-cmd --reload
firewall-cmd --list-ports
# 20/tcp 21/tcp 30001-30010/tcp

5.2.3 只允许特定来源访问 FTP(富规则)

# 只允许 10.0.0.0/24 访问 ftp 服务
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" service name="ftp" accept'

# 拒绝某个 IP 并记录日志
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.10" service name="ftp" log prefix="FTP-DENY" level="warning" drop'

firewall-cmd --reload
firewall-cmd --list-rich-rules

5.2.4 加载 FTP 连接跟踪模块

主动模式(以及部分被动模式场景)需要内核连接跟踪帮助模块,才能自动放行动态数据连接:

# 查看是否已加载
lsmod | grep nf_conntrack_ftp

# 手动加载
modprobe nf_conntrack_ftp

# 开机自动加载(CentOS 7)
echo 'nf_conntrack_ftp' > /etc/modules-load.d/ftp.conf

# CentOS 7 还需允许 helper 自动分配(部分版本默认关闭)
echo 'options nf_conntrack_helper=1' > /etc/modprobe.d/conntrack.conf
# 或临时开启
echo 1 > /proc/sys/net/netfilter/nf_conntrack_helper

5.3 iptables 放行(CentOS 6 / 手工 iptables)

5.3.1 通用规则形式

# 允许指定流量
iptables -A INPUT [OPTIONS]... -j ACCEPT

# 拒绝指定流量
iptables -A INPUT [OPTIONS]... -j REJECT

# 设置 INPUT 链默认策略为丢弃
iptables -P INPUT DROP

5.3.2 FTP 完整放行规则(被动模式 + 主动模式)

# 0. 加载 FTP 连接跟踪模块
modprobe ip_conntrack_ftp        # CentOS 6
modprobe nf_conntrack_ftp        # CentOS 7 / 较新内核

# 1. 放行已建立和相关的连接(连接跟踪,最关键的一条)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 2. 放行控制端口 21
iptables -A INPUT -p tcp --dport 21 -m state --state NEW -j ACCEPT

# 3. 放行主动模式数据端口 20
iptables -A INPUT -p tcp --dport 20 -m state --state NEW -j ACCEPT

# 4. 放行被动模式端口范围(与 pasv_min_port/pasv_max_port 一致)
iptables -A INPUT -p tcp --dport 30001:30010 -m state --state NEW -j ACCEPT

# 5. 只允许特定网段访问 FTP
iptables -A INPUT -p tcp --dport 21 -s 10.0.0.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 21 -s 10.0.0.10 -j REJECT

# 6. 默认策略
iptables -P INPUT DROP

# 7. 查看与保存
iptables -L -n --line-numbers
service iptables save                    # CentOS 6
# CentOS 7 需装 iptables-services:
# systemctl restart iptables && service iptables save

输出方向(若 OUTPUT 链默认 DROP,主动模式必需):

iptables -A OUTPUT -p tcp --sport 20 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

注意:FTP 被动模式需要放行命令端口 21 以及 pasv_min_port 至 pasv_max_port 范围内的数据端口。只放行 21 端口而忘记数据端口,是 FTP 部署最常见的故障——表现为能登录但 ls 卡住直到超时。

5.4 由 xinetd 托管(CentOS 6 及之前)

vsftpd 默认以 standalone 方式运行(listen=YES)。也可交由 xinetd 超级守护进程托管,好处是可以利用 xinetd 的访问时间控制、并发控制等特性。

版本差异:xinetd 是 CentOS 6 及之前的机制。CentOS 7 使用 systemd socket 激活(vsftpd.socket)代替,且 CentOS 7 默认不安装 xinetd。

5.4.1 配置 xinetd 服务

yum install xinetd -y
vim /etc/xinetd.d/vsftpd
service ftp
{
        socket_type     = stream
        protocol        = tcp
        wait            = no
        user            = root
        server          = /usr/sbin/vsftpd
        disable         = no
}

5.4.2 切换运行方式

/etc/init.d/vsftpd stop
/etc/init.d/xinetd restart

验证 21 端口由 xinetd 监听:

netstat -ntlup | grep :21
# tcp 0 0 0.0.0.0:21  0.0.0.0:*  LISTEN  3486/xinetd

此时直接 FTP 登录会失败:

421 Service not available, remote server has closed connection

需要将 vsftpd 配置中的 listen 改为 NO(因为 vsftpd 不能自己也去监听 21):

listen=NO
/etc/init.d/xinetd restart

再次 FTP 登录即可正常。

5.4.3 xinetd 常用扩展参数

service ftp
{
    disable      = no
    socket_type  = stream
    protocol     = tcp
    wait         = no
    user         = root
    server       = /usr/sbin/vsftpd
    access_times = 9:30-20:30        # 只在每天 9:30-20:30 允许访问
    only_from    = 10.0.0.0/24       # 只允许该网段
    no_access    = 10.0.0.10         # 排除该 IP
    instances    = 300               # 最大并发实例数(对应 max_clients)
    per_source   = 3                 # 单 IP 最大并发(对应 max_per_ip)
    log_on_success = HOST PID DURATION
    log_on_failure = HOST
}

access_times 是实现"只能在某时段访问 FTP"这一需求的唯一简洁办法,vsftpd 自身没有时间控制参数。

5.5 FTPS 加密配置

FTPS 使用 SSL/TLS 加密 FTP 的控制连接与数据连接,解决明文传输密码的问题。

5.5.1 生成自签名证书

方法一:openssl req 一步生成(推荐)

mkdir -p /etc/vsftpd/ssl
cd /etc/vsftpd/ssl

openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
    -keyout /etc/vsftpd/ssl/vsftpd.key \
    -out /etc/vsftpd/ssl/vsftpd.crt \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=IT/CN=ftp.example.com"

chmod 600 /etc/vsftpd/ssl/vsftpd.key
chmod 644 /etc/vsftpd/ssl/vsftpd.crt

参数说明:

参数 说明
-x509 生成自签名证书而不是证书请求
-nodes 私钥不加密(否则每次启动服务要输密码)
-days 3650 有效期 10 年
-newkey rsa:2048 同时生成 2048 位 RSA 私钥
-keyout / -out 私钥与证书的输出路径
-subj 非交互式指定证书主题信息

方法二:证书与私钥合并到一个 pem 文件

cat /etc/vsftpd/ssl/vsftpd.key /etc/vsftpd/ssl/vsftpd.crt > /etc/vsftpd/ssl/vsftpd.pem
chmod 600 /etc/vsftpd/ssl/vsftpd.pem

查看证书信息:

openssl x509 -in /etc/vsftpd/ssl/vsftpd.crt -noout -text
openssl x509 -in /etc/vsftpd/ssl/vsftpd.crt -noout -dates

5.5.2 ssl_enable 系列参数完整配置

# ===== SSL/TLS 总开关 =====
ssl_enable=YES                              # 启用 SSL/TLS 支持

# ===== 证书与密钥 =====
rsa_cert_file=/etc/vsftpd/ssl/vsftpd.crt    # 证书文件
rsa_private_key_file=/etc/vsftpd/ssl/vsftpd.key   # 私钥文件
# 若使用合并的 pem,只写一行即可:
# rsa_cert_file=/etc/vsftpd/ssl/vsftpd.pem

# ===== 强制加密(生产建议开启)=====
force_local_logins_ssl=YES                  # 强制本地用户登录使用 SSL 加密密码
force_local_data_ssl=YES                    # 强制本地用户数据传输使用 SSL
force_anon_logins_ssl=NO                    # 匿名登录是否强制 SSL(匿名下载一般不强制)
force_anon_data_ssl=NO                      # 匿名数据传输是否强制 SSL

# ===== 协议版本(禁用不安全的旧协议)=====
ssl_tlsv1=YES                               # 启用 TLSv1
ssl_sslv2=NO                                # 禁用 SSLv2(不安全)
ssl_sslv3=NO                                # 禁用 SSLv3(POODLE 漏洞)

# ===== 加密套件与其他 =====
ssl_ciphers=HIGH                            # 只使用高强度加密套件
require_ssl_reuse=NO                        # 不要求 SSL 会话复用(很多客户端不支持,开启会导致连接失败)
allow_anon_ssl=NO                           # 是否允许匿名用户使用 SSL
debug_ssl=YES                               # SSL 调试日志(排错时临时打开)

# ===== 隐式 FTPS(可选,端口 990)=====
# implicit_ssl=YES
# listen_port=990

重启服务:

# CentOS 6
/etc/init.d/vsftpd restart
# CentOS 7
systemctl restart vsftpd

防火墙放行(FTPS 仍需放行数据端口范围):

firewall-cmd --permanent --add-port=21/tcp
firewall-cmd --permanent --add-port=30001-30010/tcp
firewall-cmd --permanent --add-port=990/tcp        # 仅隐式 FTPS 需要
firewall-cmd --reload

5.5.3 FTPS 客户端测试

# lftp(显式 TLS)
lftp -u zhangsan,pass123 10.0.0.61
lftp> set ssl:verify-certificate no      # 自签名证书需关闭校验
lftp> set ftp:ssl-force true
lftp> set ftp:ssl-protect-data true
lftp> ls

# 一行命令形式
lftp -e "set ssl:verify-certificate no; set ftp:ssl-force true; ls; quit" -u zhangsan,pass123 10.0.0.61

# curl
curl -k --ssl-reqd -u zhangsan:pass123 ftp://10.0.0.61/

# openssl 直接握手测试
openssl s_client -connect 10.0.0.61:21 -starttls ftp

FileZilla:新建站点 → 协议 FTP → 加密选"要求显式的 FTP over TLS" → 首次连接会提示证书不受信任,勾选"始终信任该证书"即可。

注意:开启 ssl_enable=YES 且 force_local_logins_ssl=YES 后,普通 ftp 命令行客户端无法登录(它不支持 TLS),会报 530 Non-anonymous sessions must use encryption.。必须改用 lftp、curl 或 FileZilla。若要兼容老客户端,把 force_local_logins_ssl 设为 NO(但这就失去了强制加密的意义)。


六、排错清单

6.1 排错通用流程

# 1. 服务是否在跑
systemctl status vsftpd              # CentOS 7
/etc/init.d/vsftpd status            # CentOS 6

# 2. 端口是否监听
ss -ntlp | grep :21

# 3. 配置文件语法是否有错(启动失败时看这里)
vsftpd /etc/vsftpd/vsftpd.conf       # 前台运行,语法错误会直接打印
journalctl -xe -u vsftpd             # CentOS 7 查看启动失败原因

# 4. 本机是否能连通(排除网络/防火墙因素)
ftp 127.0.0.1

# 5. 打开协议级日志
# vsftpd.conf: xferlog_std_format=NO + log_ftp_protocol=YES
tail -f /var/log/vsftpd.log

# 6. 检查防火墙与 SELinux
firewall-cmd --list-all
getenforce
tail -f /var/log/audit/audit.log | grep -i denied

6.2 连接超时 / 能登录但 ls 卡住(被动模式端口未放行)

现象:

ftp> ls
227 Entering Passive Mode (10,0,0,61,117,52).
(长时间无响应,最终提示 425 Failed to establish connection 或超时)

原因与解决:

原因 排查与解决
被动端口范围未在防火墙放行 配置 pasv_min_port/pasv_max_port,并 firewall-cmd --add-port=30001-30010/tcp --permanent && firewall-cmd --reload
未加载 FTP 连接跟踪模块 modprobe nf_conntrack_ftp,并设置 nf_conntrack_helper=1
服务器在 NAT 后,227 回的是内网地址 配置 pasv_address=公网IP 与 pasv_addr_resolve=NO
客户端在 NAT 后却用主动模式 客户端执行 passive 切换为被动模式,或 wget 去掉 --no-passive-ftp
云主机安全组未放行 除系统防火墙外,还需在云平台安全组放行 21 与被动端口范围
Windows 自带 ftp.exe 只支持主动模式 改用 FileZilla / lftp

快速验证被动端口是否通:

# 客户端
telnet 10.0.0.61 21
nc -zv 10.0.0.61 30001-30010

6.3 530 登录失败

530 Login incorrect.
530 Permission denied.
530 Non-anonymous sessions must use encryption.

排查顺序:

检查项 命令/做法
密码是否正确 echo 'newpass' \| passwd --stdin username 重设
用户是否在 /etc/vsftpd/ftpusers 中 grep username /etc/vsftpd/ftpusers,在则注释掉
黑名单命中 userlist_deny=YES 时,检查 user_list 里是否有该用户
白名单未包含 userlist_deny=NO 时,必须把该用户(含 ftp、anonymous)加进 user_list
用户 shell 是 /bin/false usermod -s /sbin/nologin username
local_enable=NO 改为 YES
匿名登录被禁 anonymous_enable=YES
PAM 配置错误 检查 pam_service_name 与 /etc/pam.d/ 下文件名是否一致
虚拟用户库问题 重新 db_load;检查 db= 路径不带 .db 后缀
账号被锁定或密码过期 passwd -S username、chage -l username
SELinux 阻止读家目录 setsebool -P ftp_home_dir on
强制 SSL 而客户端不支持 报 must use encryption,改用 lftp/FileZilla 或关 force_local_logins_ssl

查看日志确认:

grep 'FAIL LOGIN' /var/log/vsftpd.log
grep vsftpd /var/log/secure

6.4 553 无法上传(权限与 SELinux)

553 Could not create file.
550 Permission denied.

排查清单(按顺序逐项检查):

# 1. vsftpd 写开关是否打开
grep -E 'write_enable|anon_upload_enable|anon_mkdir_write_enable' /etc/vsftpd/vsftpd.conf
# 必须有 write_enable=YES;匿名上传还需 anon_upload_enable=YES

# 2. 目标目录的 Linux 权限与属主
ls -ld /var/ftp/pub
# 匿名上传:目录需对 ftp 用户可写(属主为 ftp,或 777)
chown ftp:ftp /var/ftp/upload
chmod 755 /var/ftp/upload

# 3. 虚拟用户:目录属主必须是 guest_username 指定的账号
ls -ld /var/ftproot/lisi
chown virtual:virtual /var/ftproot/lisi

# 4. SELinux 布尔值
getsebool -a | grep ftp
setsebool -P ftpd_full_access on        # CentOS 7
setsebool -P allow_ftpd_full_access on  # CentOS 6
setsebool -P ftpd_anon_write on         # 匿名写

# 5. SELinux 文件上下文
ls -Zd /var/ftp/upload
semanage fcontext -a -t public_content_rw_t "/var/ftp/upload(/.*)?"
restorecon -Rv /var/ftp/upload

# 6. 磁盘空间与配额
df -h
quota -u username

# 7. 文件系统是否只读挂载
mount | grep ' / '

如果开了 SELinux 却查不出原因,直接看审计日志并让 audit2why 解释:

tail -20 /var/log/audit/audit.log | audit2why
ausearch -m avc -ts recent

6.5 chroot 报错

500 OOPS: vsftpd: refusing to run with writable root inside chroot ()
环境 解决方案
CentOS 7 / vsftpd ≥ 3.0 加 allow_writeable_chroot=YES
CentOS 6 / vsftpd 2.2.x 不支持该参数(写了会导致启动失败)。改为去掉 chroot 根目录的写权限:chmod a-w /home/user,另建可写子目录
任意版本 用 local_root 指向一个 555 权限的目录,在其下建 777 子目录
500 OOPS: vsftpd: refusing to run with writable anonymous root

匿名根目录(/var/ftp 或 anon_root)不能有写权限:

chmod 755 /var/ftp
mkdir /var/ftp/upload && chown ftp:ftp /var/ftp/upload
500 OOPS: unrecognised variable in config file: allow_writeable_chroot

当前 vsftpd 版本不支持该参数(CentOS 6),删掉这一行。

500 OOPS: could not bind listening IPv4 socket

listen 与 listen_ipv6 同时为 YES,或 21 端口已被其他进程占用:

ss -ntlp | grep :21
# 二者只能开一个

6.6 中文文件名乱码与 UTF-8

现象:ls 显示中文文件名为 ???? 或乱码,或下载后文件名变乱码。

原因:服务端文件系统编码与客户端会话编码不一致(Linux 通常 UTF-8,老 Windows 客户端为 GBK)。

解决方法:

  1. 服务端 vsftpd 配置中启用 UTF-8 文件系统声明:

     utf8_filesystem=YES
  2. 确认服务端系统 locale 为 UTF-8:

     # 查看
     locale
     # CentOS 7 设置
     localectl set-locale LANG=zh_CN.UTF-8
     # CentOS 6:编辑 /etc/sysconfig/i18n
     # LANG="zh_CN.UTF-8"
  3. 确保 vsftpd 服务进程本身运行在 UTF-8 环境(CentOS 7 systemd 不继承登录 locale):

     systemctl edit vsftpd
     [Service]
     Environment=LANG=zh_CN.UTF-8
     Environment=LC_ALL=zh_CN.UTF-8
     systemctl daemon-reload
     systemctl restart vsftpd
  4. 客户端侧设置(最常见的解决点):

    • FileZilla:站点管理器 → "字符集"标签 → 选"强制 UTF-8";若服务端是 GBK 则选"使用自定义字符集" → 填 GBK。

    • lftp:

      lftp -u user,pass 10.0.0.61
      lftp> set ftp:charset GBK          # 服务端为 GBK 时
      lftp> set file:charset UTF-8       # 本地字符集
      lftp> ls
    • Windows 命令行 ftp:先执行 chcp 65001 切换控制台为 UTF-8。

  5. 终极建议:生产环境统一使用英文/数字/下划线命名文件,彻底规避编码问题。

6.7 其他常见错误码速查

错误码 含义 常见原因与处理
220 服务就绪 正常,显示 banner
230 登录成功 正常
331 需要密码 正常
421 Service not available 服务不可用 listen=YES 却由 xinetd 托管;服务未启动
421 There are too many connections from your internet address. 超过 max_per_ip 调大 max_per_ip 或关闭多余会话
421 There are too many connected users, please try later. 超过 max_clients 调大 max_clients
425 Failed to establish connection 数据连接建立失败 被动端口未放行、conntrack 模块未加载
426 Failure writing network stream 传输中断 网络抖动、data_connection_timeout 过短
500 OOPS: ... vsftpd 内部拒绝启动/运行 见 6.5 节 chroot 相关
530 Login incorrect 认证失败 见 6.3 节
550 Permission denied 权限不足 见 6.4 节
550 Failed to change directory 目录不存在或无 x 权限 检查 local_root/anon_root 路径是否存在
553 Could not create file 无法创建文件 见 6.4 节,重点查 SELinux 与目录属主
netout: Broken pipe 连接被中途切断 TCP Wrapper/防火墙拒绝、空闲超时

6.8 注意事项汇总

  1. write_enable 是写操作的总开关,必须开启才能上传、创建目录等;匿名的 anon_upload_enable 等参数只有在它开启后才起作用。
  2. chroot_local_user=YES 可将本地用户禁锢在其主目录,提升安全性;vsftpd ≥ 3.0 需配合 allow_writeable_chroot=YES。
  3. 匿名用户上传通常需要目录具备 ftp 用户或指定用户的写入权限;匿名根目录本身绝不能可写。
  4. 被动模式在防火墙 / NAT 环境中更常用,需要固定并放行数据端口范围。
  5. TCP Wrapper 的 hosts.allow 优先级高于 hosts.deny。
  6. userlist_deny 的值决定了 user_list 是黑名单(YES)还是白名单(NO)。
  7. 启用白名单时,别忘了把 ftp、anonymous 加进名单,否则匿名访问与 yum 源都会失效。
  8. pasv_enable=NO(只允许主动模式)会让 yum、wget 等默认被动的客户端失败。
  9. max_clients 与 max_per_ip 只在 standalone(listen=YES)模式下有效。
  10. 修改虚拟用户密码后必须重新执行 db_load。
  11. 传输二进制文件(压缩包、图片、程序)前务必执行 binary。
  12. 明文 FTP 的账号密码在网络上是完全裸奔的,跨公网请务必用 FTPS 或直接改用 SFTP。

七、综合练习

练习一:安装并启动 vsftpd

  1. 安装服务器和客户端:

     sudo yum install vsftpd ftp -y
  2. 启动服务:

     systemctl start vsftpd        # CentOS 7
     # /etc/init.d/vsftpd start    # CentOS 6
  3. 使用 ftp 命令访问:

     ftp 192.168.1.130

练习二:设置一个基本的 FTP 服务器

要求:

  1. 允许匿名用户和本地用户登录。
  2. 允许匿名用户上传文件,但不允许创建目录。
  3. 每个 IP 只允许一个连接,服务器总共允许 5 个连接。
  4. 限制匿名用户传输速度为 100KB/s,本地用户为 200KB/s。
  5. 把被动连接的端口范围控制在 30001 至 30010 之间。

参考答案:

# /etc/vsftpd/vsftpd.conf
anonymous_enable=YES
local_enable=YES
write_enable=YES

anon_upload_enable=YES
anon_mkdir_write_enable=NO       # 不允许创建目录
anon_umask=022

max_per_ip=1
max_clients=5

anon_max_rate=102400             # 100KB/s
local_max_rate=204800            # 200KB/s

pasv_enable=YES
pasv_min_port=30001
pasv_max_port=30010

xferlog_enable=YES
# 准备匿名上传目录
mkdir -p /var/ftp/upload
chown ftp:ftp /var/ftp/upload
chmod 755 /var/ftp/upload
chmod 755 /var/ftp               # 根目录不可写

# SELinux 与防火墙
setsebool -P ftpd_full_access on
firewall-cmd --permanent --add-port=21/tcp
firewall-cmd --permanent --add-port=30001-30010/tcp
firewall-cmd --reload

systemctl restart vsftpd

验证:

# 匿名上传应成功,mkdir 应失败
ftp 192.168.1.130
ftp> cd upload
ftp> put /etc/hosts        # 226 成功
ftp> mkdir aaa             # 550 Permission denied
# 限速验证
wget ftp://192.168.1.130/upload/hosts
# 并发验证:开第 2 个会话应报 421 too many connections from your internet address

练习三:搭建匿名专用 FTP 服务器

要求:

  1. 禁止本地用户登录,只允许匿名用户登录。
  2. 登录后的默认目录为 /ftp_share。
  3. 在默认目录下创建两个目录:pub、upload。
  4. 只能上传到 upload 目录,不能下载 upload 里面的文件。
  5. 最大传输速率为 100KB/s。
  6. 服务器总连接数最大为 2。
  7. 每个 IP 最大连接数为 1。
  8. 只允许 192.168.0.0/24 网段登录,但排除 192.168.0.227,并将拒绝信息保存到 /var/log/ftp_deny.log。
  9. 用 FTP 做一个 yum 源,并利用该 yum 源安装 gimp。

参考做法:

  1. 关闭 SELinux(实验环境简化处理):

     setenforce 0
  2. 准备目录:

     sudo mkdir -m 755 /ftp_share/
     sudo mkdir -m 755 /ftp_share/pub
     sudo mkdir -m 777 /ftp_share/upload
     sudo chown -R root:root /ftp_share
  3. 设置匿名用户的家目录(两种做法任选其一):

     # 做法 A:修改 ftp 用户家目录
     sudo usermod -d /ftp_share ftp
    
     # 做法 B(推荐):用配置参数指定,不动系统账号
     # 在 vsftpd.conf 中写 anon_root=/ftp_share
  4. 配置服务器 /etc/vsftpd/vsftpd.conf:

     anonymous_enable=YES
     local_enable=NO                  # 禁止本地用户登录
     write_enable=YES
     anon_upload_enable=YES
     anon_mkdir_write_enable=NO
     anon_umask=0777                  # 上传后权限 000,谁都下载不了,实现"只能上传不能下载"
     anon_root=/ftp_share
     anon_world_readable_only=YES     # 只能下载所有人可读的文件
     anon_max_rate=102400
     max_clients=2
     max_per_ip=1
     tcp_wrappers=YES
     xferlog_enable=YES

    要点解析:anon_umask=0777 使上传的文件权限为 000,配合默认的 anon_world_readable_only=YES,即可实现"能上传到 upload 但下载不了 upload 里的文件"。

  5. 限制客户端 IP(TCP Wrapper):

    /etc/hosts.allow:

     vsftpd: 10.1.1.0/255.255.255.0 EXCEPT 10.1.1.3

    /etc/hosts.deny:

     vsftpd: * :spawn echo "`date`" %c >> /var/log/ftp_deny.log

    按题目要求的网段应写为:

    # /etc/hosts.allow
    vsftpd: 192.168.0.0/255.255.255.0 EXCEPT 192.168.0.227
    # /etc/hosts.deny
    vsftpd: * : spawn echo "`date`" %c >> /var/log/ftp_deny.log
  6. 做 FTP yum 源:把光盘内容放到 /ftp_share/yum,客户端 yum 配置中只需把 baseurl 一行修改为 FTP 格式:

     baseurl=ftp://192.168.0.183/yum
     yum clean all
     yum install gimp -y

练习四:操练 FTP 客户端命令

要掌握的操作:登录、退出、列目录、进入某个远程目录、进入某个本地目录、上传、下载、使用二进制传输类型、使用被动/主动数据传输模式、列出可用命令、查看某个命令的帮助信息、显示调试信息。

按以下步骤操练(括号内为参考命令和说明):

  1. 用匿名用户登录 FTP 服务器 192.168.1.130(ftp 192.168.1.130,用户名 ftp 代表匿名用户,密码为空)。
  2. 查看远程当前目录(pwd)。
  3. 查看本地当前目录(lcd,不带参数即显示本地当前目录)。
  4. 列出远程机当前目录的内容(ls)。
  5. 下载远程机当前目录下的文件 bash(get bash,文件将下载到本地当前目录,名字不变)。
  6. 下载 bash 放到本地 /tmp 下,改名为 bash2(get bash /tmp/bash2)。
  7. 进入本地目录 /tmp(lcd /tmp,回到父目录为 lcd ..)。
  8. 下载 bash 放到本地当前目录,改名为 bash3(get bash bash3)。
  9. 运行本地 shell 命令查看刚下载的文件(!ls -l /tmp/bash*)。
  10. 进入远程机目录 upload(cd upload,回到父目录为 cd ..)。
  11. 设置数据传输模式为 passive(passive,看到 Passive mode on 即为成功;该命令是开关式切换,若显示 off 就再运行一次)。
  12. 设置数据传输类型为二进制(binary)。
  13. 上传本机文件 /etc/passwd 到远程机当前目录,改名为 passwd2(put /etc/passwd ./passwd2,若远程已存在同名文件则会出错)。
  14. 列出可用命令(help 或 ?)。
  15. 查找命令 debug 的使用方法(help debug)。
  16. 开启调试信息(debug 1)。
  17. 关闭调试信息(debug 0)。
  18. 退出(exit、bye、quit,或快捷键 Ctrl+D)。

练习五:综合大题

题目:搭建 FTP 服务器,满足以下需求:

  1. 允许本地用户登录,但只允许 a、b、c 三个用户登录;
  2. 所有普通用户登录后统一进入 /ftp 目录;
  3. 允许匿名用户登录,登录到 /var/ftp;
  4. 匿名用户可以下载,不能上传;
  5. a、b、c 用户只能上传和下载自己的文件,不同用户之间不能互相删除文件;
  6. 只使用 vsftpd.log 格式的日志,并实现每月轮转、最多保留 2 个副本;
  7. 只使用主动模式的 FTP 连接方式;
  8. 三个用户只能在 /ftp 目录下活动(chroot);
  9. 匿名用户和普通用户最大下载传输率均为 100KB/s;
  10. 最大总连接数 300,单客户端最大 3 个连接;
  11. 只能在上午 9:30 到晚上 20:30 访问 FTP;
  12. 允许 10.0.0.0/24 网段登录,但拒绝 10.0.0.10,并将拒绝信息记录到 /var/log/ftp_denyip.log;
  13. 在 /var/ftp 下保存光盘文件,另一台机器通过 FTP 方式配置 yum 源;
  14. 另一台机器每天晚上 18:00 使用 lftp 下载 FTP 服务器 /ftp 里的所有内容到自己的 /backup 目录。

参考答案:

1. 创建共享目录并设置粘滞位

mkdir /ftp
chmod 757 /ftp
chmod o+t /ftp            # 粘滞位:用户只能删除自己的文件(满足需求 5)
ls -ld /ftp
# drwxr-xrwt

useradd a; useradd b; useradd c
echo '123456' | passwd --stdin a
echo '123456' | passwd --stdin b
echo '123456' | passwd --stdin c

2. 配置 vsftpd.conf

anonymous_enable=YES      # 需求 3
local_enable=YES          # 需求 1
write_enable=YES

userlist_enable=YES
userlist_deny=NO          # 白名单模式(需求 1)

local_root=/ftp           # 需求 2

local_umask=0066          # 需求 5:新建文件 600,其他用户无法读写

# 需求 4:匿名只下载不上传(不开 anon_upload_enable 即可)
anon_upload_enable=NO
anon_mkdir_write_enable=NO
anon_other_write_enable=NO

xferlog_enable=YES        # 需求 6
xferlog_std_format=NO     # 只使用 vsftpd.log

pasv_enable=NO            # 需求 7:只使用主动模式
connect_from_port_20=YES

chroot_local_user=YES     # 需求 8
# CentOS 7 若 /ftp 可写还需加:allow_writeable_chroot=YES

anon_max_rate=100000      # 需求 9
local_max_rate=100000

max_clients=300           # 需求 10
max_per_ip=3

tcp_wrappers=YES          # 需求 12

listen=NO                 # 需求 11:交由 xinetd 托管以使用 access_times

注意:pasv_enable=NO 会导致 yum 客户端无法通过 FTP 源安装软件(需求 13),因为 yum 默认使用被动模式。实际环境需权衡:若需求 13 必须满足,则应保留被动模式并改用 pasv_min_port/pasv_max_port 限定范围。这是本题内部的一处需求冲突,作答时应指出。

3. 配置白名单

vim /etc/vsftpd/user_list
a
b
c
ftp
anonymous                 # 必须加,否则匿名用户用 lftp 登录不了,yum 也无法使用

4. 配置日志轮转(需求 6)

vim /etc/logrotate.d/vsftpd
/var/log/vsftpd.log {
    nocompress
    missingok
    monthly
    rotate 2
}

5. 配置 xinetd 及访问时间(需求 11)

vim /etc/xinetd.d/vsftpd
service ftp
{
    disable     = no
    socket_type = stream
    protocol    = tcp
    wait        = no
    user        = root
    server      = /usr/sbin/vsftpd
    access_times = 9:30-20:30
}
/etc/init.d/vsftpd stop
/etc/init.d/xinetd restart
netstat -ntlup | grep :21      # 应显示 xinetd

6. 配置 TCP Wrapper 网段控制(需求 12)

vim /etc/hosts.allow
vsftpd: 10.0.0.0/255.255.255.0 EXCEPT 10.0.0.10 : spawn echo "`date`" %c >> /var/log/ftp_denyip.log

原教材示例网段写为 10.1.1.0/255.255.255.0,实际应根据题目要求的 10.0.0.0/24 网段调整为上面的写法。注意掩码必须写完整形式,不能用 /24。

7. 配置 FTP yum 源(需求 13)

服务端:

mount /dev/cdrom /mnt
mkdir -p /var/ftp/yum
cp -a /mnt/* /var/ftp/yum/

客户端:

vim /etc/yum.repos.d/ftp.repo
[ftp]
name=FTP Repo
baseurl=ftp://10.0.0.2/yum
enabled=1
gpgcheck=0
yum clean all && yum repolist

8. 定时下载任务(需求 14)

在客户端配置 lftp 脚本和 crontab。lftp 支持在命令行中指定用户名和密码(可参考 man lftp),每天 18:00 执行:

crontab -e
# 0 18 * * * lftp -u a,password -e "mirror /ftp /backup; quit" ftp://10.0.0.2

验证:

crontab -l
tail -f /var/log/cron
ls -l /backup

练习六:虚拟用户实战(进阶)

要求:

  1. 建立三个虚拟用户 dev、test、readonly,系统中不存在同名账号。
  2. dev 拥有 /data/dev 的完整读写权限,限速 500KB/s。
  3. test 拥有 /data/test 的上传权限,但不能删除文件。
  4. readonly 只能下载 /data/pub,不能做任何写操作。
  5. 所有虚拟用户均被 chroot 在各自目录内。
  6. 被动端口范围 40001–40010,并放行防火墙。

参考答案要点:

# 1. 用户库
mkdir -p /etc/vsftpd/vuser
cat > /etc/vsftpd/vuser/vusers.txt <<'EOF'
dev
dev@123
test
test@123
readonly
read@123
EOF
db_load -T -t hash -f /etc/vsftpd/vuser/vusers.txt /etc/vsftpd/vuser/vusers.db
chmod 600 /etc/vsftpd/vuser/vusers.*

# 2. PAM
cat > /etc/pam.d/vsftpd.vu <<'EOF'
auth       required     pam_userdb.so db=/etc/vsftpd/vuser/vusers
account    required     pam_userdb.so db=/etc/vsftpd/vuser/vusers
EOF

# 3. 宿主账号与目录
useradd -d /data -s /sbin/nologin virtual
mkdir -p /data/{dev,test,pub}
chown -R virtual:virtual /data
chmod 755 /data /data/dev /data/test /data/pub

主配置 /etc/vsftpd/vsftpd.conf 关键项:

anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
guest_enable=YES
guest_username=virtual
pam_service_name=vsftpd.vu
user_config_dir=/etc/vsftpd/vusers_dir
virtual_use_local_privs=YES
chroot_local_user=YES
allow_writeable_chroot=YES
pasv_enable=YES
pasv_min_port=40001
pasv_max_port=40010
xferlog_enable=YES
xferlog_std_format=NO
log_ftp_protocol=YES

个性化配置:

mkdir -p /etc/vsftpd/vusers_dir

# dev:完整读写 + 限速 500KB/s
cat > /etc/vsftpd/vusers_dir/dev <<'EOF'
local_root=/data/dev
write_enable=YES
local_max_rate=512000
EOF

# test:可上传不可删除
cat > /etc/vsftpd/vusers_dir/test <<'EOF'
local_root=/data/test
write_enable=YES
virtual_use_local_privs=NO
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_other_write_enable=NO
EOF

# readonly:只读
cat > /etc/vsftpd/vusers_dir/readonly <<'EOF'
local_root=/data/pub
write_enable=NO
download_enable=YES
EOF

收尾:

setsebool -P ftpd_full_access on
firewall-cmd --permanent --add-port=21/tcp
firewall-cmd --permanent --add-port=40001-40010/tcp
firewall-cmd --reload
systemctl restart vsftpd

# 验证:三个账号在系统中都不存在
id dev; id test; id readonly     # 均应报 no such user

练习七:思考题

  1. 为什么 vsftpd 不允许 chroot 根目录可写?如果允许会有什么安全风险?
  2. anon_umask=0777 为什么能实现"只能上传不能下载"?如果同时设置 anon_world_readable_only=NO 会发生什么?
  3. 一台服务器同时有内网 IP 192.168.1.10 和公网映射 IP 1.2.3.4,客户端从公网访问被动模式 FTP 时 ls 卡死,为什么?如何解决?
  4. max_per_ip=3 在客户端全部处于同一 NAT 出口时会带来什么问题?
  5. 为什么说 SFTP 比 FTPS 更"防火墙友好"?
  6. 用户 zhangsan 的 shell 是 /sbin/nologin,他能通过 FTP 上传文件吗?能 SSH 登录吗?为什么?
  7. 若只放行了 21 端口,主动模式和被动模式分别会出现什么现象?
  8. 虚拟用户上传的文件属主是谁?为什么?这对多虚拟用户共享目录的权限隔离有什么影响?

参考答案要点:

  1. chroot 根可写时,攻击者可在根目录写入特制文件(如替换动态库、写入 .forward 等)配合其他漏洞实现提权越狱,因此 vsftpd 2.3.5 起默认拒绝。
  2. anon_umask=0777 使上传文件权限为 000;默认 anon_world_readable_only=YES 要求文件必须"所有人可读"才能下载,000 不满足,故下不了。若改为 NO,则 vsftpd 不再校验 world-readable,但文件权限仍是 000,而匿名进程(euid=ftp)也不是属主……实际取决于文件属主:若属主是 ftp,Linux 权限位 000 对属主仍拒绝读,因此依然下载失败;关键仍在于文件权限本身。
  3. 服务端在 NAT 后,227 响应中回给客户端的是内网地址 192.168.1.10,客户端无法连通。解决:配置 pasv_address=1.2.3.4 和 pasv_addr_resolve=NO,并在 NAT 设备上映射被动端口范围。
  4. 同一 NAT 出口的所有客户端共用一个源 IP,max_per_ip=3 会导致该出口后只有 3 个人能用 FTP,其余全部收到 421。企业环境需酌情放大或改用其他限流手段。
  5. SFTP 是单通道协议,全部数据都走 TCP 22 这一条连接,防火墙只放行一个端口即可;FTPS 仍是双通道,数据连接端口动态变化,且加密后防火墙的 FTP conntrack helper 无法解析 PORT/PASV 命令,无法自动放行。
  6. 能上传(FTP 认证走 PAM,不检查 shell 是否为交互式),不能 SSH 登录(sshd 会执行用户 shell,/sbin/nologin 直接退出)。这正是纯 FTP 账号的推荐配置。
  7. 主动模式:能登录、能 ls(数据连接由服务器 20 端口出站发起,通常出站放行,且客户端侧需允许入站)——但若客户端有防火墙则失败。被动模式:能登录(21 通),但 ls/get 全部卡死超时,因为客户端要连的高端口被服务端防火墙挡住。
  8. 属主是 guest_username 指定的宿主账号(如 virtual)。因为所有虚拟用户在系统层面都以该账号身份操作文件。影响:从文件系统权限层面无法区分不同虚拟用户,若多个虚拟用户共享同一目录,彼此可以互删文件;隔离只能靠 user_config_dir 中给每人分配不同的 local_root 来实现。
最后修改:2026 年 09 月 20 日
如果觉得我的文章对你有用,请随意赞赏