BlackHat Europe 2023 议题学习(一)

Old code dies hard: Finding new vulnerabilities in old third-party software components and the importance of having SBoM for IoT/OT devices

0x01. 研究思路

研究目标:Sierra Wireless AirLink Gateways

目标搜集:Shodan 搜索 ACEmanager 的指纹

  • ACEmanager 是 Sierra Wireless AirLink Gateways 的 Web 管理系统
  • ACEmanager 不应该暴露在公网

前人研究:

  • Cisco Talos、IOActive 等都开展过相关研究,但更多关注目标本身的代码,比如 ACEmanager 等
  • 没有提及第三方组件的漏洞问题

作者的研究思路:

  • 获取设备、固件、软件包
  • 固件解压、解密
    • 老版本的固件没有加密,还带有调试符号
    • 新版本的固件可以用 IOActive 的公开方法解密
    • 使用 Qemu 来模拟运行环境
  • 黑盒功能分析
  • 软件成分分析(SCA)
  • 对高优目标(二进制、开源组件等)进行静态、动态分析
    • 根据 SCA 分析的结果,挑选高优目标开展研究
      • 可暴露在公网的 ACEmanager
      • 可通过 Telnet 配置的 AT 命令接口
      • 开源组件,尤其是没怎么爆过漏洞的组件
    • 提到了 SBoM / Software Bill of Materials,即软件物料清单,可以简单理解为 SCA 的分析结果(但是是标准化的数据)

0x02. 漏洞案例

2.1 TinyXML

TinyXML 已经被 TinyXML-2 取代,前者不再维护,所以作者选择直接对 TinyXML 进行 Fuzz。

2.1.1 CVE-2021-42260 / CVE-2023-40458

指针 p 指向攻击者可控的 XML 代码,如果 if ( *(p+1) && *(p+2) ) 检查不过,那么 p 会保持不变,可以触发死循环,实现 DoS 攻击。

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
while ( p < now )
{
// Treat p as unsigned, so we have a happy compiler.
const unsigned char* pU = (const unsigned char*)p;

// Code contributed by Fletcher Dunn: (modified by lee)
switch (*pU) {
// ...
case TIXML_UTF_LEAD_0:
if ( encoding == TIXML_ENCODING_UTF8 )
{
if ( *(p+1) && *(p+2) )
{
// In these cases, don't advance the column. These are
// 0-width spaces.
if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 )
p += 3;
else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU )
p += 3;
else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU )
p += 3;
else
{ p +=3; ++col; } // A normal character.
}
}
else
{
++p;
++col;
}
break;
// ...
}
}

2.1.2 CVE-2023-34194 / CVE-2023-40462

触发 assert 实现 DoS 攻击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool TiXmlBase::StringEqual( const char* p,
const char* tag,
bool ignoreCase,
TiXmlEncoding encoding )
{
assert( p );
assert( tag );
if ( !p || !*p )
{
assert( 0 ); // assert
return false;
}
// ......
}

2.2 ALEOS

ALEOS 即 AirLink Enterprise Operating System,可以理解为 IoT 设备的定制系统,ACEmanager 等都运行在该系统上。这里的漏洞实际上是自研代码的漏洞。

2.2.1 CVE-2023-40460

XML 上传 XSS 漏洞:

  • 内容几乎没有校验
  • 扩展名可以任意指定
  • 配合奇怪的路径处理逻辑,最终可以上传 HTML 并且可以通过 URL 访问

前提条件:需要先登录 ACEmanager 再发起上传操作。

2.2.2 CVE-2018-4063

这是作者提到的 Cisco Talos 发现的一个漏洞,这里笔者简单补充一下,细节可参考 TALOS-2018-0748

When uploading template files, you can specify the name of the file that you are uploading. There are no restrictions in place that protect the files that are currently on the device, used for normal operation. If a file is uploaded with the same name of the file that already exists in the directory, then we inherit the permissions of that file. In this case, the files that exist in the directory that the template file is saved to are:

  • fw_expected_rm.cgi
  • fw_status.cgi
  • fw_upload_init.cgi
  • fw_upload_init.sh
  • rm_switching_action.cgi

These files all have executable permissions on the device. By uploading a small wrapper, we can upload arbitrary code to the device and run by simply navigating to the web page through the browser. Since ACEManager is running as root any executables run will be running also as root.

即上传同名文件覆盖原有文件,可以继承原有文件的可执行权限;而通过浏览特定的 Web 页面,可以触发这些 cgi 代码的执行;最终实现 root 权限代码执行。另外,这个也需要先登录 ACEmanager 才能发起攻击。

2.3 硬编码问题

  1. 内置 TLS 证书和私钥(默认使用)
  2. 调试 Shell root 用户硬编码密码 SHA512 哈希值(功能默认禁用)

2.4 openNDS

openNDS(非 OpenDNS)是一个开源的 Captive Portal 组件(基于另一个开源组件 Nodogsplash 进行的分叉开发),官方介绍如下:

openNDS (open Network Demarcation Service) is a high performance, small footprint, Captive Portal. It provides a border control gateway between a public local area network and the Internet.

Captive Portal 在日常生活中非常常见,比如连接机场 WiFi 时出现的认证系统,就是类似 openNDS 这样的组件在工作。

作者在确认所依赖的 openNDS 版本之后,去分析了此版本号之后提交的 commit(因为没有公开的 CVE,所以直接跟着 Patch 来分析),尝试根据 commit 来寻找漏洞(或者没有补完的同类漏洞变种)。

2.4.1 CVE-2023-38320

如果收到的 HTTP 请求不带 User-Agent 字段,可以触发空指针引用实现 DoS(在 Patch b97bf35 中可以看到 do_binauth 对应的 CVE 为 CVE-2023-38322,此外作者还发现了一些其他空指针引用问题,可以参考 OpenNDS v10.1.2 release)。

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
static int show_preauthpage(struct MHD_Connection *connection, const char *query)
{
s_config *config = config_get_config();
char *msg;
const char *user_agent;
char *enc_user_agent;
char *preauthpath;
char *cmd;
char *enc_query;
int rc;
int ret;
struct MHD_Response *response;
preauthpath = safe_calloc(SMALL_BUF);
safe_asprintf(&preauthpath, "/%s/", config->preauthdir);
if (strcmp(preauthpath, config->fas_path) == 0) {
free (preauthpath);
user_agent = safe_calloc(USER_AGENT);
enc_user_agent = safe_calloc(ENC_USER_AGENT);

MHD_get_connection_values(connection, MHD_HEADER_KIND, get_user_agent_callback, &user_agent);

uh_urlencode(enc_user_agent, ENC_USER_AGENT, user_agent, strlen(user_agent)); // strlen(NULL)
// ......
}
// ......
}

2.4.2 CVE-2023-38316

URL unescape 存在命令注入漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
size_t unescape(void * cls, struct MHD_Connection *c, char *src)
{
char *unescapecmd;
char *msg;

debug(LOG_DEBUG, "Escaped string=%s\n", src);

unescapecmd = safe_calloc(QUERYMAXLEN);
msg = safe_calloc(QUERYMAXLEN);

// 命令注入
snprintf(unescapecmd, QUERYMAXLEN, "/usr/lib/opennds/unescape.sh -url \"%s\"", src);
debug(LOG_DEBUG, "unescapecmd=%s\n", unescapecmd);

if (execute_ret_url_encoded(msg, sizeof(msg) - 1, unescapecmd) == 0) {
debug(LOG_DEBUG, "Unescaped string=%s\n", msg);
strcpy(src, msg);
}

free(unescapecmd);
free(msg);

return strlen(src);
}

2.4.3 CVE-2023-41101 / CVE-2023-40465

preauthenticated 存在 Query String 解析堆溢出漏洞(老版本是栈溢出漏洞)。

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
static int preauthenticated(struct MHD_Connection *connection, const char *url, t_client *client)
{
s_config *config = config_get_config();
const char *host = config->gw_address;
const char *accept = NULL;
const char *redirect_url;
char *query;
char *querystr;
// ......

// Check for preauthdir
if (check_authdir_match(url, config->preauthdir)) {

debug(LOG_DEBUG, "preauthdir url detected: %s", url);
query = safe_calloc(QUERYMAXLEN); // #define QUERYMAXLEN 8192

if (!query) {
ret = send_error(connection, 503);
free(query);
return ret;
}

get_query(connection, &query, QUERYSEPARATOR); // Heap Buffer Overflow
debug(LOG_DEBUG, "preauthenticated: show_preauthpage [%s]", query);
ret = show_preauthpage(connection, query);
free(query);
return ret;
}
// ......
}
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
// save the query or empty string into **query.
static int get_query(struct MHD_Connection *connection, char **query, const char *separator)
{
int element_counter;
char **elements;
char *query_str;
struct collect_query collect_query;
int i;
int j;
int length = 0;

// ......
// length 的长度依赖于 URL Query String 内容
for (i = 0; i < element_counter; i++) {
if (!elements[i])
continue;
length += strlen(elements[i]);

if (i > 0) // q=foo&o=bar the '&' need also some space
length++;
}

// ......
for (i = 0, j = 0; i < element_counter; i++) {
// ......
strncpy(*query + j, elements[i], length - j); // Heap Buffer Overflow
// ......
}
// ......
}

2.4.4 Root

作者利用前面的栈溢出漏洞,配合一些其他漏洞实现了设备 Root。

0x03. HoneyPot

最后,作者搭建蜜罐收集了一波攻击数据:在各种各样的攻击数据中发现了针对 Sierra Wireless AirLink Gateways 的攻击,但是是直接使用的 Cisco Talos 公布的 PoC,并没有使用 0day 或其他未知漏洞。

0x04. 小结

首先是学习作者的研究思路,其次是了解一些常见的漏洞模式。

请作者喝杯咖啡☕