0. 概述
近一两年,供应链攻击已不只是白帽子的实验试水,转变成黑客和黑产的真实攻击手段,“软件供应链安全”概念,重新成为了炙手可热的话题。
当前对供应链安全的探讨多是关于机制的,例如企业上下游公司的攻击面,或者各种开发语言软件包管理引用的投毒欺骗。但是对于一线的开发实践中的风险,目前鲜有分析。
试想,在一个多人协作开发的项目中,如果:
- 有一个偷懒的开发者复制很多网上贴的示例代码或错误代码;
- 或者一个新加入的开发者,复制了该项目的某些旧代码,其中有一些带有已修复的bug;
- 甚至如果有一个恶意开发者,故意写了一个形似手误的bug、但实际是可以被远程利用的隐蔽后门。
那么项目的拥有者要如何分辨这些有风险的代码?只要把这些潜在的“坏的”开发角色换为上游开源代码供应方,一个完全可能的“开源供应链漏洞”场景就很好理解了。
从这一个简单的假想出发,本文将带领读者看到,设计开源代码使用的开发实践中的真实威胁,以及我们构建好的一个解决方案。
1. “代码复用”引入的深层次供应链问题
目前为止,受到广泛关注的供应链风险,集中于两类:以名称易于混淆的恶意软件包仓库投毒,如PyPi、NPM仓库的投毒攻击;以及渗透软件厂商的上下游供应商,以窃取其掌握的敏感信息。但源代码的流动和依赖,远非库依赖那么简单。
这里,我们抽象出一个开发实践中普遍存在,但内中安全风险并未引起广泛注意和分析的供应链威胁向量——代码复用。
1.1. 代码复用与漏洞:Google P0分析视角
今年6月,Google Project Zero下属的漏洞根因分析小组发出半年度在野利用漏洞报告。报告分析2022年上半年内,发现被在野利用的系统或关键基础软件0day漏洞中,50%(9/18)并非全新孤立的漏洞,而是某个历史漏洞的关联“变种”,主要分为三类:
- 历史被修复的漏洞,补丁代码被回滚造成漏洞重现,如CVE-2022-22620,是WebKit引擎在2013年已经修复的漏洞,于2016年代码重构中被原样改了回去;
- 某个此前被修复的漏洞,在有关联和相似关系的其它功能模块中发现类似漏洞,如CVE-2021-39793,是Linux etnaviv驱动程序修补的bug,在Google Pixel Mali GPU私有驱动代码中的同源漏洞;
- 对于漏洞根因,此前的修复方案不完整,造成可在新的漏洞触发路径利用,如Windows系统win32k驱动中,CVE-2022-21882是CVE-2021-1732的相同用户模式回调bug。
这样的观察侧面表明,自体或继承的同源代码,带有复杂的风险,特别是对黑客黑产而言,挖掘利用这些“炒冷饭”的漏洞,实际是很经济的实践。
1.2. 各种形式的“代码复用”及对应的风险案例
结合开源生态以及企业级产品开发实践,我们归结除了简单的软件包引用形式外,至少有三个颗粒度的代码复用形式,特别是在C/C 这类没有统一的包依赖管理机制的主流开发语言中存在。
1.2.1. 开源项目子目录或文件形式包含
某些项目依赖于特定开源组件功能,但出于维持明确的依赖关系、清晰的制品形式的目的,并不会动态链接(依赖)独立的软件包或动态库,而是将固定版本的开源代码,完整目录或者裁剪的部分文件形式,直接包含在代码目录下,同步编译静态链接在制品二进制中。
这种情况下,开发者的诉求一般是追求“稳定可用即可”,并规避依赖子模块接口更新带来的维护成本,更不会主动关注子模块是否有安全更新并跟进,因此很容易存在有历史漏洞的成分。
例如,Java开发的移动端通信SDK和软件Telegram,以jni调用形式包含了boringssl,而后者是Google自OpenSSL 1.1.0历史分支fork并维护的二次开发项目。虽然boringssl以一定机制跟进了上游的漏洞修复,但Telegram对这些依赖的更新没有确定策略,最后一次更新boringssl停留在2020.8.15,因此近两年多的漏洞都可能影响着Telegram(如果构造出合理的调用路径),并进一步影响封装Telegram的更下游应用,如Nekogram。
1.2.2. 函数和片段级别代码复用(复制)
开源代码往往也成为开发实践中取之不竭的代码模板和材料。对于某些典型原子功能的实现,开发者“借用”开源代码片段并依据自己项目的上下文做适当修改变形,是较为普遍,但又无法评估存在占比的实践。根据Synopsys今年在17个商业体超过2400个商用软件代码仓库中分析后,形成的开源安全与风险分析报告认为,商业软件中78%比例的代码实际是开源代码。
在我们对开源代码的分析中,也有一定量的代码复用,并随着复用的旧版本开源代码引入潜在脆弱代码的案例。例如,某款韩国 自研 的IoT专用安全库中的密码算法实现中,即发现了疑似复制自OpenSSL密码算法库中中国SM2算法的功能实现,且复制版本为被爆出高危漏洞CVE-2021-3711的修补前函数。
1.2.3. 数据结构和接口调用的语法复用
另一种典型的问题是数据结构和接口的误用。对某些未充分文档化的数据结构和功能接口,项目内部协作的开发者可能存在共通的错误理解和误用情况;作为SDK导出的接口被下游开发者误用的情况更是屡见不鲜。此时就会看到针对一个漏洞根因的多处位置、语法各异但语用同源的漏洞,这样的案例屡见不鲜。
一个很容易理解的案例是,OpenSSL漏洞CVE-2021-3712,根因在于定义的结构体ASN1_IA5STRING,其中带有一个非'