我将讨论代码审查热点的(不是新颖的)概念。热点是可能包含漏洞的代码部分。它们不适合自动报告,因此安全工程师应该手动查看它们。我将定义我所说的热点;我会找到一些 Semgrep 的例子;最后,我将展示我如何收集这些规则。
什么是热点?
在这种情况下,热点是可能包含安全漏洞的代码部分。您并不是“总是”在寻找特定的问题,而是在寻找不良做法、常见错误、不安全的配置,简而言之,寻找通常会发生坏事的地方。
审查现代软件项目中的每一行代码是不可能的。因此,我们搜索一个心理或书面的关键字列表,如SSLv3
、MD5
、memcpy
或encrypt/decrypt
。我们不确定我们会找到什么,但这些是开始寻找错误的好地方。
静态分析规则的类型
您(作为安全工程师)应该有两组独立的以安全为中心的静态分析规则:
security
: 对于开发者。hotspots
: 对于安全工程师。
安全规则
security
规则检测特定的漏洞(例如,log4j
)。理想情况下,它们应该是轻量级的并且返回零误报。我应该使开发人员能够自信地在他们的工作流程(例如 CI/CD 管道或编辑器)中部署我的规则,并相信我不会浪费他们的时间。如果不是,他们将不再信任我并丢弃(或规避)应用程序安全设备。我也会这样做。
热点规则
Hotspots
是给我的。我想找到代码中容易出错的部分。我通常可以通过快速审查来丢弃误报。这些规则应该很吵,但不要花太多时间减少噪音。
现有文献综述
我不是在提出一个新颖的想法。我是从别人那里学到的。
每个人都有一个热点列表
每个安全工程师都有一个随时间积累的个人(心理或书面)关键字列表。 .NET Interesting Keywords to Find Deserialization Issues by irsdl就是一个很好的例子。您可以通过快速搜索找到类似的列表。收集这些很有趣。
硬编码秘密探测器
硬编码的秘密是热点。有大量的产品和正则表达式来查找 API 密钥、密码和加密密钥。结果通常具有较高的误报率,需要人工审核。
Semgrep 规则中的审计类别
Semgrep规则存储库将它们称为audit
规则并将它们存储在security/audit
. 您可以使用p/security-audit策略运行它们 。
如果安全规则不鼓励使用不良模式(例如格式化的 SQL 字符串),我们建议将审计附加到您的命名空间。这将其与专门用于检测漏洞的安全规则区分开来。
Semgrep 规则存储库中的审计不应受到安全保护
Semgrep 向前冲。
你呢,帕西娅?
TL;DR:运行规则下security
也会运行嘈杂audit
。 security
我认为应该是单独的audit
类别。您可以通过使用策略来避免此问题,但这仍然是本地规则的问题。
Semgrep 可以将本地规则与--config /path/to/rules/
. 它将运行路径和任何子目录中的每个规则。所以, --config semgrep-rules/python/lang/security
也将运行规则 python/lang/security/audit
我们可以直接使用注册表,无需下载规则。 --config r/python.lang.security
将运行注册表中的所有规则, /python/lang/security
包括audit
.
这种行为并不理想。audit
规则在设计上是嘈杂的。我以不同的方式组织了我们的内部 Semgrep 规则。例如,我们有python.lang.security
和 python.lang.hotspots
。我可以将规则传递security
给开发人员并为我们自己保留噪音hotspots
。
微软应用检查器
Microsoft Application Inspector是一个“代码中的内容”工具。它具有内置功能,例如cryptography
或authentication
。每个规则都属于一个特性,并包含一个关键字/正则表达式列表。如果一条规则受到影响,最终报告将包含该功能。例如,如果代码具有md5
应用程序具有cryptography
.
我使用 Application Inspector 和DevSkim(一个使用相同规则格式的 IDE linter)玩了几个星期,但我认为它们不适合我。Application Inspector 旨在呈现功能(例如,此应用程序具有 authentication
),但我对导航和查看结果很感兴趣。
费利克斯的韦格利 - 乔纳森和乔迪的“与韦格利一起玩”
几天前,我正在查看weggli中的一些 C++ 规则。是来自 Google Project Zero的Felix Wilhelmweggli
的 C/C++ 静态分析工具。weglli 和 Semgrep 使用相同的解析器 ( Tree-Sitter ) 并具有相似的规则模式。自述文件有一个示例列表,我将一些示例移植到 Semgrep。
我还发现了 Julien Voisin 和 Jordy (Oblivion)的Playing with Weggli。他们在 Linux 代码库上运行了一些自定义的 weggli 规则。该博客为我提供了 Semgrep 规则的想法(请参阅 稍后讨论的规则)。sizeof(*ptr)
谢谢,菲利克斯、乔纳森和乔迪!
不同类型的热点
我为热点创建了一个简单的类别。我将定义每一个并讨论示例。
- 不安全的配置:具有易受攻击配置的(通常是第 3 方)组件。
- 危险功能:使用这些功能通常是一个安全问题。
- 危险模式:使用不安全的安全方法和结构。
- 有趣的关键字:变量/类/方法名称和注释中的特定术语。
1. 不安全的配置
框架、库或基础架构的配置不安全。我们通常可以通过某些保留关键字的存在(或遗漏)来发现不安全的配置。这些配置可以在代码或配置文件中(d'oh)。
Go 中的 TLSv1 支持
寻找VersionTLS10和VersionSSL30
1在 Go 代码中查看对 TLSv1.0 或 SSLv3 的支持。使用这个简单的 Semgrep 规则 ( https://semgrep.dev/s/parsiya:blog-2022-03-go-tlsv1 ) 来查找这些热点,甚至自动 修复它们。
Semgrep 注册表中有类似的规则: https ://semgrep.dev/r?q=go-stdlib.disallow-old-tls-versions 。
在 Go 中跳过证书验证
我们可以禁用 TLS 证书2使用InsecureSkipVerify签入 Go 。这很糟糕,但不一定是问题。我们可能正在处理没有有效证书的内部端点3.
如果InsecureSkipVerify
为真,我们可以使用可选的 VerifyPeerCertificate回调来进行我们自己的检查。最后一个是为所有连接执行的验证连接,它可以终止 TLS 握手。
另一个用于查找所有三个关键字的简单 Semgrep 规则: https ://semgrep.dev/s/parsiya:blog-2022-03-go-cert-check 。
位于https://semgrep.dev/r?q=go-stdlib.bypass-tls-verification的 Semgrep 注册表中的规则利用 Semgrep 中的 Sem(antic) 并查找类似 tls.Config{..., InsecureSkipVerify: true, ...}
.
Java中的外部实体注入
Java 因外部实体注入 (XXE) 问题而臭名昭著。大多数 XML 解析库没有安全的默认值。我们使用硬编码的字符串和语言常量来查找它们。例如,DocumentBuilderFactory
。
现有的 Semgrep 规则在消除误报方面做得不错,但不可能找到所有内容。热点规则的时间更容易,并且可以标记所有这些规则以供手动审查。我使用了 OWASP XML 外部实体预防备忘单来组成一个列表(警告:很多噪音):https ://semgrep.dev/s/parsiya:blog-2022-03-java-xxe 。
Dockerfile 中的安全问题
dockerfiles 本质上是配置文件。容器用途广泛。我们可以自取其辱(注意 C++!一个新的竞争者就在这里)。我们的热点规则可以查找 它是否以 root 身份运行?或 源未固定。
几乎所有的云、k8s 和类似的配置问题都属于这一类。 找出配置如何不安全,将它们各自的关键字添加到您的规则中并在所有内容上运行它们。
2. 危险功能
每种编程语言、框架和库都有危险的功能。 但是,它们的存在不一定是漏洞。你可以说我们不应该使用这些危险的函数,我同意,但删除它们并不总是可行的,尤其是在遗留代码中。
MD5
MD5
是一个密码破译的哈希函数(强调“密码”)。也就是说,我们不能报告每个实例。在某些情况下使用MD5
完全没问题。我在现实世界中看到了一些安全的例子:
- 自定义内容管理系统(例如博客)使用 MD5 为图像创建标识符。如果您可以编辑博客文章并添加具有相同哈希值的不同图像,您可以做坏事并覆盖前一个。这是没用的,因为您可以使用您的访问权限删除原始图像。
- 从 20 位数字用户 ID 生成数据库索引。ID 必须是有效数字。据我所知,不可能用两个数字生成 MD5 冲突(准备被证明是错误的)。
标记MD5
是下意识的反应。也许您会创建一个票证并要求开发人员将其更改为 SHA-256 “确定”。请记住,如果要求开发人员在没有合理漏洞的情况下花费周期,您的声誉将受到打击。
“不安全的随机数”java.lang.Math.random
类似。它们可以在非加密环境中使用。关于不使用 CSPRNG(密码安全伪随机数生成器)的“每日提示”模块的票是愚蠢的。
C 中的 sizeof(*ptr)
使用sizeof(pointer)
而不是实际的对象类型是 C/C++ 中的常见错误。在此示例中,我们使用memcpy(dst, src, sizeof(char*))
的会导致经典的缓冲区溢出。sizeof(char*)
通常是 4 (x86) 或 8 (x64) 字节,而sizeof(char)
是 1。
#include <stdio.h>
#include <string.h>
int main() {
char dst[20];
char* src = "hello hello";
// seg fault - sizeof(char*) == 8
memcpy(dst, src, strlen(src)*sizeof(char*));
// sizeof(char): 1 - sizeof(char*): 8 - sizeof(source): 8
// printf("sizeof(char): %lu - sizeof(char*): %lu - sizeof(src): %lu\n",
// sizeof(char), sizeof(char*), sizeof(src));
}
有趣的是,memcpy(dst, src, sizeof(src))
我们得到一个警告:
warning: 'memcpy' call operates on objects of type 'char' while the size is
based on a different type 'char *'
[-Wsizeof-pointer-memaccess]
memcpy(dst, src, sizeof(src));
我创建了一个规则来查找sizeof($TYPE*)
代码中的所有内容pattern-regex
。这也将搜索评论。我们可以减少误报 pattern-not-regex
。尝试扩展 https://semgrep.dev/s/parsiya:blog-2022-03-sizeof-ptr。
Go中的文本/模板
Go 的标准库提供了两个模板包。 html/template做一些输出编码,而 text/template不做。在 Web 应用程序中使用text/template
可能会导致 XSS。我们应该审查查找和审查text/template
进口。
Semgrep 注册表对此问题有一个审计规则。我正在从 autofix 博客中回收我的类似规则: https ://semgrep.dev/s/parsiya:go-import-text-template-fix 。
Go 中的不安全
我尝试一个非原创的编程语言笑话:
在每一种安全编程语言的标准库下都有一堆不安全的东西。
Go 和 Rust 被认为是安全的编程语言,但它们都允许我们unsafe
通过Go 的 unsafe 包和 Rust 的 unsafe 关键字来使用。
我们应该标记所有不安全的东西吗?取决于行业。我不。名望开发者喜欢使用巧妙的技巧。找到这些实例很容易。import "unsafe"
在 Go 和unsafe
Rust中寻找。Go 的示例规则(Semgrep 不支持 Rust,但 Rust 已经是安全的 :p): https ://semgrep.dev/s/parsiya:blog-2022-03-go-unsafe 。
3. 危险模式
危险的模式通常会导致安全漏洞。将它们视为“对通常安全的方法的不安全使用”。
Java中格式化的SQL字符串
Semgrep 注册表有一个规则,看起来很吓人,但它只是试图找到作为 SQL 查询执行的连接字符串。
如果您有时间,请标记并查看每个 SQL 查询。exec
(和类似的)命令也是不错的选择。我们想要审查它们并检查攻击者是否可以影响他们的输入并获得命令注入。
PHP 中 openssl_decrypt 的返回值
我最近遇到了这个问题。openssl_decrypt 是 PHP 中的一个安全函数,成功时返回解密字符串,失败时返回false
。如果我们不检查这种极端情况,我们可能会遇到漏洞。openssl-decrypt-validate Semgrep 规则标记这些 情况以供审查:
硬编码的秘密
假设您将 AES 密钥存储在源代码或配置文件中。这是一种危险的模式。AES 是安全的,不是一个危险的功能,但你削弱了它,因为现在每个有权访问代码的人都可以破解你的加密。
在密码散列方案中使用静态盐是相同的。你已经削弱了你的(希望是)安全算法。
4. 有趣的关键词
查找特定的变量/方法/类名称和注释。这些不是语言关键字,而是上下文概念(wut?!)。
您是否曾经在代码库中搜索过password
密码是如何处理的?它们可能存储在代码注释中名为password,
passwrd , or another variation. What about searching for
TODO or
security` 的变量中?
名称中包含 Encode 和 Decode 的函数
weggli有一个例子来查找函数decode
名称中的函数。我想查看任何名称中包含encode
和decode
名称的函数。 encrypt/decrypt
是另一个不错的选择。这些函数可能会增加我们的攻击面,因为我们正在处理两种不同的格式。解析器错误很有趣!
https://semgrep.dev/s/parsiya:blog-2022-03-encode-decode-function-name上的 Semgrep 规则 很容易创建(伙计,我喜欢 Semgrep)。我们捕获元变量中的所有函数 $FUNC(...)
,然后用于metavariable-regex
过滤它们。
错误和功能跟踪代码
错误通常在代码注释中提及。例如,如果我修复票证, BUG-1234
我会在代码中的该位置添加注释以及一些其他信息。新功能或合并/拉取请求也是如此。在代码中搜索这些模式以查找功能、已修复的错误、现有错误、解决方法 ( // BUG-234: hacky way of bypassing a security guardrail!
) 和其他有趣的东西。
在WSL Remote 扩展中的幸运 RCE 期间,我 在VS Code 服务器代码 中找到了对 CVE 的引用。
CVE-2021-1416的页面没有太多信息。该代码讲述了一个更好的故事。VS Code 服务器将从 node_modules
Windows 上的特定路径加载代码。如果攻击者可以将他们自己的 Node 模块放在这些路径中,他们就可以实现 RCE。为什么 Azure Storage Explorer
还要运行这段代码?!
我们可以搜索诸如CVE*
、BUG-[number]
和CL[number]
(CL 代表Change List
perforce,相当于 git commit)之类的项目。
我如何收集这些?
我已经解释了我的例子来自哪里。让我们列个清单:
- 静态分析规则
- 编码标准
- 文档
- 其他错误
- 经验
1. 静态分析规则
浏览不同语言和工具的静态分析规则。我浏览了 Semgrep 的audit
规则和 weggli 示例。查看 GitHub 安全实验室的 CodeQL 查询了解更多信息。虽然无法在 Semgrep 中复制某些 CodeQL 规则,但可以提取关键字以供人工审核。
为什么选择 Semgrep 而不是 CodeQL?简短的回答是 CodeQL 很好,但对我不起作用。
您甚至可以使用其他语言的模式并将它们调整为您的目标。我们刚刚在 Java 中看到了 XXE,但它也发生在其他语言中。搜索 xml + other-language
并查看您能找到什么。
Microsoft Application Inspector和 DevSkim规则中的关键字很有用。
2. 编码标准
编程语言和开发团队通常有自己的编码标准。一些函数和库被禁止;一些模式被积极劝阻。将这些添加到您的列表中。
您可以找到遗留代码、一次性异常(“嘿,我可以在这里使用一次 memcpy 吗?”)以及代码审查中遗漏的项目。
3. 文档
阅读和编写文档是一种很好的学习方式。
有时编程语言在in large, friendly letters
.
一个很好的例子(感谢我的朋友Tim Michaud)是 PHP 的 unserialize。文档提到:
警告:无论选项值如何,都不要将不受信任的用户输入传递给unserialize()
allowed_classes
。
在某些时候,开发人员停止尝试玩带有错误的 Whac-A-Mole 并说出类似的话。其他人不屈服并试图阻止模式(例如, Jackson 序列化小工具)。就个人而言,我认为这是一场失败的战斗:
故意不安全的系统是不安全的。如果您在不安全的路径中安装游戏,我们无能为力。
每个云提供商、库、框架和操作系统都有官方和非官方的安全指南。阅读它们并将项目添加到您的列表中。
OWASP 安全检查表和备忘单也是很好的资源。上面的 Java XXE 规则是从 OWASP XXE 备忘单编译而来的。
与开发人员交谈并了解他们的威胁模型。他们比你更了解代码。我的一些最好的错误来自这些对话。“什么让你彻夜难眠?”、“什么数据最重要?”和“希望什么更安全?” 是好问题。
4. 其他错误
研究错误。很少有公共错误伴随源代码,但 开源拉/合并请求很棒。识别易受攻击的模式并创建规则。不要花很多时间试图清除误报。我们的目标是找到热点。有时仅仅标记一个特定的功能就足够了。
阅读其他工程师编写的内部安全漏洞。查看外部安全研究人员向您的组织披露的错误。获取代码/配置并尝试执行根本原因分析。找出易受攻击的部分。这有两个用途:
- 您会发现新模式并了解更多信息。
- 您可以寻找变体。针对您的代码库的其余部分运行该模式。
票号和注释也是不错的起点。正如我们上面讨论的,在代码中查找票号BUG-1234
、注释 ( CVE-2021-1234
) 和其他项目。
5.经验
这里没什么好讨论的,但是多年来你开始在代码中看到东西。如果您的直觉告诉您有问题,请将其添加到您的列表中。定期修剪它。
该列表不必很复杂。encode/decode
, authentication/authN
, authorization/authZ
, encrypt/decrypt
, 或之类的东西hash/hmac
是很好的选择。我们已经看到了 .NET Interesting Keywords to Find Deserialization Issues列表。查找这些列表。
我们今天在这里学到了什么?
我介绍了hotspots in source code
. 这些位置可能包含漏洞,但应手动检查。结果是嘈杂的,因此热点规则不适合 CI/CD 管道和自动警报。
热点的主要受众是安全工程师。我们不得不依靠静态分析工具来审查数百万行代码。我们这里的方法不是科学的,而是整体的。我们依靠直觉和人工分析。这通常不是机器可以做的事情(我相信 Semmle 人不同意)。
我浏览了这个概念的现有实例并尝试创建一个轻量级分类。我们讨论了每个类别的示例和 Semgrep 规则。上一节解释了我如何收集想法和样本以找到更多热点。
如果您有任何反馈,您知道在哪里可以找到我。我整个星期都在这里。