GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

PYSEC-2026-3808

Vulnerability from pysec - Published: 2026-09-10 09:44 - Updated: 2026-09-10 11:01
VLAI
Details
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:

image

Impacted products
Name purl
asyncssh pkg:pypi/asyncssh

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "asyncssh",
        "purl": "pkg:pypi/asyncssh"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.23.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "0.8.1",
        "0.8.2",
        "0.8.3",
        "0.8.4",
        "0.9.0",
        "0.9.1",
        "0.9.2",
        "1.0.0",
        "1.0.1",
        "1.1.0",
        "1.1.1",
        "1.10.0",
        "1.10.1",
        "1.11.0",
        "1.11.1",
        "1.12.0",
        "1.12.1",
        "1.12.2",
        "1.13.0",
        "1.13.1",
        "1.13.2",
        "1.13.3",
        "1.14.0",
        "1.15.0",
        "1.15.1",
        "1.16.0",
        "1.16.1",
        "1.17.0",
        "1.17.1",
        "1.18.0",
        "1.2.0",
        "1.2.1",
        "1.3.0",
        "1.3.1",
        "1.3.2",
        "1.4.0",
        "1.4.1",
        "1.5.0",
        "1.5.1",
        "1.5.2",
        "1.5.3",
        "1.5.4",
        "1.5.5",
        "1.5.6",
        "1.6.0",
        "1.6.1",
        "1.6.2",
        "1.7.1",
        "1.7.2",
        "1.7.3",
        "1.8.0",
        "1.8.1",
        "1.9.0",
        "2.0.0",
        "2.0.1",
        "2.1.0",
        "2.10.0",
        "2.10.1",
        "2.11.0",
        "2.12.0",
        "2.13.0",
        "2.13.1",
        "2.13.2",
        "2.14.0",
        "2.14.1",
        "2.14.2",
        "2.15.0",
        "2.16.0",
        "2.17.0",
        "2.18.0",
        "2.19.0",
        "2.2.0",
        "2.2.1",
        "2.20.0",
        "2.21.0",
        "2.21.1",
        "2.22.0",
        "2.23.0",
        "2.3.0",
        "2.4.0",
        "2.4.1",
        "2.4.2",
        "2.5.0",
        "2.6.0",
        "2.7.0",
        "2.7.1",
        "2.7.2",
        "2.8.0",
        "2.8.1",
        "2.9.0"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-54591",
    "GHSA-2wxc-x7rj-hg8f"
  ],
  "details": "| | |\n|---|---|\n| Product | asyncssh (all versions through 2.23.0) |\n| Related | CVE-2019-6111 (same class in OpenSSH) |\n| Fix | AsyncSSH 2.23.1 |\n\nA malicious SSH server can write arbitrary files on the asyncssh SCP client\u0027s 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.\n\n---\n\n**Steps to exploit:**\n\n**Step 1 - Normal usage:** Application calls `await asyncssh.scp((conn, \u0027file\u0027), \u0027/home/user/downloads/\u0027)`. This is the standard, documented API.\n\n**Step 2 - SCP protocol:** asyncssh opens an SSH exec channel, runs `scp -f file`. The server controls the filename field:\n\n```\nC0644 100 ../pwned.txt\\n       (simple traversal)\n\nD0755 0 ..\\n                   (traverse up, repeat as needed)\nC0644 47 .bashrc\\n             (write payload)\nE\\n\n```\n\n**Step 3 - `_parse_cd_args`** (`scp.py:134-142`) returns the filename verbatim:\n\n```python\ndef _parse_cd_args(args: bytes) -\u003e Tuple[int, int, bytes]:\n    permissions, size, name = args.split(None, 2)\n    return int(permissions, 8), int(size), name  # no sanitization\n```\n\nThe returned `name` is not passed through `basename()` and is not checked for `..` or `/` components.\n\n**Step 4 - `_recv_files`** (`scp.py:706-713`) joins the unsanitized name:\n\n```python\nnew_dstpath = posixpath.join(dstpath, name)\n```\n\nWith `dstpath=b\u0027/home/user/downloads/subdir\u0027` and `name=b\u0027../pwned.txt\u0027`, this resolves to `/home/user/downloads/pwned.txt`, outside the target.\n\n**Step 5 - File write:** `_recv_file` opens the traversed path via `self._fs.open(dstpath, \u0027wb\u0027)` and writes attacker-controlled content. The resolved path is not checked against the target directory boundary.\n\n**Step 6 - RCE chains:**\n\n| Target | Execution trigger | Reliability |\n|---|---|---|\n| `~/.bashrc` | Next terminal open | High |\n| `~/.profile` | Next login | High |\n| `~/.ssh/rc` | Next SSH connection (requires sshd) | High |\n| `~/.ssh/authorized_keys` | Attacker logs in with `command=` | Medium |\n\n---\n\n**Reproduction:**\n\nLink to reproduction script: [path_traversal_poc.zip](https://github.com/user-attachments/files/28160665/path_traversal_poc.zip)\n\n```bash\ndocker build -t asyncssh-scp-traversal -f Dockerfile .\ndocker run --rm asyncssh-scp-traversal\n```\n\nThe attached `poc_scp_traversal.py` starts a malicious SSH server in-process using asyncssh\u0027s own API, then downloads from it via `asyncssh.scp()`.\n\n*Expected Output:*\n\n\u003cimg width=\"1400\" height=\"815\" alt=\"image\" src=\"https://github.com/user-attachments/assets/496745e4-d11d-4ed8-bddd-d15dd13d1751\" /\u003e",
  "id": "PYSEC-2026-3808",
  "modified": "2026-09-10T11:01:58.237758Z",
  "published": "2026-09-10T09:44:57.655635Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/ronf/asyncssh/security/advisories/GHSA-2wxc-x7rj-hg8f"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54591"
    },
    {
      "type": "WEB",
      "url": "https://github.com/ronf/asyncssh/commit/d730803b8e4e94c20c7580d90f94d1e05f9f58de"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/ronf/asyncssh"
    },
    {
      "type": "WEB",
      "url": "https://github.com/ronf/asyncssh/releases/tag/v2.23.1"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/asyncssh"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-2wxc-x7rj-hg8f"
    }
  ],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "asyncssh has SCP Path Traversal to Arbitrary File Write"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…