vsftpd 3.0.5 - High - Remote - Privilege leftover - FTPS data accepts any resumed ticket
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · vsftpd-ssl-data-any-ticket
Class: Privilege leftover Reach: Remote
vsftpd 3.0.5 (Chris Evans) - official tarball SHA-256 26b602ae454b0ba6d99ef44a09b6b9e0dfa7f67228106736df1f278c70bc91d3. Distro FTPS compiles #define VSF_BUILD_SSL (stock tarball #undef) and operators set ssl_enable=YES. Standalone listen=YES is the compiled and sample default.
ssl_accept (ssl.c:494-501) is the data-channel bind. It checks SSL_session_reused() only. There is no SSL_SESSION compare against the control SSL. Data SSL has no FTP USER/PASS. Standalone ssl_init runs in the listener before fork (main.c:147-150), so children inherit one SSL_CTX. SSL_CTX_set_timeout(INT_MAX) (ssl.c:182-186). No SSL_OP_NO_TICKET. SSL_session_reused()==1 means this ClientHello carried a ticket this process can decrypt, not that it is Alice's control session.
The man page for require_ssl_reuse says reuse proves the same master secret as the control channel. The C does not. Distinct from the 3.0.3 Tim Kosse kill-on-reuse-fail (ftpdataio.c:184-187), which still 522s a fresh handshake and does not stop Mallory resuming a ticket minted on a different control.
This leftover is one FTPS transfer. It is not a USER/PASS skip, not a shell, and not a chroot escape.
| ID | no CVE yet |
| Class | Privilege leftover (FTPS data accepts any resumed ticket) |
| Reach | Remote (same-IP PASV; shared NAT is AC:H) |
| CWE | CWE-345, CWE-287 |
| CVSS | High: 7.4 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N |
| Product | vsftpd (Chris Evans) |
| Affected | 3.0.5 with VSF_BUILD_SSL and ssl_enable=YES. require_ssl_reuse=YES (default). require_cert=NO (default) |
| Auth | Mallory: AUTH TLS only (no USER/PASS). Alice: anonymous PBSZ/PROT P |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:18700 control, PASV 18701-18708. Compose vsftpd-ssl-data-any-ticket |
Who: another FTPS client (or on-path) who shares Alice's source IP. PASV accept still requires the control peer IP (privops.c:212-218) unless pasv_promiscuous. Shared NAT is the usual FTPS case require_ssl_reuse was added to stop. Mallory never logs in.
| Method + sink | Privilege |
|---|---|
Mallory AUTH TLS only, TLS 1.2 ALPN ftp, keeps the session ticket from the listener SSL_CTX. Alice anon PBSZ/PROT P, PASV, RETR secret.txt blocked in get_remote_transfer_fd until accept. Mallory same-IP PASV, resumes his ticket (session_reused=True) | Decrypt that one RETR. Lab: data socket 35 bytes VSFTPD-SSL-DATA-ANY-TICKET-WITNESS. Alice 150 then 226. Control TLS and password stay Alice's. No directory walk beyond that transfer. |
ssl_accept gate; lab was RETR steal)| Method + sink | Privilege |
|---|---|
Same ticket resume while Alice STOR / APPE | Alice's named file is written from Mallory's TLS plaintext, as Alice's uid / anon ftp uid, inside the existing chroot |
| Method + sink | Privilege |
|---|---|
Fresh data handshake (reused=False) | 522 session reuse required (3.0.3 kill). That transfer dies. Control stays Alice's |
privops.c:212-218)require_cert=YES plus a presented cert binds SHA-256 digestsPROT C for local users (force_local_data_ssl default YES)ssl_init inheritance)ftp (vsftpd FATALs)I pinned the official vsftpd 3.0.5 tarball rather than a GitHub mirror or a Debian/Fedora tree. Nineteen source hunts. The SSL hunt stopped on ssl_accept: reuse is a boolean, not a bind to this control SSL_SESSION. Listener SSL_CTX plus INT_MAX ticket lifetime is the rest.
Wrong turns: ALPN must be ftp or vsftpd FATALs (3.0.4 ALPACA leftover is already closed). Arm64 link needed explicit -lpam -lcap; stock vsf_findlibs.sh only probes x86_64 paths. Treating the 3.0.3 Tim Kosse kill as this bug: it 522s a fresh handshake on Alice's data fd and does not stop a ticket minted on a different control.
cd lab
./run.sh
Official 3.0.5 with #define VSF_BUILD_SSL. Conf: ssl_enable=YES, allow_anon_ssl=YES, force_anon_logins_ssl=YES, force_anon_data_ssl=YES, require_ssl_reuse=YES, require_cert=NO. Loopback Docker. Two same-IP clients. TLS 1.2 tickets. ALPN ftp. Control 127.0.0.1:18700, PASV 18701-18708. Compose vsftpd-ssl-data-any-ticket. Witness VSFTPD-SSL-DATA-ANY-TICKET-WITNESS. Last line:
SUCCESS vsftpd-ssl-data-any-ticket reused-ticket=yes fresh-handshake=522 VSFTPD-SSL-DATA-ANY-TICKET-WITNESS
Bind the data SSL_SESSION to the control SSL_SESSION (compare session ids / master secret). Cap ticket lifetime to the control connection. Or SSL_OP_NO_TICKET plus a session cache keyed by control. ProFTPD 1.3.9d compares sessions.
Vendor intake is [email protected] (security.appspot.com/vsftpd.html). Do not open a public GitHub issue on any vsftpd mirror.
Abraxas Labs - abraxaslabs.tech - github.com/abraxas - @abraxas_null - [email protected]