跳转至

FTP 服务器

本文你会学到

  • FTP 协议的命令通道与数据通道
  • 主动模式与被动模式的区别与防火墙配置
  • 匿名 FTP 服务配置与限制
  • 用户账号认证与权限隔离
  • vsftpd 配置文件的关键参数
  • 客户端连接与故障排查
  • 为何 FTP 应被 SFTP / FTPS 替代
  • FTP 的安全陷阱与最小权限原则

安全警告:FTP 是明文传输协议

FTP 在网络上以**明文**传输账号、密码和文件内容,极易被中间人截获。现代环境请优先使用 SFTP(基于 SSH)或 FTPS(FTP over TLS)。本文介绍的纯 FTP 仅适用于内网隔离环境或公开匿名下载场景。

FTP 协议概述

FTP(File Transfer Protocol)是最古老的文件传输协议之一,使用 TCP 协议,并同时维护两条连接通道:

  • 命令通道:客户端连接服务器 port 21,传输用户名、密码、目录列表等命令
  • 数据通道:传输实际文件内容,建立方向取决于工作模式

主动模式与被动模式

FTP 有两种数据连接模式,区别在于数据通道由谁发起连接。

sequenceDiagram
    participant C as 客户端
    participant S as FTP 服务器

    Note over C,S: 主动模式(Active)
    C->>S: 命令通道: 连接 port 21
    C->>S: PORT 命令: 告知本地 port BB
    S->>C: 服务器主动从 port 20 连接客户端 port BB(数据通道)

    Note over C,S: 被动模式(Passive / PASV)
    C->>S: 命令通道: 连接 port 21
    C->>S: PASV 命令: 请求被动模式
    S->>C: 回应: 服务器监听 port PASV(随机高端口)
    C->>S: 客户端主动连接服务器 port PASV(数据通道)
维度 主动模式 被动模式
数据连接发起方 服务器 → 客户端 客户端 → 服务器
服务器数据端口 port 20(固定) 随机高端口(可限定范围)
客户端在 NAT 后 ❌ 服务器无法回连内网 ✅ 客户端主动出去,正常工作
防火墙复杂度 客户端防火墙需放行入站 服务器防火墙需放行被动端口范围

现代实践

绝大多数 FTP 客户端默认使用**被动模式**,因为客户端通常在 NAT 或防火墙后面。服务器端需通过 pasv_min_port / pasv_max_port 限定被动端口范围,便于防火墙放行。

安全问题与替代方案

FTP 的核心安全问题:

  • 账号密码以**明文**通过命令通道传输
  • 文件内容以**明文**通过数据通道传输
  • 历史上多次暴露严重安全漏洞

主要替代方案对比见本文末尾「FTP vs SFTP vs FTPS 对比」章节。

vsftpd 安装与基础配置

vsftpd(Very Secure FTP Daemon)以安全为设计核心:以低权限用户身份运行、支持 chroot 监狱、将高权限操作限制在独立受控进程中。

安装

安装 vsftpd
apt update && apt install vsftpd -y
systemctl enable --now vsftpd
安装 vsftpd
dnf install vsftpd -y
systemctl enable --now vsftpd

关键配置项说明

vsftpd 的配置集中在 /etc/vsftpd.conf(Debian)或 /etc/vsftpd/vsftpd.conf(RHEL)。注意:等号两侧不能有空格

/etc/vsftpd.conf 关键参数速查
# ── 服务模式 ──────────────────────────────
listen=YES                    # standalone 模式(IPv4),推荐
listen_ipv6=NO                # 若启用 listen,此项应为 NO

# ── 用户登录权限 ──────────────────────────
anonymous_enable=NO           # 是否允许匿名登录
local_enable=YES              # 是否允许系统本地用户登录
write_enable=YES              # 是否允许上传(写入操作)

# ── chroot 限制 ──────────────────────────
chroot_local_user=YES         # 将本地用户限制在家目录(推荐开启)
chroot_list_enable=YES        # 启用例外名单
chroot_list_file=/etc/vsftpd/chroot_list  # 不受 chroot 限制的账号列表

# ── 被动模式端口范围 ──────────────────────
pasv_min_port=40000
pasv_max_port=40100

# ── 连接控制 ─────────────────────────────
max_clients=50                # 最大同时连接数(0 = 不限)
max_per_ip=5                  # 同一 IP 最大连接数
idle_session_timeout=300      # 空闲超时(秒)
data_connection_timeout=120   # 数据传输超时(秒)

# ── 日志 ─────────────────────────────────
xferlog_enable=YES
xferlog_file=/var/log/xferlog
dual_log_enable=YES
vsftpd_log_file=/var/log/vsftpd.log

# ── 其他 ─────────────────────────────────
use_localtime=YES             # 使用本地时区(否则默认 GMT)
pam_service_name=vsftpd
userlist_enable=YES
userlist_deny=YES

匿名 FTP 配置(只读公开下载)

适用场景:校园内网公开软件镜像、团队内部公开文档分享。

/etc/vsftpd.conf —— 匿名只读配置
# 禁用本地用户,只允许匿名
local_enable=NO
anonymous_enable=YES
no_anon_password=YES          # 跳过密码验证
anon_world_readable_only=YES  # 仅允许下载可读文件
write_enable=NO               # 禁止任何写操作
anon_upload_enable=NO
anon_mkdir_write_enable=NO

# 匿名根目录为 ftp 账号的家目录(默认 /var/ftp)
# 将文件放入 /var/ftp/pub/ 即可对外提供下载

# 速率与连接限制
anon_max_rate=2000000         # 2 MB/s
max_clients=100
max_per_ip=5
idle_session_timeout=600

use_localtime=YES
xferlog_enable=YES
listen=YES
pam_service_name=vsftpd
设置匿名目录权限
1
2
3
4
5
# 匿名根目录不能被 ftp 用户写入(安全要求)
chmod 755 /var/ftp
# 公开下载目录
mkdir -p /var/ftp/pub
chmod 755 /var/ftp/pub

本地用户 FTP(chroot 限制)

允许系统账号通过 FTP 管理自己的家目录,同时用 chroot 防止跨目录访问。

/etc/vsftpd.conf —— 本地用户配置
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022

# 所有本地用户默认 chroot,例外账号写入 chroot_list
chroot_local_user=YES
chroot_list_enable=YES
chroot_list_file=/etc/vsftpd/chroot_list

userlist_enable=YES
userlist_deny=YES
userlist_file=/etc/vsftpd/user_list

use_localtime=YES
xferlog_enable=YES
listen=YES
pam_service_name=vsftpd

chroot + 可写家目录的限制

vsftpd 要求:若用户被 chroot 到家目录,家目录本身不能被该用户写入(否则报 500 OOPS: vsftpd: refusing to run with writable root inside chroot)。

解决方案:

  • 家目录权限设为 755(不可被用户写入),在家目录下创建可写子目录
  • 或在 vsftpd.conf 中添加 allow_writeable_chroot=YES(有安全风险,不推荐)

vsftpd 虚拟用户

虚拟用户是不存在于系统 /etc/passwd 的 FTP 专属账号,通过 PAM 和 Berkeley DB 实现认证。

创建用户数据库

创建虚拟用户数据库
# 安装 db 工具
apt install libdb-utils -y        # Debian
# dnf install libdb-utils -y     # RHEL

# 创建用户文件:奇数行为用户名,偶数行为密码
cat > /etc/vsftpd/virtual_users.txt << 'EOF'
alice
alice_password
bob
bob_password
EOF

# 生成 Berkeley DB 格式数据库
db_load -T -t hash -f /etc/vsftpd/virtual_users.txt /etc/vsftpd/virtual_users.db
chmod 600 /etc/vsftpd/virtual_users.db

PAM 配置

/etc/pam.d/vsftpd-virtual
auth    required pam_userdb.so db=/etc/vsftpd/virtual_users
account required pam_userdb.so db=/etc/vsftpd/virtual_users

vsftpd 配置(虚拟用户模式)

/etc/vsftpd.conf —— 虚拟用户配置
# 虚拟用户映射到系统用户 ftp
guest_enable=YES
guest_username=ftp
local_enable=YES
pam_service_name=vsftpd-virtual

# 虚拟用户目录(每个用户对应一个子目录)
user_sub_token=$USER
local_root=/var/ftp/virtual/$USER
write_enable=YES
virtual_use_local_privs=YES
为虚拟用户创建目录
mkdir -p /var/ftp/virtual/alice /var/ftp/virtual/bob
chown ftp:ftp /var/ftp/virtual/alice /var/ftp/virtual/bob

FTPS(FTP over TLS)配置

FTPS 在 FTP 基础上加入 TLS 加密,保护命令和数据通道。分两种模式:

模式 端口 说明
显式 FTPS(推荐) 21 先建立普通 FTP,再通过 AUTH TLS 升级为加密
隐式 FTPS 990 连接时直接协商 TLS,较旧的方式

生成自签名证书

生成 vsftpd TLS 证书
1
2
3
4
5
6
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
    -keyout /etc/ssl/private/vsftpd.key \
    -out /etc/ssl/certs/vsftpd.crt \
    -subj "/CN=$(hostname)"

chmod 600 /etc/ssl/private/vsftpd.key

vsftpd SSL 配置

/etc/vsftpd.conf —— 显式 FTPS 配置
# 启用 SSL/TLS
ssl_enable=YES

# 证书与私钥
rsa_cert_file=/etc/ssl/certs/vsftpd.crt
rsa_private_key_file=/etc/ssl/private/vsftpd.key

# 强制本地用户使用 TLS(匿名用户可豁免)
force_local_logins_ssl=YES
force_local_data_ssl=YES
allow_anon_ssl=NO

# 仅允许 TLS 1.2+(禁用旧版不安全协议)
ssl_tlsv1_2=YES
ssl_sslv2=NO
ssl_sslv3=NO
ssl_tlsv1=NO

# 推荐的加密套件
ssl_ciphers=HIGH

客户端支持

FileZilla 连接 FTPS 时,在站点管理器的「加密」选项中选择「要求显式 FTP over TLS」(显式模式)即可。服务器使用自签名证书时,客户端首次连接会提示接受证书。

被动模式与防火墙

限定被动端口范围

/etc/vsftpd.conf —— 被动端口配置
1
2
3
4
5
6
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100

# 若服务器在 NAT 后,需告知客户端真实公网 IP
# pasv_address=203.0.113.10

防火墙放行(firewalld)

firewalld 放行 FTP 相关端口
# 放行命令端口
firewall-cmd --permanent --add-service=ftp

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

# 加载 FTP 连接跟踪模块(支持主动模式 NAT 穿透)
firewall-cmd --permanent --add-module=nf_conntrack_ftp

firewall-cmd --reload

防火墙放行(iptables)

iptables 放行 FTP 相关端口
# 加载连接跟踪模块(主动模式必须)
modprobe nf_conntrack_ftp
echo "nf_conntrack_ftp" >> /etc/modules-load.d/ftp.conf

# 放行命令端口
iptables -A INPUT -p tcp --dport 21 -j ACCEPT

# 放行被动端口范围
iptables -A INPUT -p tcp --dport 40000:40100 -j ACCEPT

# 允许已建立的连接(数据通道回包)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

nf_conntrack_ftp 的作用

nf_conntrack_ftp 内核模块能感知 FTP 协议,自动识别 PORT/PASV 命令中的数据端口,从而让 RELATED 规则自动放行数据通道。主动模式下必须加载此模块,否则防火墙会拦截服务器从 port 20 发出的数据连接。

FTP 客户端

命令行客户端

ftp 命令基础用法
# 连接 FTP 服务器
ftp ftp.example.com

# 在 ftp> 提示符下常用命令
# ls          列出远程目录
# cd pub      进入远程目录
# get file    下载文件
# put file    上传文件
# binary      切换二进制模式(传输非文本文件必须)
# passive     切换被动/主动模式
# bye         退出
lftp 功能更强的客户端
# 安装
apt install lftp -y

# 连接并自动使用被动模式
lftp -u alice ftp.example.com

# 镜像下载整个目录
lftp -e "mirror /pub /local/backup; bye" ftp.example.com

# 支持 FTPS
lftp -e "set ftp:ssl-force true; open ftp.example.com"

curl 访问 FTP

curl 操作 FTP
# 列目录
curl ftp://ftp.example.com/pub/

# 下载文件
curl -o file.tar.gz ftp://user:pass@ftp.example.com/pub/file.tar.gz

# 上传文件
curl -T localfile.txt ftp://user:pass@ftp.example.com/upload/

# 使用 FTPS(显式)
curl --ftp-ssl ftp://user:pass@ftp.example.com/pub/

FileZilla 图形客户端

FileZilla 是跨平台的开源 FTP 客户端(filezilla-project.org),同时支持 FTP、FTPS(显式/隐式)和 SFTP。

连接配置要点:

  • 协议:选「FTP」(纯 FTP)或「SFTP」
  • 加密:选「要求显式 FTP over TLS」(FTPS 显式)
  • 传输设置:建议选「被动模式」,最大连接数与服务器 max_per_ip 保持一致

FTP vs SFTP vs FTPS 对比

特性 FTP SFTP FTPS(显式)
传输加密 ❌ 明文 ✅ SSH 全程加密 ✅ TLS 加密
身份认证加密 ❌ 明文 ✅ SSH 密钥/密码 ✅ TLS 加密
底层协议 TCP(21/20) SSH(22) TCP(21)+ TLS
防火墙友好性 ⚠️ 需要两个端口+被动范围 ✅ 单端口 22 ⚠️ 与 FTP 相同
NAT 穿透 ⚠️ 需要连接跟踪模块 ✅ 无问题 ⚠️ 与 FTP 相同
服务器配置复杂度 低(SSH 内置) 高(需 TLS 证书)
客户端兼容性 极广(遗留系统) 广(现代系统) 较广
适用场景 遗留兼容、内网匿名 推荐:通用文件传输 必须 FTP 协议且需加密

现代建议

  • 优先选 SFTP:基于 SSH,配置简单,单端口,全程加密,安全性最高
  • 次选 FTPS:需要兼容仅支持 FTP 的旧客户端时,叠加 TLS 加密
  • 纯 FTP 仅用于:公开匿名只读下载(内网或公网均可),或完全隔离的遗留系统集成

访问控制

黑白名单机制

vsftpd 提供两层用户访问控制:

/etc/vsftpd/ftpusers(PAM 层黑名单)

/etc/pam.d/vsftpd 引用,列表内的用户**始终**无法登录 FTP,不受 vsftpd.conf 控制。默认包含 rootbindaemon 等系统账号。

/etc/vsftpd/user_list(vsftpd 层黑/白名单)

行为由 userlist_deny 控制:

/etc/vsftpd.conf —— user_list 行为控制
1
2
3
4
5
6
7
8
userlist_enable=YES
userlist_file=/etc/vsftpd/user_list

# 黑名单模式(默认):列表内的用户不能登录
userlist_deny=YES

# 白名单模式:只有列表内的用户才能登录
# userlist_deny=NO
示例:切换为白名单模式
1
2
3
4
5
# vsftpd.conf 设置 userlist_deny=NO 后
# user_list 只写允许登录的账号
echo "alice" >> /etc/vsftpd/user_list
echo "bob"   >> /etc/vsftpd/user_list
systemctl restart vsftpd

两个文件的优先级

ftpusers(PAM)检查先于 user_list(vsftpd)。即使 user_list 设为白名单,ftpusers 中的账号依然无法登录。如需允许 root 登录(强烈不推荐),必须同时从两个文件中移除。

日志与排查

日志格式

vsftpd 支持两种日志:

xferlog 格式示例(wu-ftp 兼容格式)
Mon Aug  8 16:35:22 2024 1 192.168.1.100 1234567 /home/alice/data.tar.gz b _ o r alice ftp 0 * c
# 字段依次:时间 传输秒数 客户端IP 文件大小 文件路径 传输类型 特殊操作 传输方向 访问模式 用户名 服务 认证方式 认证用户ID 传输结果
vsftpd.log 格式示例(vsftpd 原生格式)
1
2
3
Mon Aug  8 16:35:22 2024 [pid 12345] CONNECT: Client "192.168.1.100"
Mon Aug  8 16:35:23 2024 [pid 12345] [alice] OK LOGIN: Client "192.168.1.100"
Mon Aug  8 16:35:30 2024 [pid 12345] [alice] OK DOWNLOAD: Client "192.168.1.100", "/home/alice/data.tar.gz", 1234567 bytes, 2.34Mbyte/sec

启用双日志

/etc/vsftpd.conf —— 日志配置
1
2
3
4
5
6
xferlog_enable=YES
xferlog_file=/var/log/xferlog        # wu-ftp 兼容格式
xferlog_std_format=YES

dual_log_enable=YES
vsftpd_log_file=/var/log/vsftpd.log  # vsftpd 原生格式(更易读)

常见错误排查

530 Login incorrect

用户名或密码错误,或账号被拒绝登录。排查步骤:

  • 检查 /etc/vsftpd/ftpusers/etc/vsftpd/user_list 中是否包含该账号
  • 确认 local_enable=YES(本地用户)或 anonymous_enable=YES(匿名)
  • 查看 /var/log/vsftpd.log 中的详细拒绝原因
  • RHEL 系统检查 SELinux:getsebool -a | grep ftp
RHEL 系统 SELinux 放行 FTP 家目录访问
1
2
3
4
5
# 允许本地用户访问家目录
setsebool -P ftp_home_dir=1

# 若需要匿名上传
setsebool -P allow_ftpd_anon_write=1

500 OOPS: cannot change directory

用户登录后无法切换到家目录,通常是 SELinux 或文件权限问题。

排查目录访问问题
1
2
3
4
5
6
7
8
# 检查家目录权限
ls -ld /home/alice

# 检查 SELinux 上下文
ls -Z /home/alice

# 恢复默认 SELinux 上下文
restorecon -Rv /home/alice

被动模式无法传输数据

客户端连上命令通道但无法列目录或下载,典型提示 Can't build data connection

  • 检查防火墙是否放行了 pasv_min_portpasv_max_port 端口范围
  • 确认已加载 nf_conntrack_ftp 模块:lsmod | grep conntrack_ftp
  • 若服务器在 NAT 后,检查 pasv_address 是否配置为公网 IP