asyncssh has SCP Path Traversal to Arbitrary File WriteGHSA-2wxc-x7rj-hg8f
HIGHFix: ronf/asyncssh@d730803GHSA-2wxc-x7rj-hg8f is a high-severity (CVSS 8.1) Path Traversal vulnerability in asyncssh. A fix is available for asyncssh — see the affected versions and patch details below.
Exploitation Status
No confirmed exploitation observed yet
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for GHSA-2wxc-x7rj-hg8f.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
GHSA-2wxc-x7rj-hg8f by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.
Where this sits among everything scored
Of 385,386 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.
Real-World Exposure
asyncsshReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
| Product | asyncssh (all versions through 2.23.0) |
| Related | CVE-2019-6111 (same class in OpenSSH) |
| Fix | AsyncSSH 2.23.1 |
A malicious SSH server can write arbitrary files on the asyncssh SCP client's filesystem by sending filenames containing ../ traversal sequences. The SCP receive path does not currently sanitize server-provided filenames. By chaining directory traversals via the D (directory) action, an attacker can escape any target directory and overwrite ~/.bashrc, ~/.ssh/rc, or ~/.ssh/authorized_keys, achieving code execution. This is the same vulnerability class as CVE-2019-6111. The mitigation applied in OpenSSH does not appear to have been adopted in asyncssh.
Steps to exploit:
Step 1 - Normal usage: Application calls await asyncssh.scp((conn, 'file'), '/home/user/downloads/'). This is the standard, documented API.
Step 2 - SCP protocol: asyncssh opens an SSH exec channel, runs scp -f file. The server controls the filename field:
C0644 100 ../pwned.txt\n (simple traversal)
D0755 0 ..\n (traverse up, repeat as needed)
C0644 47 .bashrc\n (write payload)
E\n
Step 3 - _parse_cd_args (scp.py:134-142) returns the filename verbatim:
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
The returned name is not passed through basename() and is not checked for .. or / components.
Step 4 - _recv_files (scp.py:706-713) joins the unsanitized name:
new_dstpath = posixpath.join(dstpath, name)
With dstpath=b'/home/user/downloads/subdir' and name=b'../pwned.txt', this resolves to /home/user/downloads/pwned.txt, outside the target.
Step 5 - File write: _recv_file opens the traversed path via self._fs.open(dstpath, 'wb') and writes attacker-controlled content. The resolved path is not checked against the target directory boundary.
Step 6 - RCE chains:
| Target | Execution trigger | Reliability |
|---|---|---|
~/.bashrc | Next terminal open | High |
~/.profile | Next login | High |
~/.ssh/rc | Next SSH connection (requires sshd) | High |
~/.ssh/authorized_keys | Attacker logs in with command= | Medium |
Reproduction:
Link to reproduction script: path_traversal_poc.zip
docker build -t asyncssh-scp-traversal -f Dockerfile .
docker run --rm asyncssh-scp-traversal
The attached poc_scp_traversal.py starts a malicious SSH server in-process using asyncssh's own API, then downloads from it via asyncssh.scp().
Expected Output:
<img width="1400" height="815" alt="image" src="https://github.com/user-attachments/assets/496745e4-d11d-4ed8-bddd-d15dd13d1751" />Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | asyncssh | all versions | 2.23.1pip install --upgrade 'asyncssh==2.23.1' |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for asyncssh, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update asyncssh to 2.23.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-2wxc-x7rj-hg8f is resolved across your whole dependency graph.
Workarounds
Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.
Fixing This On Your OS
If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.
This Important flaw in AsyncSSH's SCP client allows a malicious SSH server to perform arbitrary file writes on the client's filesystem through directory traversal. This occurs when an AsyncSSH SCP client connects to a compromised or malicious server, enabling unauthorized data modification or system disruption on the…
The core risk of this flaw is that the malicious server uses a path traversal trick to write files where it shouldn't on your client (like overwriting /etc/shadow or critical system binaries). By making the container's root filesystem read-only, you completely neutralize this attack. Even if the vulnerability is triggered, the container's kernel will block the unauthorized write attempt.Source: Red Hat security advisory for GHSA-2wxc-x7rj-hg8f (CC BY 4.0)
Frequently Asked Questions
Is GHSA-2wxc-x7rj-hg8f in your dependencies?
Find it across PyPI, including transitive dependencies.