# asyncssh has SCP Path Traversal to Arbitrary File Write

asyncssh SCP client trusts server-supplied filenames, so a malicious SSH server traverses out of the download dir and overwrites ~/.bashrc or authorized_keys for RCE.

- Payout: Research
- Severity: high
- Program: asyncssh
- Found by: Jaden-Furtado, JadenFurtado
- Disclosed: 2026-08-26
- CVE: CVE-2026-54591
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-2wxc-x7rj-hg8f
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-2wxc-x7rj-hg8f
- Tags: area:oss domain:oss impact:integrity impact:rce kind:research platform:ghsa sev:high tech:python technique:chained technique:source-review vuln:path-traversal vuln:rce

**Target:** SCP client receive path in asyncssh Python library
**Root cause:** `_parse_cd_args` returns the server-provided filename verbatim without basename() or `..`/`/` checks, and `_recv_files` joins it with posixpath.join against the destination, allowing escape from the target directory.
**Steps:**
1. Application calls `await asyncssh.scp((conn, 'file'), '/home/user/downloads/')`.
2. Malicious server responds to `scp -f file` with `C0644 100 ../pwned.txt\n` or `D0755 0 ..\n` then `C0644 47 .bashrc\n` ... `E\n`.
3. `_parse_cd_args` (scp.py:134-142) splits args and returns `name` unsanitized.
4. `_recv_files` (scp.py:706-713) does `new_dstpath = posixpath.join(dstpath, name)`, resolving outside the target.
5. `_recv_file` opens the traversed path with `self._fs.open(dstpath, 'wb')` and writes attacker content.
6. Chain to `~/.bashrc`, `~/.profile`, `~/.ssh/rc`, or `~/.ssh/authorized_keys` for code execution.
**How found:** Recognized the same vulnerability class as OpenSSH CVE-2019-6111 and checked whether asyncssh adopted the OpenSSH mitigation; found the SCP receive path never sanitizes server filenames.
**Exploitation:** Attacker controls an SSH server the victim's asyncssh SCP client connects to; by emitting `D` directory actions with `..` and `C` file actions with traversal names, the client writes attacker content to arbitrary paths such as `~/.bashrc` or `~/.ssh/authorized_keys`, yielding code execution on next shell/login/SSH connection.
**Escalation:** File write escalated to RCE via shell rc files and authorized_keys with `command=` forced command.
**Insight:** When auditing SCP/rsync-style clients, check whether the OpenSSH CVE-2019-6111 basename mitigation was ported; grep for posixpath.join on server-supplied names and for missing basename() in protocol parsers.

## Proof of concept (copied exactly from the original report)

**Malicious SCP protocol traversal payload**

```
C0644 100 ../pwned.txt\n       (simple traversal)

D0755 0 ..\n                   (traverse up, repeat as needed)
C0644 47 .bashrc\n             (write payload)
E\n
```

**Unsanitized filename parse in _parse_cd_args**

```
def _parse_cd_args(args: bytes) -> Tuple[int, int, bytes]:
    permissions, size, name = args.split(None, 2)
    return int(permissions, 8), int(size), name  # no sanitization
```

**Path join with unsanitized server name**

```
new_dstpath = posixpath.join(dstpath, name)
```

