如何在代码中查找漏洞是一个非常复杂的问题,如果高屋建瓴地看,这个查找漏洞的过程应该是以下这样的:
- 找到危险的功能;
- 找到从你控制的输入到危险功能的路径;
- 向程序输入错误的行为;
先让我们看看如何找到危险的功能,根据经验,80%的错误都在大约20%的代码中。由于通常需要彻底理解代码才能发现新的漏洞,因此决定重点关注哪20%至关重要。根据我的经验,哪里的“异常词汇(Bad Word)”频率最多,哪里能找出的漏洞机会就越大。
本文我将通过一个简短的故事向你展示寻找漏洞的思路。Terraform 是一个安全和高效的用来构建、更改和合并基础架构的工具。采用 Go 语言开发。Terraform 可管理已有的流行的服务,并提供自定义解决方案。最近在一次渗透测试工作中,当我在查看一份大段的Terraform 材料时,无意中看到了这行代码:
- driver.raw_exec.enable = 1
原本我认为在这段代码中是没有找到漏洞的可能,但当以上那行奇怪的代码出现时。我不得不停下来弄清楚它们是什么意思负责什么操作。原来,它正在配置Hashicorp的一个名为Nomad的操作调度程序。HashiCorp是由Mitchell Hashimoto和Armon Dadgar联合创办,总部位于美国旧金山,致力于为企业提供服务,通过数据中心管理技术研发,让开发者通过工具构建完整的开发环境,提高开发效率。Nomad 是一个集群管理器和调度器,专为微服务和批量处理工作流设计。
Nomad 是分布式,高可用,可扩展到跨数据中心和区域的数千个节点。Nomad 提供一个常规工作流跨基础设施部署应用。开发者使用一个声明式操作规范来定义应用该如何部署,资源有什么要求(CPU,内存,硬盘)。Nomad 接收这些操作,查找可用的资源来运行应用。调度算法确保所有的约束都满足,尽量在一个主机部署尽可能多的应用,优化资源利用。
和其他渗透测试人员一样,我立即对Nomad进行了了解。果然,我查出了这行代码的蹊跷之处,官方文档是这样解释其作用的:“这使你可以无隔离地运行操作,出于安全原因,默认情况下将其禁用。”
当我迅速完成我可以构建的最简单的Nomad模块时,我高兴坏了,几分钟后,我就可以以root身份访问该模块,并且获取大量凭证。在此之前,我从来没有听说过Nomad,更不用说这个配置选项了,那么是什么让我想要深入挖掘呢?它是徘徊在同一段代码上的两个安全异常词汇的组合,即“raw”和“exec”。这些异常词汇可以帮助你找到代码中最关键的安全部分,这样你就可以将你的注意力放在最重要的地方了。
常见的异常词汇
1. raw
Raw意味着你正在访问较低级别的抽象,当你的安全控制在更高的级别执行时,允许此“raw”界面的用户绕过它们,这就会成为一个漏洞。
示例:
- CAP_NET_RAW是一种Linux功能,允许你创建原始套接字,并使用它们来绕过典型的进程隔离限制。
- Nomad中的raw_exec驱动程序使你可以创建具有Nomad代理权限的,在容器外部运行的操作。
- 许多ORM都具备rawQuery或rawSQL方法,可让你直接执行查询。 ORM生成的查询通常是不可注入的,但是在使用“raw” 界面时,如何组织SQLi由用户决定。
2. eval | exec | run
将用户输入与用动态语言(Javascript、SQL、bash等)编写的代码相结合通常会导致注入攻击,攻击者可以提交代码作为输入内容,从而导致解释器的行为异常。运行此代码通常被称为“执行”,“评估”或“运行”。
示例:
- 无隔离状态下,raw_exec运行一个Nomad操作;
- execute(sql)在许多python数据库驱动程序中运行sql查询;
- exec(code)是一个python方法,它运行传递给它的代码;
- eval(code)是许多动态语言提供的函数,比如运行传递给它的代码的Javascript,Python也有一个eval函数,但它只用于表达式;
这个函数将返回大量的假阳性结果,因为正如Steve Yegge预测的那样,似乎每个动词都通过run()、execute()或justDoIt()方法变成了名词。
3. process | system | popen | exec | spawn
这些词可以指示子进程的创建,如果子进程生成了shell,则可以注入shell命令。即使它直接调用execve系统调用,你仍然可以向程序添加或修改参数。
示例:
- python中的子进程模块;
- 节点中的child_process模块;
- golang中的os / exec程序包;
- python中的os.system方法;
- ruby中的popen模块;
4. privilege | permission | capability | role | rbac | policy | authorization | claims
这些词将帮助你找到负责向用户、容器、进程、文件、EC2实例等授予特权的代码,使用任何高度特权的对象来操作你的命令,甚至完全绕过authz。
示例:
- docker --privileged标志为主机提供了容器功能的root特权;
- linux内核将root用户权限划分为“功能”,你可以将其分配给程序,从而允许它执行创建原始套接字,调试你不拥有的进程或绕过文件ACL之类的操作。
- Kubernetes使用一个称为RBAC(基于角色的访问控制)的api扩展来授权对k8s资源的访问,k8s全称kubernetes,这个名字大家应该都不陌生,k8s是为容器服务而生的一个可移植容器的编排管理工具,越来越多的公司正在拥抱k8s,并且当前k8s已经主导了云业务流程,推动了微服务架构等热门技术的普及和落地,正在如火如荼的发展。
- 许多云提供商使用术语“role binding”向主体授予一组权限;
- JWT具有向用户告知用户特权的“claims”,并且用户使用jwt.ParseWithClaims之类的功能对其进行验证。
5. reflect | klass | constantize | forName
许多编程语言都允许你通过函数名称,类,方法,变量等来查找它们(甚至实例化/调用它们),这通常称为“反射”。如果用户可以控制要调用的方法的名称或要返回的变量的名称,则可能会导致程序行为异常。
示例:
- Javascript中的Reflect对象;
- ruby String#constantize方法;
- java Class.forName方法;
- klass是通过反射查找的类的常见变量名(因为“class”往往是一个保留词汇);
6. pickle | yaml | serialize | marshal | objectinput
这些词表示程序可能正在使用支持复杂对象的格式对数据进行反序列化,这可能使攻击者可以读取文件、发送HTTP请求甚至执行任意代码,具体取决于序列化格式以及运行时可用的对象(JVM类路径上的类,python中sys.path上的包等)。
示例:
- python的pickle格式;
- node-serialize包;
- 大多数YAML解析器;
- Java的ObjectInputStream ;
- php的反序列化功能;
7. parse | open | request
这些词汇之所以有趣,原因与eval()及其类似的函数一样,攻击者可以输入解析器所识别的元字符,以改变解析器的行为。主要区别在于,你不是用动态语言运行代码,而是利用解析器访问文件或URL等资源。
示例:
- 控制URL解析器的输入可能会导致SSRF、绕过代理限制、非斜杠格式等等;
- 控制文件路径解析器的输入可能导致LFI,RFI和本地文件读/写;
8. unsafe | insecure | dangerous
有时候,API开发人员喜欢通过在名称中包含“insecure” 或“unsafe”来提醒人们注意危险的API。
示例:
- Rust中unsafe {};
- Go语言编写的TLS套件中的InsecureSkipVerify;
- React中dangerouslySetInnerHtml(),React主要用于构建UI。你可以在React里传递多种类型的参数,如声明代码,帮助你渲染出UI、也可以是静态的HTML DOM元素、也可以传递动态变量、甚至是可交互的应用组件;
- Go中不安全的程序包;
9. todo | fixme | xxx
随着代码的发展,开发人员会添加注释,以提醒自己实现功能、修复漏洞或清理一些自己不喜欢的代码。有时,这些注释可能会导致你发现重要的漏洞、丢失的功能等,你可以利用它们。
示例:
- 有一次,我在Apache服务器的Web根目录中找到一个todos.txt文件,它包含了一长串未修补的安全漏洞。
- 还有一次,我发现一个FIXME注释提到了一个功能问题。事实证明,这是一个非常难以发现的漏洞,但利用ReDoS漏洞却是微不足道的。
10. merge | clone
这些词通常表示一个object, dict, map等正在与另一个对象合并或克隆到新对象中,这可能会导致一些有趣的安全问题,例如Javascript原型污染漏洞、大规模分配漏洞等。
示例:
- LoDash中的_.merge;
- LoDash中的_.clone;
11. alloc | free
这是一个很好的线索,说明正在进行手动内存管理。众所周知,这很难解决,并且可能导致诸如缓冲区溢出、释放后使用、双重释放等漏洞。
示例:
- malloc ();
- free ();
- Objective C中的[object alloc] 消息;
12. AES | RSA | DSA | DES | CBC | ECB | HMAC | GCM
这些是加密原语,可以表明作者在使用他们自己的加密系统,而不是使用更高级别的抽象。有许多微妙的方法可以不安全地使用它们,所以请仔细阅读并咨询密码学家。
示例:
- aes.NewCipher(密钥);
- 新的RSAPrivateKey(keyBytes);
- HMAC.new(secret,digestmod = SHA256);
13. JWT | JKS | JWK | JKU …
JSON Web令牌是安全传输数据的标准,在现代应用程序堆栈中非常常用,有很多不安全地使用它们的方法,因此值得关注处理JWT的代码。
常见的JWT问题:
- 无算法;
- 操纵alg标头;
- 不验证aud或 iss claims;
- 未验证有效期(exp和nbf claims)
- 签名但不加密敏感数据;
示例:
- JWTVerifier;
- jwt.ParseWithClaims;
- jwt.verify;
14. password | private | token | secret | key | Authorization
这些词汇很好地说明了你可能有一些硬编码到存储库中的密钥,如API密钥、数据库密码、加密密钥等。
示例:
- BEGIN RSA PRIVATE KEY;
- AWS’s “secret access key”;
- Django的SECRET_KEY设置;
15. validate | verify
这些词汇通常表示正在执行业务/安全规则,不过请仔细检查这些内容,以获取通过验证的输入内容,因为这也可能导致漏洞。他们试图禁止的输入类型也可以为你提供有关潜在漏洞的线索。
示例:
16. XML | xerces | SAX | etree | xpath | DocumentBuilder
解析攻击者控制的XML可能会导致诸如本地文件读取以及拒绝服务攻击等一系列安全问题。
示例:
- DocumentBuilderFactory.newInstance();
- SAXParserFactory.newInstance();
- xml.etree.elementtree;