手把手教你怎样找代码漏洞

  • 2020-11-17
  • 本文字数:3579 字

    阅读完需:约 12 分钟

本文是关于如何查找代码漏洞文章系列的第一篇。从宏观来看,过程是这个样子:

  • 找到潜藏危险的功能

  • 找到从你控制的输入到这个危险功能的路径

  • 修正会导致程序出问题的输入

我们将从第一部分开始:如何找到潜藏危险的功能。以我的经验,80%的错误都藏在大约 20%的代码中。具体来说,这个比例更接近 90/10。由于我们通常需要彻底理解代码才能发现罕见的漏洞,因此找出需要重点关注的那 20%就变得至关重要。我使用的一种方法是关注“坏词(bad words)”的群集。

我将通过一个简短的故事告诉大家。最近在一次红队行动中,我浏览一个巨大的 terraform 仓库时看到一行看起来很正常的代码:

driver.raw_exec.enable = 1
复制代码

这个时候,我甚至都没有在尝试寻找漏洞,但是看到这行代码后,我就停下来弄清楚它在做什么。原来,它正在配置 Hashicorp 一个名为 Nomad 的作业调度程序。作为一名尽责的红队队员,我立刻查询了 Nomad 文档。在那里,我发现了一个不祥警告:

这让你可以无隔离地运行作业;出于安全原因,默认情况下将其禁用。

我马上精神了,迅速做出一个最简单的 Nomad 作业。几分钟后,我就有了对该集群的 root 访问权限和一大堆凭证。这次行动马上就可以完成了。

在此之前,我甚至从未听说过 Nomad,更不用说这个配置选项了。那么这行代码到底有什么特殊引起了我的关注,让我想要深入研究下去呢?其实是因为两个安全坏词的组合,它们流连在同一代码段,分别是“raw”和“exec”。这些坏词可以帮助你筛选出代码中最关键的安全部分,让你能高效地利用你的注意力。

常见的坏词(bad words)

raw

Raw 表示你正在访问较低级别的抽象。当在较高级别上实施安全控制时,这将成为一个问题,让这个“raw”界面的用户可以绕过它们。

例子:

  • CAP_NET_RAW 是一个 Linux 功能,允许你创建 raw socket,并使用它们来绕过典型的进程隔离限制。

  • Nomad 中的raw_exec驱动程序允许你创建在容器外部运行的作业,拥有 nomad 代理的许可。

  • 许多 ORM 都有rawQueryrawSQL方法,让你可以直接执行查询。ORM 生成的查询通常是不可注入的,但是要由用户决定使用“raw”界面时是否阻止 SQLi。

eval|exec|run

一般来说,将用户输入与用动态语言(JavaScript、SQL、bash 等)编写的代码相结合,通常是注入攻击的好途径。攻击者可以提交代码作为输入,从而导致解释器执行不当行为。运行这种代码通常称为“executing”、“evaluating”或“running”。

例子:

  • raw_exec无隔离地运行 Nomad 作业

  • conn.cursor().execute(sql)在许多 python 数据库驱动程序中运行 SQL 查询

  • exec(code)是运行传递给它的代码的 python 方法

  • eval(code)是许多动态语言提供的一个函数,例如 JavaScript,可以用它运行你传递的代码。Python 也有一个 eval 函数,但仅用于表达式。

*这将返回很多误报,因为正如 Steve Yegge 预测的那样,似乎每个动词都通过run()execute()justDoIt()方法变成了一个名词。

process|system|popen|exec|spawn

这些词可以用来指示子进程的创建。如果子进程生成了一个外壳,那么你就可能注入外壳命令。即使它直接调用 execve syscall,你仍然可以在程序中添加/修改参数。

例子:

  • python 中的subprocess模块

  • node 中的child_process模块

  • golang 中的os/exec

  • python 中的os.system方法

  • ruby 中的popen模块

privilege|permission|capability|role|rbac|policy|authorization|claims

这些单词可以帮助你找到负责向用户、容器、进程、文件、EC2 实例等授予特权的代码。你可以使用任何拥有高特权的实体来下手,甚至完全绕过 authz。

例子:

  • docker 的--privileged 标志为主机提供了容器的可用 root 特权。

  • linux 内核将 root 用户权限划分为“capabilities”,你可以将其分配给一个程序,从而允许它执行创建 raw socket、调试你不控制的进程或绕过文件 ACL 之类的操作。

  • Kubernetes 使用一个称为 RBAC(基于角色的访问控制)的 api 扩展来授权对 K8s 资源的访问。

  • 许多云提供商使用术语“role binding”向一个主体授予一组权限。

  • JWT 拥有“claims”,可以告知消费者用户的特权,并且消费者可以使用jwt.ParseWithClaims之类的函数来验证它们。

reflect|klass|constantize|forName

许多编程语言都允许你通过名称来查找函、类、方法、变量等(甚至实例化/调用它们)。这通常称为“reflection”。如果用户可以控制要调用方法的名称或要返回变量的名称,则可能会导致程序行为异常。

例子:

  • JavaScript 中的Reflect对象

  • ruby 中的String#constantize方法

  • java 的Class.forName方法

  • klass是通过反射查找类的一个通用变量名称(因为“class”往往是保留字)

pickle|yaml|serialize|marshal|objectinput

这些词表示程序可能正在使用支持复杂对象的格式对数据进行反序列化。这可以让攻击者读取文件、发送 HTTP 请求甚至执行任意代码,具体取决于序列化格式以及运行时可用的对象(JVM 类路径上的类、python 中sys.path上的包等)。

例子:

  • python 的 pickle 格式

  • node-serialize 包

  • 大多数 YAML parser

  • Java 的 ObjectInputStream

  • php 的 unserialzie 函数

parse|open|request

这些单词可能很有趣,原因和eval()之类差不多:攻击者可以输入有问题的 parser 识别的元字符来更改其行为。主要区别在于,你不是在动态语言中运行代码,而是利用 parser 来访问文件或 URL 等资源。

例子:

  • 控制 URL parser 的输入可能会导致 SSRF、绕过代理限制、off-by-slash 等问题。

  • 控制文件路径 parser 的输入可能导致 LFI、RFI 和本地文件读/写。

unsafe|insecure|dangerous

有时,API 开发人员喜欢在名称中包含“insecure”或“unsafe”来引起开发人员对危险 API 的关注。

例子:

  • Rust 中的unsafe {}

  • Go 的 TLS 包中的InsecureSkipVerify

  • React 中的dangerouslySetInnerHtml()

  • Go 中的unsafe软件包

todo|fixme|xxx

随着代码的发展,开发人员会添加注释,以提醒自己要实现功能、修复错误或清理一些自己不喜欢的代码。有时,这些注释可能会帮助你发现重要的错误、缺少的功能等,可以利用它们。

例子:

  • 有一次,我在 Apache 服务器的 Web 根目录中找到一个todos.txt文件。它包含一长串未修补的安全漏洞。

  • 还有一次,我发现了一个 FIXME 注释,其中提到了一个性能问题。这个问题很难发现,但是利用它来暴露 ReDoS 漏洞却很简单。

merge|clone

这些词通常表示一个对象、字典、映射等正在与另一个对象合并或克隆到一个新对象中。这可能会导致有趣的安全问题,例如 JavaScript 原型污染漏洞、大规模分配漏洞等。

例子:

  • LoDash 里的_.merge

  • LoDash 里的_.clone

alloc|free

它们是发生手动内存管理的一个很好的线索。众所周知,这很难解决,并且可能导致诸如缓冲区溢出、释放后使用、双重释放等漏洞。

例子:

  • malloc()

  • free()

  • Objective C 中的[object alloc]消息

AES|RSA|DSA|DES|CBC|ECB|HMAC|GCM

这些是加密原语,可以表明作者正在使用自己的密码系统,而不是使用更高级别的抽象。有许多不起眼的细节可以让它们变得不够安全,因此请仔细阅读代码并咨询加密专家。

例子:

  • aes.NewCipher(key)

  • new RSAPrivateKey(keyBytes)

  • HMAC.new(secret, digestmod=SHA256)

JWT|JKS|JWK|JKU……

JSON Web 令牌是安全传输数据的标准,在现代应用程序栈中非常常用。有很多不安全地使用它们的途径,因此值得关注处理 JWT 的代码。

常见的 JWT 问题:

  • none 算法

  • 操纵 alg 标头

  • 不验证 aud 或 iss claim

  • 未验证有效期(exp 和 nbf claim)

  • 签名但不加密敏感数据

例子:

  • JWTVerifier

  • jwt.ParseWithClaims

  • jwt.verify

password|provate|token|secret|key|Authorization

这些词是很好的指示,表明你可能已经将一些秘密硬编码到了存储库中,例如 API 密钥、数据库密码、加密密钥等。

例子:

  • BEGIN RSA PRIVATE KEY

  • AWS 的“secret access key”

  • Django 的 SECRET_KEY 设置

validate|verify

这些词通常表示代码正在执行业务/安全规则。请仔细检查这些内容,以获取通过了验证,但也可能导致漏洞的输入。他们试图禁止的输入类型也可以为你提供有关潜在漏洞的线索。

例子:

app.get('/signup', (req, res) => {    // verify! this probably means that only users with certain    // emails are allowed to sign up. I wonder what it's    // verifying?	if (!verifyEmail(req.body.email)) {    	res.send('unauthorized');        return;    }        register(req.body.email);        res.redirect('/dashboard');});// looks like it's verifying the email belongs to a user// on company.com. can you think of a way to make this return// true without having a @company.com email address?//// what about will@company.com.btlr.dev?function verifyEmail(email) {	return email.includes('@company.com');}
复制代码

XML|xerces|SAX|etree|xpath|DocumentBuilder

解析攻击者控制的 XML 可能会导致许多安全问题,从本地文件读取到拒绝服务攻击等等。

例子:

  • DocumentBuilderFactory.newInstance();

  • SAXParserFactory.newInstance();

  • xml.etree.elementtree

原文链接:

https://btlr.dev/blog/how-to-find-vulnerabilities-in-code-bad-words