密码是对服务、系统和数据的访问权限进行授权的数字身份凭证,常见的密码有API密钥、非对称私钥、访问Token等。
硬编码密码(Hardcoded Secret),或称嵌入式密码(Embedded Secret),是指将密码以明文方式直接写入代码中。这种处理方式极大地提高了攻击者命中密码的概率,使服务或系统暴露在风险中,容易造成严重损失。
针对此问题,本文详细讨论了硬编码密码的成因、危害及治理方法;另外,本文从安全人员角度出发,对现有的硬编码密码检测工具的算法进行了深入调研,并提出了我们的自动化检测工具。
硬编码密码的成因及类型
随着互联网组织转向云架构、SaaS 平台和微服务,密码等数字身份验证凭证的数量和多样性正在快速增长。
与此同时,企业也不断推动更短的发布周期,开发人员面临巨大时间压力的同时,需要处理的密码量比以往任何时候都多。许多开发人员采取捷径,选择使用硬编码的方式处理密码。
在企业的代码仓库中普遍存在大量的硬编码密码问题。据GitGuardian统计,在公共Git存储库上每天会泄露数以千计的密码,其中仅2020年就有超过200万个密码被上传至Git存储库中[1],而2021年该组织发现的密码数量超过600万,同比增长近2倍[2],而私人存储库的密码泄露事件存在可能性比公共库高4倍。根据统计[1][2][6],硬编码密码包括API密钥、访问Token、非对称私钥、认证ID、安全证书、口令、特权用户账户等类型。硬编码密码所涉及的平台十分广泛,包括如下领域:开发工具,如Django、Rapid API;数据存储,如MySQL、Mongo;金融服务,如PayPal、Amazon MWS;消息通讯系统,如Gmail、Telegram;云提供商,如AWS、Azure、Google;私钥;社交媒体,如Twitter、Facebook;版本控制平台,如Github、Gitlab;等等。
除了程序代码中,这些硬编码还容易出现在基础设施配置文件、监控日志、运行日志、堆栈调试track记录、git历史中。所有类别的硬编码密码都使企业暴露在攻击之下。
硬编码密码的危害
硬编码密码主要对安全和研发两方面具有危害:
1. 削弱系统安全性
攻击者常通过公共代码库或反编译分析获得硬编码密码字符串,利用密码访问敏感数据或获取敏感操作权限。
攻击者还可以进一步扩大攻击范围,进行数据勒索、帐户操纵、帐户创建、通过用户数据进行利用等,使得企业和用户都遭受严重损失。
在以下案例中,攻击均是从密码的泄露开始的:
2014年,Uber数据库被未经授权访问,导致数千名Uber司机私人信息的数据被泄露[7];
2016年,Uber又因外部的未授权访问导致5700万用户的个人信息被泄露;
2018年,Github和Twitter[10]在内部日志系统中以明文方式存储密码,分别涉及2700万和3.3亿用户数据泄露;
2020年,用户在Github仓库中发现了星巴克的API密钥,涉及重大信息泄露[8];
2021年,黑客组织 Sakura Samurai 在一次重大数据泄露事件中获得了访问联合国 (UN) 员工私人数据和系统的权限[9]……由硬编码密码导致的安全事故层出不穷,也不断有相关CVE和CWE被披露。硬编码密码对特定设备、固件、服务、应用程序本身,对其连接的IT生态系统其他部分,甚至使用服务的第三方都存在风险,使其同样暴露在风险中。
2. 不易于程序维护
硬编码密码的修复较为困难,密码一旦被利用无法轻易被修正。对于正在线上运行的服务或系统,修复硬编码密码问题需要停服重新发布。大型企业的服务流量较大,服务间还存在依赖,则需要灰度发布,修复流程更长,其间可能持续受到攻击者威胁。密码的蔓延也使维护变得困难。与传统凭证不同,密码旨在分发给开发人员、应用程序和基础设施系统,这将不可避免地使开发中使用的密码数量增加,一个密码可能出现在代码中多处位置,这进一步增加了修复的难度。
此外,开源的代码造成密码泄露,即使在源码中删除硬编码密码,也会残留在git历史里。
如何治理硬编码密码
企业代码中的硬编码密码问题日益严重,只有通过安全人员和研发人员的共同协作才能解决。源代码中的密码泄露很难彻底避免,但与其他漏洞一样,它完全由内生因素决定:开发人员需要访问更多的资源,以更快的速度构建和部署。这意味着只要有足够的纪律和教育,再加上正确的工具,就有可能大幅改善这种情况。
从开发人员角度,需要注意尽量避免将密码以明文形式写入代码中。代码中需要对密码进行校验时,对入站身份验证可使用强单向散列函数进行密码模糊化,并将这些散列结果存储在具有适当访问控制的配置文件或数据库中;对出站身份验证,可将密码存储在代码之外的一个经过严格保护的、加密的配置文件或数据库中,该配置文件或数据库不会被所有外部人员访问,包括同一系统上的其他本地用户[13];大型企业可以使用KMS服务进行一站式密码管理。
从安全人员角度,应尽量做到风险左移,尽早发现密码泄露,帮助开发人员降低修复成本。可通过代码检测扫描,将硬编码密码检测集成到开发工作流程中,提前发现硬编码密码问题。
硬编码密码检测算法
由于硬编码密码有如此的危险性,学术界和工业界都有许多组织针对此问题研发了代码扫描工具。我们对开源工具和学术文章进行了一系列调研,总结了目前的硬编码密码扫描工具常用的检测算法,并对其优缺点进行了讨论。
4.1 正则表达式匹配
正则表达式通常被用来检索符合某种模式的字符串。对于检测具有固定结构或特征的密码,正则表达式可能很有效。常用于密码检测的正则表达式可分为(1)针对各种特定平台密码的表达式和(2)不针对任何平台的通用表达式。
(1)针对各种特定平台密码的表达式
许多平台的API密钥、访问Token、认证ID等具有平台特有的特征,例如亚马逊AWS密钥均以“AKIA”字符串开头;常用于非对称加密的私钥如RSA、EC、PGP及通用私钥等,常由ssh-keygen、openssl等工具生成,多数情况下私钥以单独的PEM等文件格式存储,其内容也具有一定特征,例如RSA私钥文件由"-----BEGIN RSA PRIVATE KEY-----"字符串作为开头。对于这类密码,可以通过匹配具有其特征的正则表达式进行检测。
下表列举了部分常用平台密码的类型以及正则表达式。本文仅以此表举例,实际上特定平台的密码种类十分丰富,此处不便一一列举。
平台 | 密码类型 | 正则表达式 |
Amazon AWS | API密钥 | AKIA[0-9A-Z]{16} |
API密钥 | AIza[0-9A-Za-z\-_]{35} | |
Azure | API密钥 | AccountKey=[a-zA-Z0-9+\/=]{88} |
认证ID | [0-9]+-[0-9A-Za-z_]{32}\.apps\.google | |
PayPal | 访问Token | access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32} |
访问Token | [1-9][0-9]+-[0-9a-zA-Z]{40} | |
RSA私钥 | 非对称私钥 | -----BEGIN RSA PRIVATE KEY----- [\r\n]+(?:\w+:.+)*[\s]* (?:[0-9a-zA-Z+\/=]{64,76}[\r\n]+)+ [0-9a-zA-Z+\/=]+[\r\n]+ -----END RSA PRIVATE KEY---- |
(2)不针对特定平台的通用的表达式
由于特定平台表达式和平台的一一对应性质,其覆盖范围有限,此时需要用覆盖范围较广的通用表达式来补充。许多平台的密码具有一些通用的特征,例如密码字符串以api_key、access_token等关键字为开头。此外,根据开发人员的编程命名习惯规范,也可以根据变量名中的关键字进行匹配,例如变量名中含有Secret、Token等关键字的字符串很可能是密码。
优点
配置简单。
自定义扩展方便。
缺点
正则表达式覆盖范围不够广则容易漏报。
使用一组不准确的正则表达式容易出现大量误报。
即使是正确的正则表达式也有一定程度的误报,例如“AKIAXXXEXAMPLEKEYXXX”虽然符合亚马逊AWS的正则表达式,但并不是有效的密码。
通用表达式中使用变量名关键字匹配的检测方法容易被对抗。
4.2 熵字符串编码检测
在信息论概念中,熵是对不确定性的量度,越随机的数据的熵越高。大多数API密钥、访问Token等密码字符串具有高度熵的特性,因此可以通过搜索高熵字符串来检测密码。这种算法被以TruffleHog[14]为代表的工具所采用。现有的工具一般采用香农熵算法来计算字符串熵值,对字符串A的香农熵值计算公式如下图所示,其中pi表示第i个字符出现的频率[15]。
优点
能够检测出无明显特征的密码,对于正则表达式未覆盖到的范围有补充效果。
可用于对正则表达式检测结果的验证,如上文提到的正则表达式误报字符串“AKIAXXXEXAMPLEKEYXXX”,其熵值较低,可通过验证筛除。
缺点
字符串被判定为密码的熵阈值难以确定,阈值过高容易漏报,阈值过低又容易误报。即使是学术论文中的阈值也全凭实验经验确定,缺乏坚实的理论支撑。
一些高熵值的SHA、MD5等字符串容易被误报为密码。对于这一问题,可通过过滤SHA、MD5值出现较密集的文件扩展名来降低误报,例如.lock, .inc文件。
容易将具有明显升降序的字符串误报为密码。香农熵只对出现频率进行计算,不考虑字符顺序,具有明显升降序的字符串同样会表现出较高熵值,例如Hex编码的“123456789abcdef”和“d9b41a72f683ce5”两字符串香农熵相等,但前者一般不会是一个有效的密码。对于这一问题,需要通过一些启发式处理方式降低升降序字符串的熵值,或通过后期过滤筛除。
硬编码密码检测工具研发实践:Gold-digger
为了保障美团整体研发环境安全,同时节约安全人员的审计成本,我们研发了针对硬编码密码的代码扫描工具。我们认为在众多字符串中寻找密码如同在沙里淘金,因此将工具命名为Gold-digger。
5.1 工具设计
为服务于全公司研发环境,Gold-digger工具有如下需求
- 编程语言无关:公司各业务使用的编程语言不同,Gold-digger需要无障碍地应用于所有编程语言代码中。
- 模块化,方便扩展迭代:为了根据测试反馈结果不断提高效果,Gold-digger需要长期不断迭代。
- 能够集成到软件开发生命周期中:Gold-digger侧重预防,需要工具集成在CI/CD管道中,从源头遏制密码泄露风险。
- 高精确率召回率:Gold-digger的设计初衷之一是节约人力成本,为降低审计、维护和运营压力,必需尽可能准确、全面地检测密码。
基于上述需求,Gold-digger的架构主要分为四个模块:核心引擎、转换器、检测器、过滤器。Gold-digger工作流程如下图,箭头表示数据流向。核心引擎依次读取代码仓库中文件,经过预验证和输入处理后将代码以行为单位传输给检测器,其中部分特殊格式由转换器处理后再传输给检测器;检测器在代码中检测密码候选值;过滤器对密码候选值进行后过滤,将过滤后的密码传回核心引擎;最后核心引擎将代码仓库中所有密码进行收集后,将密码相关信息输出为可读性较强的JSON文件报告。核心引擎:该模块为Gold-digger赋能,负责调度其他模块,也是负责输入输出处理、数据收集存储等。输入处理部分负责读取代码文件,先调用过滤器对文件后缀进行全局预验证,再通过引号标识匹配或调用转换器识别代码中的字符串及其赋值变量;然后核心引擎调用调用检测器和过滤器进行密码收集,将检测到的密码数据以文件为单位存入不同集合,能够方便地对集合进行合并、删减等操作;输出处理部分将仓库中所有密码关键信息,如密码值、文件位置、检测算法等,输出为JSON格式。
转换器:该模块是专为部分特殊格式文件进行格式转换的处理器。尽管核心引擎能处理大部分代码格式,但无法处理.yaml、.ini、.properties等不使用引号作为字符串特征标识的格式。为保证语言无关性,我们使用转换器处理上述特殊格式,将其转换为用引号标识字符串的代码。这种解析方式无需为不同语言分析抽象语法树,能够有效节省算力。
检测器:该模块是Gold-digger的密码检测算法模块。我们在综合调研同类型检测工具后,汲取了各方优点,采用了正则表达式匹配和熵字符串检测两类算法。Gold-digger的检测器包含数十种特定平台正则表达式、通用正则表达式以及Hex编码、Base64编码的熵字符串检测算法。检测器中每种算法以插件方式各自独立,方便扩展、启用或禁用,同时Gold-digger也允许用户自定义检测算法插件。代码将遍历所有检测算法,任一算法命中便记录为密码候选值。
过滤器:该模块为Gold-digger的验证、过滤模块。预验证部分在检测器运行前对文件格式进行验证,筛除压缩文件、多媒体文件等非文本类型;后过滤部分在检测器运行后对所有密码候选值进行启发式过滤,并将过滤后的密码传回核心引擎。后过滤过程中每项密码候选值将遍历所有过滤规则,所有规则都未能筛除的候选值会被记录在密码集合中(检测器和过滤器的主要处理流程如下图所示)。过滤规则主要为开发测试人员凭经验总结的启发式规则,例如:过滤升降序字符串、过滤高度重复字符串、过滤uuid、过滤间接引用赋值等。CI/CD集成:Gold-digger的最终目标是消除代码中的密码泄露问题,因此检测到密码并不是最后一步,修复才是最后一步。我们把Gold-digger集成到公司研发环境CI/CD管道中,方便开发人员根据密码报告及时修复漏洞,从源头遏制风险。当开发人员向公共存储库提交代码后,Gold-digger会进行on-push扫描,对包含密码的提交进行拦截和告警。此外,我们还允许开发人员在本地使用Gold-digger,进行pre-commit或pre-push检查,整体风险左移。5.2 数据对比
我们使用Gold-digger与最先进的开源工具进行了对比。我们对内部服务代码进行了分析,将人工安全审计发现的密码作为测试基准,使用工具对代码进行测试并统计结果。下表结果显示,我们的工具准确率和召回率高于其他所有开源工具,误报率和漏报率低于其他所有开源工具。
工具 | 准确率 | 召回率 | 误报率 | 漏报率 |
Gitleaks | 45.45% | 7.75% | 54.55% | 92.25% |
Git-Secrets | NAN | 0.00% | NAN | 100% |
Whispers | 40.00% | 26.36% | 60% | 73.64% |
Gittyleaks | 4.43% | 34.11% | 95.57% | 65.89% |
Detect-secrets | 87.36% | 58.91% | 12.64% | 41.09% |
TruffleHog | 25.93% | 5.43% | 74.07% | 94.57% |
Gold-digger | 98.84% | 65.89% | 1.16% | 34.11% |
分析显示,Gold-digger检测的密码中大部分是通过通用正则表达式和熵字符串检测获得的。这是由于内部代码中包含的密码大多无明显前缀后缀特征,特定平台表达式检测不到。正因如此,大部分工具尽管定义了大量的特定平台的正则表达式但漏报率仍很高,例如trufflehog定义了700多种特定平台正则表达式,但通用正则表达式种类较少,故对特定平台表达式未覆盖到部分的检测能力较弱。Gold-digger可以利用通用正则表达式和熵字符串检测进行弥补,有效降低漏报。
密码检测的一大难点是避免来自非密码字符串的误报。Gold-digger通过多种启发式规则的过滤得到了较低的误报率。其他工具大量误报的主要原因则是正则表达式的匹配范围太宽泛又缺乏有效过滤手段,例如Gitleaks通过通用正则表达式识别到大量密码候选值,但其中既有真正的密码,又有appkey name、间接引用等,但未进行筛除。
总结
随着互联网组织架构的高速发展和软件发布周期的不断缩短,硬编码密码问题在企业代码仓库中日益严重,其危害已通过多起严重安全事故显示出来。硬编码密码的大规模治理必需由安全人员和研发人员共同合作。美团为保障研发安全,设计了具有编程语言无关、模块化架构、集成在CI/CD管道等特点的硬编码密码的扫描工具Gold-digger。该工具的效果优于目前所有开源工具,能够有效帮助开发人员尽早发现并修复密码泄露,从源头保障研发安全。
参考文献[1] https://www.securitymagazine.com/articles/94776-over-two-million-corporate-secrets-detected-on-public-github-in-2020[2]https://www.gitguardian.com/state-of-secrets-sprawl-report-2022[3]https://blog.gitguardian.com/a-practical-guide-to-prioritize-and-remediate-thousands-of-secrets-leaks-incidents/[4]https://blog.gitguardian.com/codecov-supply-chain-breach/?utm_medium=pdf&utm_campaign=the-state-of-secrets-sprawl-2022[5]https://docs.shiftleft.io/ngsast/analyzing-applications/secrets[6]https://www.ndss-symposium.org/wp-content/uploads/2019/02/ndss2019_04B-3_Meli_paper.pdf[7]https://www.uber.com/newsroom/statement-update/[8]https://hackerone.com/reports/716292[9]https://blog.gitguardian.com/united-nations-databreach-jan/[10]https://blog.twitter.com/official/en_us/topics/company/2018/keeping-your-account-secure.html[11]https://www.zdnet.com/article/github-says-bug-exposed-account-passwords/[12]https://www.uber.com/newsroom/2016-data-incident/[13]https://www.secpulse.com/archives/172585.html[14]https://github.com/trufflesecurity/trufflehog[15]http://www.ueltschi.org/teaching/chapShannon.pdf