第24章 FTPサーバーのセキュリティ設定
LPIC-2 212.2 相当
FTPは古くからあるファイル転送プロトコルで、現在では機密性の高い用途にはSFTP(SSH経由、前々章で扱いました)が好まれる傾向にありますが、不特定多数への公開ダウンロードサイトなど、匿名アクセスを前提とした用途では今でも使われています。ここではFTPプロトコルの内部構造から、代表的なFTPサーバーソフトウェア(vsftpd・Pure-FTPd・ProFTPd)の安全な設定、ファイアウォールとの兼ね合いまでを扱います。
FTPプロトコルの構造
FTPは、他の多くのプロトコルにはない特徴的な仕組みを持っています。コマンドのやり取りを行う「制御コネクション」と、実際にファイルの中身をやり取りする「データコネクション」を、別々のTCPコネクションとして確立します。
| コネクション | 役割 | ポート |
|---|---|---|
| 制御コネクション | ログイン・ディレクトリ移動・ファイル一覧要求などのコマンドをやり取りする | 21番(サーバー側) |
| データコネクション | 実際のファイルの中身や、ディレクトリ一覧の結果を転送する | アクティブモードでは20番(サーバー側)、パッシブモードでは動的な高位ポート |
制御コネクションは接続している間ずっと維持されますが、データコネクションはファイル転送のたびに新しく確立され、転送が終わると切断されます。この「1つのプロトコルで2本のコネクションを使う」という構造が、後述するファイアウォール・NAT環境での特有のトラブルの根本原因になります。
アクティブモードとパッシブモードの違い
アクティブモード(サーバーの20番からクライアントへ接続を張る)
クライアント サーバー
|--- 制御コネクション確立(クライアント発信、宛先21番) --->|
|--- PORTコマンド(クライアント側の待受ポートを通知) ------>|
|<-- データコネクション(サーバーの20番から新規発信) -------|
|=== ファイル転送 ============================================|
パッシブモード(クライアントがサーバーの高位ポートへ接続する)
クライアント サーバー
|--- 制御コネクション確立(クライアント発信、宛先21番) --->|
|--- PASVコマンド ------------------------------------------->|
|<-- サーバーの待受ポート番号を通知(例:40023番) -----------|
|--- データコネクション(クライアント発信、宛先40023番) --->|
|=== ファイル転送 ============================================|
| モード | データコネクションの向き | ファイアウォール・NAT越しでの相性 |
|---|---|---|
| アクティブ | サーバーの20番ポートから、クライアントが指定したポートへ新規発信する | クライアント側のNAT/ファイアウォールがサーバーからの新規着信を拒否するため、失敗しやすい |
| パッシブ | クライアントが、サーバーの通知した高位ポートへ新規発信する | クライアント側は発信のみで済むため制約を受けにくいが、サーバー側で高位ポート範囲の開放が必要 |
アクティブモードでは、サーバー側がクライアントの指定したポートへ新規に接続しに行くため、クライアントが家庭用ルーターやNATの内側にいる場合、外部からの新規着信としてブロックされて失敗することがよくあります。パッシブモードでは逆にクライアント側が接続を開始するため、この問題を回避できます。現代のインターネット利用者のほとんどがNATの内側(家庭用ルーター、モバイル回線のキャリアNATなど)にいるため、クライアント側に一切の着信設定を求めないパッシブモードが事実上の標準になっています。その代わり、パッシブモードを使うにはサーバー側でデータ転送用の高位ポート範囲を明示的に開放しておく必要があります。
FTPが平文であることの問題とFTPS/SFTPとの違い
通常のFTPは、ユーザー名・パスワードを含めすべての通信が平文でやり取りされるため、経路上での盗聴によって認証情報やファイルの中身がそのまま読み取られてしまうリスクがあります。この問題に対応する方法として、FTPSとSFTPという似た名前の2つの規格がありますが、両者は全く別物です。
| 規格 | 正体 |
|---|---|
| FTPS(FTP over TLS/SSL) | 既存のFTPプロトコルに、TLSによる暗号化を追加したもの。制御・データの両コネクションをTLSで保護する。21番ポートを使い続ける「Explicit」方式と、専用ポート(990番)を最初から使う「Implicit」方式がある |
| SFTP(SSH File Transfer Protocol) | SSHプロトコルの上で動くサブシステムの1つで、FTPとはプロトコル的に無関係。1本のSSH接続(22番ポート)の中でファイル操作を行うため、制御・データコネクションの分離という問題自体が発生しない |
vsftpdの設定
vsftpd("very secure FTP daemon")は、名前の通りセキュリティを重視して設計されたFTPサーバーで、多くのディストリビューションで既定のFTPサーバーとして採用されています。設定ファイルは/etc/vsftpd.conf(ディストリビューションにより/etc/vsftpd/vsftpd.conf)です。実務レベルの完全な設定例を、コメント付きで示します。
/etc/vsftpd.conf の完全な例
anonymous_enable=NO # 匿名(ログイン不要)アクセスを許可するか
local_enable=YES # ローカルのUnixユーザーアカウントでのログインを許可するか
write_enable=YES # 書き込み(アップロード・削除等)を全体として許可するか
local_umask=022 # ローカルユーザーがアップロードしたファイルのumask
anon_upload_enable=NO # 匿名ユーザーのアップロードを許可するか
anon_mkdir_write_enable=NO # 匿名ユーザーによるディレクトリ作成を許可するか
anon_other_write_enable=NO # 匿名ユーザーによる削除・リネーム等その他の書き込み操作を許可するか
chroot_local_user=YES # ログインユーザーを自分のホームディレクトリ配下に閉じ込める
allow_writeable_chroot=YES # chrootルートが書き込み可能でも起動を許可する(下記の500 OOPS参照)
chroot_list_enable=YES # chroot_local_userの例外リストを使うか
chroot_list_file=/etc/vsftpd.chroot_list # 例外リストのパス
userlist_enable=YES # userlist_fileによるログイン可否リストを使うか
userlist_deny=YES # YESならリスト記載ユーザーを拒否(ブラックリスト)、NOならリスト記載ユーザーのみ許可(ホワイトリスト)
userlist_file=/etc/vsftpd.user_list # リストのパス(root等を記載し、rootでのFTPログインを禁止するのが定石)
pasv_enable=YES # パッシブモードを許可するか
pasv_min_port=40000 # パッシブデータ転送に使うポート範囲の下限
pasv_max_port=40100 # パッシブデータ転送に使うポート範囲の上限
pasv_address=203.0.113.10 # NAT環境で、クライアントに通知する外部(グローバル)IPアドレス
ssl_enable=YES # FTPS(TLS暗号化)を有効にするか
rsa_cert_file=/etc/ssl/certs/vsftpd.crt # サーバー証明書
rsa_private_key_file=/etc/ssl/private/vsftpd.key # 秘密鍵
force_local_data_ssl=YES # データ転送そのものにもTLSを必須にする
xferlog_enable=YES # 転送ログを記録するか
xferlog_file=/var/log/vsftpd.log # ログの出力先
log_ftp_protocol=YES # FTPプロトコルのコマンドレベルのやり取りまで詳細にログするか
listen=YES # IPv4でスタンドアロン動作(vsftpd自身がデーモンとして待ち受ける)
listen_ipv6=NO # IPv6でのスタンドアロン動作(listenとlisten_ipv6は同時にYESにできない)
chroot_local_user=YESにした状態で、ホームディレクトリ自体がそのユーザーから書き込み可能になっていると、vsftpdは既知の脆弱性クラス(書き込み可能なchrootルートを起点にした脱出攻撃)を警戒し、セッションの確立を拒否します。この際に表示されるのが500 OOPS: vsftpd: refusing to run with writable root inside chroot()というエラーです。allow_writeable_chroot=YESを設定すればこの警告を無視して起動できますが、根本的な対策としては、ホームディレクトリ自体は書き込み不可(読み取り専用)にしておき、その配下に書き込み可能な専用サブディレクトリ(例:~/uploads)を別途用意する構成の方が安全です。
chroot_list_enableとchroot_list_fileを使うと、全ユーザー一律ではなく例外を設けられます。chroot_local_user=YESの場合、リストに記載されたユーザーは例外的にchrootされなくなります(逆にchroot_local_user=NOの場合は、リストに記載されたユーザーだけがchrootされます)。userlist_enable/userlist_deny/userlist_fileの組み合わせでは、パスワード入力を求める前の段階でユーザー名だけを見てログイン可否を判定でき、rootのような特権アカウントをFTPログインの対象から明示的に除外するのに使われます。
listen・listen_ipv6を使うスタンドアロン方式に対し、古典的な運用としてxinetd経由で必要な時だけvsftpdを起動する方式もあります。この場合listen=NOにした上で、xinetd側に設定を持たせます。
/etc/xinetd.d/vsftpd の例
service ftp
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/vsftpd
}
匿名FTPの構成
不特定多数へファイルを公開するダウンロードサイトなどでは、匿名アクセスを有効にすることがあります。匿名ユーザーがアクセスするルートディレクトリ(慣例的に/srv/ftp)は、所有者をroot(またはFTP専用の管理ユーザー)にし、匿名ユーザー自身には書き込み権限を持たせない(755程度のパーミッション)のが基本です。
$ sudo chown root:root /srv/ftp # 正常時は無出力
$ sudo chmod 755 /srv/ftp # 正常時は無出力
$ ls -ld /srv/ftp
drwxr-xr-x 3 root root 4096 Aug 21 09:20 /srv/ftp
この行のこの値に注目: chown・chmodはどちらも成功時は無出力なので、実際に反映されたかはls -ldで確認します。drwxr-xr-xのうち、所有者(root)以外の書き込みビット(w)が立っていないことが、匿名ユーザーによる書き換えを防ぐポイントです。
- アップロード先ディレクトリを、公開ダウンロード領域とは別の専用ディレクトリに分離する
- アップロード先ディレクトリを「書き込みはできるが一覧取得・ダウンロードはできない」権限にする(アップロードされたファイルがその場ですぐ外部から取得できないようにする)
- 定期的にアップロード内容を確認し、不審なファイルがあれば速やかに削除する
匿名アクセスを許可しつつ、アップロード先を安全に分離する設定例
anonymous_enable=YES
anon_upload_enable=YES
anon_mkdir_write_enable=NO
anon_umask=077
anon_root=/srv/ftp
アップロード専用ディレクトリの権限設定(書き込みはできるが中身は見えない)
$ sudo mkdir /srv/ftp/incoming # 正常時は無出力
$ sudo chown ftp:ftp /srv/ftp/incoming # 正常時は無出力
$ sudo chmod 733 /srv/ftp/incoming # 正常時は無出力
# 733 = 所有者は読み書き実行可、グループ・その他は書き込み・実行のみ(読み取り不可)
# ディレクトリの実行権限だけではファイル名を知らない限り中身を読めないため、
# 一覧取得や推測によるダウンロードのハードルを上げられる
$ ls -ld /srv/ftp/incoming
drwx-wx-wx 2 ftp ftp 4096 Aug 21 09:22 /srv/ftp/incoming
この行のこの値に注目: drwx-wx-wxのグループ・その他の欄にr(読み取り)が含まれていないことが重要です。これにより、匿名ユーザーはファイルを書き込めても、他人がアップロードしたファイル名をlsで一覧取得できません(実行ビットだけがあるため、正確なファイル名を知っていれば個別にアクセスすること自体は可能です)。
anon_umask=077を設定しておくと、アップロードされたファイル自体のパーミッションも本人以外読み取り不可になり、ディレクトリ権限と合わせて二重に保護できます。
Pure-FTPdの主要オプション
Pure-FTPdは、vsftpdと並んでよく使われるもう1つのFTPサーバーです。vsftpdが設定ファイルベースであるのに対し、Pure-FTPdは起動時のコマンドラインオプションで細かく制御する点が特徴です。
| オプション | 意味 |
|---|---|
-l | 認証方式(authバックエンド)を指定する。例:-l unix(Unixアカウント)、-l puredb:/etc/pure-ftpd/pureftpd.pdb(専用DB)、-l pam(PAM連携) |
-A | すべてのユーザーをchroot化する(vsftpdのchroot_local_user=YESに相当) |
-E | 匿名(anonymous)ユーザーのログインを禁止する |
-j | ホームディレクトリが存在しないユーザーに対して自動作成する |
-P | パッシブモード時にクライアントへ通知する外部IPアドレス(vsftpdのpasv_addressに相当。NAT環境で必要) |
-p | パッシブモードのポート範囲を指定する(例:-p 40000:40100) |
-Y | TLSの強制レベルを指定する(0=無効、1=TLSを許可するが必須にはしない、2=TLS必須) |
$ sudo pure-ftpd -A -E -j -P 203.0.113.10 -p 40000:40100 -Y 2
Aug 21 09:25:03 : pure-ftpd: (SSL support: on)
Aug 21 09:25:03 : pure-ftpd: Trusted-IP support: no
Aug 21 09:25:03 : pure-ftpd: Uploads/Downloads ratio: none / none
この行のこの値に注目: フォアグラウンドで起動すると(-Bでデーモン化しない限り)、起動時のログがそのまま端末に表示されます。SSL support: onで暗号化が有効になっていること、指定した-Y 2(TLS必須)が反映されていることをこの起動時ログで確認できます。実運用ではsystemdユニット経由で起動し、通常はデーモン化(バックグラウンド常駐)してこのログはsyslogへ送られます。
Debian系では、これらのオプションを起動コマンドに直接書く代わりに、/etc/pure-ftpd/conf/以下に、オプションに対応した名前のファイルを作り、その中に値だけを書く方式が使われます。起動スクリプト(pure-ftpd-wrapper)がこのディレクトリを読み取り、実際のコマンドライン引数を組み立てます。
/etc/pure-ftpd/conf/ 以下のファイル例
$ cat /etc/pure-ftpd/conf/ChrootEveryone
yes
$ cat /etc/pure-ftpd/conf/PassivePortRange
40000 40100
$ cat /etc/pure-ftpd/conf/TLS
2
ProFTPdの位置づけ
ProFTPdは、vsftpd・Pure-FTPdとは異なり、Apacheのhttpd.confに似た「ブロック構造」の設定構文を採用しているのが最大の特徴です。Apacheの運用経験があれば、直感的に設定を読み書きしやすい点が支持されています。
/etc/proftpd/proftpd.conf の例(Apache風の構文)
<VirtualHost ftp.example.com>
ServerName "Example FTP Server"
User ftp
Group ftp
<Anonymous /srv/ftp>
User ftp
Group ftp
UserAlias anonymous ftp
RequireValidShell off
<Limit WRITE>
DenyAll
</Limit>
</Anonymous>
</VirtualHost>
ProFTPdはモジュール構成も豊富で、データベースと連携した仮想ユーザー管理(mod_sql)や、複数のFTPサイトを1つのプロセスでホストするバーチャルホスト機能など、大規模・複雑な要件に対応しやすい設計になっています。一方、vsftpdやPure-FTPdは、よりシンプルで攻撃対象領域を絞った構成を志向しており、単純なファイル配布・アップロード用途であればこちらが選ばれることが多くなっています。
ファイアウォールとの兼ね合い
パッシブモードを使う場合、サーバー側で開放したポート範囲(pasv_min_port〜pasv_max_portなど)を、ファイアウォール側でも確実に許可しておく必要があります。
nftablesでの許可例
nft add rule inet filter input tcp dport 21 accept
nft add rule inet filter input tcp dport 40000-40100 accept
ただし、Linuxのnetfilter(接続追跡機構)にはnf_conntrack_ftpという専用のヘルパーモジュールが用意されています。このモジュールをロードしておくと、制御コネクション上でやり取りされるPASV応答の内容をカーネルが実時間で解析し、そこで合意されたデータポートへの接続を「関連(RELATED)」として動的に追跡・許可できます。これにより、ファイアウォール側で広いポート範囲を静的に開放しなくても、ESTABLISHED,RELATEDを許可する一般的なルールだけでFTPのパッシブ接続が通るようになります。
$ sudo modprobe nf_conntrack_ftp # 正常時は無出力
$ lsmod | grep nf_conntrack_ftp
nf_conntrack_ftp 16384 0
nf_conntrack 172032 2 nf_conntrack_ftp,nf_nat
この行のこの値に注目: lsmodの出力にnf_conntrack_ftpの行が表示されれば、モジュールが正しくロードされています。3列目(Used by相当)の0は、まだこのモジュールに依存している他のモジュール・接続がないことを示します。恒久的にロードしたい場合は、/etc/modules-load.d/以下にモジュール名を1行書いたファイルを追加します。
nf_conntrack_ftpは、制御コネクションの中身(PASV応答の平文)を解析することで動作します。FTPS(TLSで制御コネクションを暗号化したもの)を使っている場合、ファイアウォールはその中身を読み取れなくなるため、このヘルパーは機能しなくなります。FTPSを使う場合は、conntrackヘルパーに頼らず、パッシブポート範囲を静的にファイアウォールで開放しておく必要があります。
ログの確認とトラブルシューティング
vsftpdはログを/var/log/vsftpd.log(xferlog_std_formatを有効にすると標準のxferlog形式)に記録します。
$ tail -f /var/log/vsftpd.log
Thu Aug 21 09:30:12 2026 [pid 5522] [alice] OK LOGIN: Client "192.168.1.50"
Thu Aug 21 09:30:45 2026 [pid 5522] [alice] OK DOWNLOAD: Client "192.168.1.50", "/home/alice/report.pdf", 245760 bytes, 512.34Kbyte/sec
Thu Aug 21 09:31:02 2026 [pid 5540] [?] FAIL LOGIN: Client "198.51.100.23"
$ grep "FAIL" /var/log/vsftpd.log # ログイン失敗を抽出
Thu Aug 21 09:31:02 2026 [pid 5540] [?] FAIL LOGIN: Client "198.51.100.23"
Thu Aug 21 09:31:05 2026 [pid 5541] [?] FAIL LOGIN: Client "198.51.100.23"
Thu Aug 21 09:31:09 2026 [pid 5542] [?] FAIL LOGIN: Client "198.51.100.23"
この行のこの値に注目: FAIL LOGINの行にある[?]は、ログイン自体が失敗したためユーザー名を特定できなかったことを表します。同一の送信元IP(この例では198.51.100.23)から短時間に何度もFAIL LOGINが続く場合は、ブルートフォース攻撃の兆候であり、fail2banのようなツールでの自動遮断や、ファイアウォールでの当該IPの遮断を検討します。
接続できないときの切り分け手順は、次の順序で確認すると効率的です。
- サービス自体が起動しているかを確認する(
systemctl status vsftpd) - 制御コネクション(21番ポート)に到達できるかを確認する(
telnet server 21やnc -zv server 21)。ここで失敗する場合はファイアウォールやlisten設定自体を疑う - ログインまでは成功するが、ファイル一覧取得(
ls)やダウンロードで止まる場合は、データコネクションの確立に失敗している可能性が高い。クライアントをアクティブモードに切り替えて試し、アクティブでは成功するがパッシブでは失敗する(あるいはその逆)場合、原因はモードに応じたポート開放の問題に絞り込める - パッシブモードで失敗する場合は、サーバーの
pasv_min_port〜pasv_max_portがファイアウォールで開放されているか、NAT環境であればpasv_addressが正しい外部IPアドレスに設定されているか(サーバーの内部IPをそのまま通知していないか)を確認する
特にpasv_addressの設定漏れは、サーバーがNATの内側にある構成(クラウド環境など)で頻発するつまずきポイントです。クライアントに通知されるIPアドレスが内部プライベートIPのままだと、クライアントはそのアドレスへ到達できず、一覧取得や転送だけが原因不明のまま止まって見えます。