CVE-2021-36260

Catalpa 网络安全爱好者

2021 年 09 月 18 日, 一篇博客公开了一个影响海康威视 IPC/NVR 多个型号设备的未授权代码执行安全风险,编号为 CVE-2021-36260,本文此漏洞进行复现分析。

获取固件

根据披露信息,去海康威视欧洲区域网站下载设备固件,由于此漏洞影响不同型号设备,我们选择 DS-2CD1x10 系列进行测试,点此下载固件

拿到固件后 binwalk 直接解包,得到一个 linux rootfs,以及一个 cramfs 分区数据。(PS:某些版本的公开固件可能是加密的,需要读取 flash 芯片数据)

尽管能够成功解开固件,但是在解包的文件系统中没有找到类似 httpd 等关键程序。在 cramfs 分区中存在一些可执行文件,其中一个叫做 davinci_bak 的文件既不是可执行程序,也不是配置文件,且通过 binwalk 查看熵值发现文件似乎经过加密。

搜索 davinci_bak 在 daemon_fsp_app 文件中找到相关信息,此程序从 /dev/hikio 设备文件中读取密钥,然后利用 OpenSSL 对 davinci_bak 进行 AES 解密,猜测此文件内包含主要逻辑。

串口调试

从本地固件中无法得到 AES 密钥信息,所以我们要尝试从真机上提取主程序。

拆解机器,在主板上找到了预留的 UART 调试接口,焊接处理之后即可使用 FT232 调试。

接好线路将串口工具接入电脑,设置波特率为 115200,上电开机看到串口有数据输出。

等待系统正常启动,发现输出了命令提示符,但是许多 linux 命令都无法使用,ps 看到目前我们操作的是 /bin/psh 程序,此程序为海康威视编写的 protect-shell,禁用了大部分系统功能。

获取 root shell

为了方便后续的操作,我们需要获取完整的 shell,通过在网络上检索信息,找到 ipcamtalk 论坛一篇帖子,其中提到了可以通过修改 u-boot 启动参数的方式来获取完整 shell。

重启设备,当串口提示 “Hit Ctrl+u to stop autoboot:” 时按下 Ctrl + u 进入 u-boot 命令行,输入 help 看到帮助信息。

用 “setenv bootargs console=ttyS0,115200 root=/dev/ram0 mem=61M dbg=9 debug single” 命令来修改 bootargs 环境变量,“saveenv” 保存修改,“reset” 重启设备,等待设备启动后即可得到完整的 shell。

不过由于我们修改了启动进程,导致目前系统还未初始化,其中的关键程序没有启动,可以执行以下命令序列完成设备初始化并保留 root shell。

1
2
3
4
5
rm /bin/psh
echo "#!/bin/bash" > /bin/psh
echo "/bin/bash" >> /bin/psh
chmod +x /bin/psh # 将 psh 替换为完整的 bash
./linuxrc # 执行系统初始化脚本

挂载 nfs

设备自带的 busybox 经过大幅度裁剪,缺少 curl、wget 等向设备传输文件的功能,导致后续测试困难。我们考虑用 nfs 实现文件传输,在摄像头上利用 mount 命令挂载外部设备的 nfs 文件系统到本地。

首先准备一份完整的 busybox,在本地开启一个 nfs 服务器,设备上执行以下命令

1
2
3
4
mkdir /tmp/hik
busybox mount -o nolock -t nfs 192.168.0.160:/home/iot/Desktop/hik /tmp/hik
chmod 777 /tmp/hik/busybox-armv5l
/tmp/hik/busybox-armv5l telnetd -l/bin/sh -p31337 &

执行后即可连接设备 31337 端口得到 shell,防止串口输出的其它信息干扰分析。

补丁对比

在 /home/process 目录下找到解密完成的 davinci 程序,将它拷贝回 nfs 目录

1
cp /home/process/davinci /tmp/hik

这样在 nfs 服务器端就能下载到此程序。

同样的操作我们解包出修复前和修复后的两个 davinci 程序,用 bindiff 进行补丁对比,得到的结果中涉及一个叫做 hwif_is_all_language_character_legal 的函数,两个版本函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// OLD Version
int __fastcall hwif_is_all_language_character_legal(char *filename, int a2)
{
int v3; // r4
size_t filename_len; // r0
size_t v5; // r5
char *v6; // r0
char *filtered_filename; // r6
char *tmp_buf; // r2
size_t i; // r3
int v10; // r1
char v11; // r0
bool v12; // zf
size_t v13; // r0
size_t v14; // r2

v3 = a2;
if ( !filename )
goto LABEL_5;
if ( !a2 )
{
dev_debug_v1(2, 48, "basefun_lib/string/string_deal.c");
return v3;
}
filename_len = strlen(filename);
v5 = filename_len;
if ( filename_len != v3 || (v6 = calloc(1u, filename_len + 1), (filtered_filename = v6) == 0) )
{
LABEL_5:
dev_debug_v1(2, 48, "basefun_lib/string/string_deal.c");
return -1;
}
tmp_buf = v6;
for ( i = 0; i < v5; ++i )
{
v10 = filename[i];
v11 = filename[i];
v12 = v10 == '`';
if ( v10 != '`' )
v12 = v10 == ';';
if ( !v12 )
*tmp_buf++ = v11;
}
v3 = 0;
*tmp_buf = 0;
memset(filename, 0, v5);
v13 = strlen(filtered_filename);
if ( v13 >= v5 )
v14 = v5;
else
v14 = v13;
memcpy(filename, filtered_filename, v14);
free(filtered_filename);
return v3;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// NEW Version
int __fastcall hwif_is_all_language_character_legal(int a1, unsigned int a2)
{
unsigned int i; // r3
unsigned __int8 v3; // r12
bool v4; // cc
int result; // r0

if ( a1 )
{
for ( i = 0; i < a2; ++i )
{
v3 = *(a1 + i);
v4 = v3 - 48 > 9;
if ( v3 - 48 > 9 )
v4 = v3 - 97 > 25;
if ( v4 && v3 - 65 > 25 && v3 != 45 && v3 != 95 )
goto LABEL_11;
}
result = 0;
}
else
{
dev_debug_v1(
1,
38,
"hardwareif/r2/unihardwareif.c",
402,
"hwif_is_all_language_character_legal",
"p_str is NULL.\n");
LABEL_11:
result = -1;
}
return result;
}

旧版本只检查了字符串中是否包含 `;,而新版中限制字符串是纯数字/字母组成。

漏洞分析

通过交叉引用 hwif_is_all_language_character_legal 函数定位到漏洞的具体位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ...
if ( hwif_is_all_language_character_legal(a1, v3) )
goto LABEL_13;
snprintf(s, 0x1Fu, "%s%s", "/home/webLib/doc/i18n/", a1);
if ( !check_is_file_exist(s) )
{
dev_debug_v1(5, 38, "hardwareif/r2/unihardwareif.c");
return 0;
}
memset(s, 0, 0x20u);
snprintf(s, 0x1Fu, "/dav/IElang.tar %s.tar.gz", a1);
memset(v5, 0, sizeof(v5));
snprintf(
v5,
0xFFu,
"tar -xf %s -C %s; tar -zxf %s%s.tar.gz -C %s",
s,
"/home/webLib/doc/i18n/",
"/home/webLib/doc/i18n/",
a1,
"/home/webLib/doc/i18n/");
if ( system(v5) < 0 )
{
//...
}
//...

我们可以看到参数 a1 经过 hwif_is_all_language_character_legal 过滤之后会将其作为文件名检查目标文件是否存在,如果不存在则将 a1 拼接到 tar 命令中直接带入 system 函数执行,显然存在命令注入。

  • Title: CVE-2021-36260
  • Author: Catalpa
  • Created at : 2021-10-18 00:00:00
  • Updated at : 2024-10-17 08:46:37
  • Link: https://wzt.ac.cn/2021/10/18/CVE-2021-36260/
  • License: This work is licensed under CC BY-SA 4.0.