前言
在此文中我将讲述我在软件漏洞挖掘的实践中学到的技术及方法,不过这些内容并非那些前沿的技术,大多是基础类型的技术及方法。对于初学者而言,希望能够给予入门的指导,对于经验丰富的漏洞挖掘工作者而言,我认为也可以从中获得一些启发。
受限于我个人的知识水平及能力,这篇文章并不可能做到面面俱到,也希望阅读者能够与我积极交流,对于其中的错误不吝赐教。
我将会把此文分为三个章节,分别阐述我的观点。首先我会从一个较高的角度总结于我眼中何谓漏洞挖掘;然后详细讨论在软件漏洞挖掘过程中我们需要掌握的技能以及需要的知识和工具等;最后我将谈谈一些我认为有利于漏洞挖掘但是却并非纯技术性的想法。
一、什么是漏洞挖掘
从某个角度来讲,我们可以将漏洞挖掘工作比作玩迷宫游戏,不同的是,这个迷宫与我们平时所见的游戏中的迷宫略有不同:
- 你无法立即看到它整体的外观
- 随着漏洞挖掘工作的深入,这个迷宫的形状逐渐扩大
- 你将会拥有多个起点及终点,但是无法确定这些点具体在哪里
- 最终这个迷宫可能永远也无法100%的完整,但是却能够弄清楚A点至B点的一条完整路径
可以用下面这张图来进行描述:
具体一点的描述,我们可以将漏洞挖掘工作归结为三个步骤:
- 枚举程序入口点(例如:与程序交互的接口)
- 思考可能出现的不安全状态(即漏洞)
- 设法使用识别的入口点到达不安全状态
即是说,在这个过程中,迷宫是我们研究的应用程序,地图是我们堆程序的理解程度,起点是我们的入口点(交互接口),终点为程序的不安全状态。
所谓入口点,既可以是UI界面上直观可见的交互接口,也可以是非常模糊与透明的交互接口(例如IPC),以下是部分安全研究员较为感兴趣的关注点:
- 应用程序中比较古老的代码段,并且这一部分随着时间的推移并没有太大的变化。
- 应用程序中用于连接由不同开发团队或者开发者开发的程序模块的接口部分
- 应用程序中那些调试和测试的部分代码,这部分代码本应在形成Release版本时去除,但由于某些原因不小心遗留在程序中。
- C-S模式(带客户端和服务端)的应用中客户端及服务端调用API的差异部分(例如网页表单中的hide属性字段)
- 不受终端用户直接影响的内部请求(如IPC)
我认为从攻击面上来划分可以讲漏洞分为两大类,通用漏洞(General)和上下文漏洞(contextual)。通用型漏洞是指在我们对应用的业务逻辑不是非常熟悉的情况下能够找出的漏洞,例如一些RCE(远程代码执行)、SQLi(sql注入)、XSS(跨站)等。上下文漏洞是指需要在对应用的业务逻辑、认证方式等非常熟悉的情况下才能找到的漏洞,例如权限绕过等。
在漏洞挖掘的过程中,我首先会根据经验优先考虑研究测试那些可能会对应用产生巨大威胁的部分。一些轻量级威胁检测模型(如STRIDE)可以辅助我们做出这样的决策。
下面我们首先看一下一个WEB应用程序的漏洞示例,后面将会介绍桌面程序:
首先我们假设目标web应用是一个单页面应用(single-page-application SPA),我们已经获得合法验证去访问这个应用,但是我们没有任何关于服务端的源代码或者二进制文件。在这种情况下,当我们枚举入口点时,可以通过探寻该应用的不同功能来进一步了解其业务逻辑及功能,可以通过抓包分析看HTTP请求内容,也可以分析客户端的网页代码获取需要提交表单的列表,但是最终的限制还是我们无法具体知悉客户端和服务端调用的API之间的区别,不过通过以上方法,我们可以找到一些入口点,
接着就是操作这些入口点,以试图达到我们预期的不安全状态。由于漏洞的形态很多,我们通常需要构建一个适用于该测试应用程序的业务功能漏洞的测试集,以求达到最高效的寻找漏洞。如果不那样做的话,我们就将会在一些无用的测试集上花费大量时间,并且看不到任何效果(举个例子,当后台的数据库为Postgresql时,我们用xp_cmdshell去测试,测试再多次都无济于事)。所以在构造测试集时,需对应用程序的逻辑有较深的理解。下图形象的展示了低效率测试集的效果:
对于桌面应用程序,漏洞挖掘的思路本质上与web程序是类似的,不过也有一些区别:最大的区别在于,桌面应用的执行方式与流程与web程序不一样,下图展示的是桌面应用漏洞挖掘的一些内容:
与黑盒测试相比,当有源代码时(白盒测试),在寻找代码入口和程序执行路径等漏洞挖掘点时所做的猜测性的工作会大大减少,在这种情况下,将数据载荷从入口点执行到不安全的程序位置的效率低于从不安全的程序位置回溯至入口点。不过在白盒测试中,你对代码的测试的覆盖面可能会由于你自己的知识局限性而受到影响。
二、漏洞挖掘需要具备的知识
从事漏洞挖掘工作需要具备的知识是极其广泛的,并且随着时间在不断改变,也取决于你所研究的对象(web程序、桌面程序、嵌入式等等)。不过,万变不离其宗,所需要掌握的知识领域却总可以认为是确定的,我认为大致可以分为以下四个方面:
1. 程序正向开发技术。这是一个开发者需要掌握的能力,包括编程语言、系统内部设计、设计模式、协议、框架等。拥有丰富编程经验与开发能力的人在漏洞挖掘过程中往往比那些只对安全相关领域有所了解的人员对目标应用能有更深入的理解,从而有更高的产出。
2. 攻防一体的理念。这些知识涵盖了从基本的安全原则到不断变换的漏洞形态及漏洞缓解措施。攻击和防御结合的理念,能够有效帮助研究者既能够发现漏洞,同时也能够快速给出有效的漏洞缓解措施和规避方法。
3. 有效使用工具。能够高效的使用工具能够快速将思路转化为实践,这需要通过花时间去学习如何配置和使用工具,将其应用于自己的任务并构建自己的工作流程来不断积累经验。更进一步,需要深入掌握所使用工具的原理,以及如何对其进行二次开发,以使得其能够更加高效的应用于当前的工作实际。事实上,我认为面向过程的学习方法往往比面向工具的学习方法更加高效以及有价值,当自己发现一个在使用一个工具遇到瓶颈时,先不要退缩,尝试去改造它,或者通过自己动手实践去完成能够适应当前工作的工具,这往往能够帮助快速积累大量实践经验。帮助我们以后更加高效的去实践漏洞挖掘工作。
4. 对目标应用的理解。最后,也是最重要的,作为一个漏洞挖掘人员,对自己研究的应用程序在安全性方面必须要比这个程序的开发者或维护者有更深的理解。这样你才能尽可能的发现这个程序中的漏洞并修复它。
下面这张表格介绍了我认为在挖掘web应用程序和桌面应用程序的漏洞时所需要掌握的内容,限于笔者个人的认知水平,所展示的内容并非特别齐全:
这是我经过无数的不眠之夜、数以千计的小时、一次又一次的错误而总结的知识,我相信它将会帮助你提高你挖掘漏洞的能力。如果说上面一节是讲诉挖掘漏洞所需要的知识,那么下面的这一节将讲述挖掘漏洞如何做。
三、漏洞挖掘需要做什么
当我分析一个应用程序时,我通常采用下图展示的四个“分析模型”,每当我遇到障碍导致我思路受阻时,我就会从其中一个模型切换到下一个模型,当然,这不是一个线性的切换,我不知道这个方法是否对每个人都有用,但是对于我的确是帮助巨大:
在每一个模型之中都有主动活动(active activities)与客观活动存在(passive activities),主动活动需要我们对程序的执行环境及上下文有一个比较全面的了解,而客观活动却不一定,比如它是客观存在与程序的一些技术文档之中。不过,这种划分也不一定严格,不过对于每一个activity,我们可以从以下几方面去考虑:
- 理解有关漏洞的相关模型
- 试图假设一个场景去破坏程序
- 尝试去破坏程序
程序用途分析(Use case analysis)。程序用途分析是指理解一个应用程序做了什么工作,会提供什么服务等。每当我去分析一个新的应用程序时,我所做的第一件事就是程序用途分析。同时,阅读与该程序的相关文档将有助于进一步理解程序功能(如上图所示)。我总是这样做,以期对程序有一个透彻的了解。
或许,比起直接构造测试样例进行测试,这项工作可能并不是那么有趣。不过,它的确帮助我节省了一大笔时间。举一个我自己的例子,我之前发现过Oracle Opera(一个广泛使用的酒店管理软件)[CVE-2016-5563/4/5]的远程代码执行漏洞,我就是通过阅读器用户手册快速找到可能存在危险的数据表,最后快速找出远程代码执行漏洞。有关这个漏洞的分析,请参看:
http://jackson.thuraisamy.me/oracle-opera.html
(执行条件分析)Implementation analysis,执行条件分析是指理解应用程序运行时需要包括的环境因素,比如网络配置,端口使用等。可以通过端口扫描,漏洞扫描等方式进行操作。这些配置上的问题,很可能就会导致一个漏洞的出现。
聚个例子,一个系统权限级别的程序,由于配置错误,可能会使的低权限的用户能够访问并修改,最终导致一个权限提升,引发一个提权漏洞。又比如,在一个网络程序中,可能程序本身并没有错误,但是由于这个服务器开了一个anonymous的FTP服务器,那么任何人都有可能访问这个机器,这就导致web应用的源代码或者其他敏感文件暴露在外面。这些问题,理论上并非程序自身的漏洞,但是由于其执行环境的因素,就使得其成为一个漏洞。
通信分析(Communications analysis)。通信分析是指对一个目标程序与其他进程或系统之间交互信息的方式进行深入分析。在这样的前提下,可通过发送精心构造的请求(request)等方法,触发不安全状态。许多web应用程序漏洞都是通过这种方式发现的。
在上述模式下,需要我们对数据通信协议有较为清晰的认识,如果并不能够清晰的了解协议格式呢?这种情况就要借助黑盒测试的技术进行解决,主要通过发送请求,并根据返回值进行判断是否存在异常。
下面举个例子来说明,这里假设存在一个金融网站,里面有一项功能允许用户使用不同的货币购买预付信用卡。这里假设实现这项业务的请求(Request)如下:
- FromAccount : 用于购买预付卡的账户
- FromAmount : 需要从fromAccount转入预付卡的金额(例如 ¥100)
- CardType: 需要购买的预付卡类型(例如 USD、GBP
- currencyPair : 付款账户和预付信用卡的货币配对,用于计算汇率(如 CADUSD、CADGBP)
当我们要去测试这样一个应用程序时,首先我们可能会想到发送一个正常的数据请求,以帮助我们了解该应用程序的标准响应的格式。例如下图,用CAD去购买82美刀的预付卡的请求和响应是下面这个样子:
或许我们并不知道程序在后台到底对我们的数据做了何种处理,但是我们通过观察status字段的属性知道我们的请求是ok的,下面如果我们将fromAmount参数调整为一个负数,或者将fromAccount调整为其他某个人的账户,那么这个web应用就可能会返回错误响应,比如验证不通过。另外如果我们将currencyPair从CADUSD(加元对美元)改为CADJPY(加元对日元)而cardType不变,那么我们会看到返回值中toAmount字段从82.20变为8863.68。如果程序缺乏足够的验证的话,我们有可能通过这种方式获取到更多的钱而付出的钱不变。
另一方面,如果我们能够获取到后台代码或程序,那事情就变得更加有意思,我们可以轻松了解到后台的运作流程,如后台是如何处理请求,有哪些错误响应,这将有助于我们构造出更加具有针对性的数据对应用进行测试。
代码分析(Code and binary analysis)。代码分析是指理解一个目标程序是如何处理用户输入的数据,以及该程序的执行流程。目前有很多方法可以对程序实现动态和静态分析,下面介绍其中一部分:
(1) 数据流分析。数据流分析对于寻找入口点以及数据是如何走向潜在的不安全状态是非常有用的。当我在通信分析(Communications analysis)遇到困难,很难构造出合适的request,我便会调整思路采取其他的方式寻找不安全状态,在数据流分析中,我可以使用这种方法,首先判断是否存在不安全状态,如果存在,则进一步跟踪数据是如何流向该状态。这个方法的有点是,一但发现不安全状态,入口点也随之确定,这为发掘漏洞提供巨大帮助。
在这个过程中,动态分析和静态分析需要紧密配合起来。举个例子,当你在寻找如何从A点走到B点时,静态分析就好比是在阅读一张地图,而动态分析就好比直接在这地图上走,需要实时关注路上的天气及交通状况,IDA和windbg的配合就是如此。静态分析可以对程序有一个全貌但不细致的理解,动态分析则可以对程序有一个狭隘但却细致的理解,二者是相互补充的。
(2) 外部引用分析。在分析程序的过程中,程序上下文环境中可能非常有限,这个时候分析程序的导入API可以帮助我们进一步了解程序的功能以及它与操作系统的交互方式。比如说,程序引用加密库对数据进行加密,那么你可以跟踪这个加密库并分析其功能,进而分析自己的输入是否能够影响其功能。令外,理解程序是如何与操作系统的交互也可以帮助我们进一步找到我们可以与程序进行交互的入口点。
(3) 字符串分析。与外部引用分析一样,分析程序中的字符串将帮助我们进一步理解程序中的功能,特别是那些调试信息,关键字或者令牌什么的,或者是那些看起来特别奇特的东西,对这些关键的字符串的分析及跟踪也将有利于我们寻找到更多的程序入口,进而更加全面的找出程序中的缺陷。
(4) 安全扫描。使用自动化的扫描工具(如PHP源代码扫描AWS)可能帮助我们快速找到一些常见的漏洞。但是对于寻找基于上下文和基于设计的漏洞并没有太大帮助。我通常并不会对这种方法有太多青睐,只会用来做一些辅助性的功能,如果单纯靠扫描就能找出一堆漏洞,你研究的目标安全做的就太差了,这在目前并不常见,或者说研究这类目标对于提高你自己并无任何帮助。
(5) 依赖性分析。一个应用程序往往会以来其他外来的组件,比如一些开源模块,它所依赖的开源模块自身存在的漏洞可能会被引入造成自身的未公开漏洞。值得一提的是,现今一个程序往往都是引用了众多第三方扩展模块,这些第三方的漏洞极易造成主程序的漏洞。举个例子,大多数浏览器都会使用sqlite做数据缓存,如果sqlite存在漏洞,那么这些浏览器都有可能存在问题,无论是谷歌还是火狐。
(6) 版本分析。如果你有机会访问程序的代码仓库,那么你就可以通过分析历史版本的方式对程序进行分析,这种方式不是基于上下文的,比如说,寻找那些长时间没有做改动的部分,这些部分极易寻在漏洞。
代码分析通常需要花费比其他方式更多的时间,同时也更难,因为研究者对这个程序的功能和使用的技术的掌握程度要不亚于其开发者,另外,一个程序的开发可能是由一个团队进行维护,那么对于研究者,全面掌握这些东西显得比较困难。但是只要肯专研,其实什么也都是能够克服的,中国有句古话,只要肯专研,铁棒变花针。
我无法不去强调编程能力的重要性,如果一个研究人员对他当前研究的程序所采用的语言和技术有深入扎实的功底,那么他必将创造出很多有价值的东西。从攻击的角度来说,他可以发现更加简单及直观,编写利用程序也将得心应手。从防御的角度来说,他可以提供出代码级别的具有高度针对性的修复建议而非那种通用的方法。
四. 有关漏洞挖掘的其他想法
1. 漏洞的复杂性
漏洞的复杂性分布非常广。一方面,有很多漏洞非常简单与直观,并且利用代码一目了然,比如说经典的sql注入。另一方面,在系统中有的看似并不相关,并且就其自身而言并非不安全,但是当这些东西以一种特定的方式结合起来的时候,就有可能引发大的漏洞,比如说条件竞争,或者一些其他的复杂的逻辑漏洞。我尝试将这些漏洞按照复杂级别分为“一级漏洞”和“二级漏洞”,不过也有其他分类方法。引用一局来自Project Zero的Ben Hawkes说过的一句话:
The modern exploit is not a single shot vulnerability anymore. They tend to be a chain of vulnerabilities that add up to a full-system compromise.
如今想要完成一个完整的利用,只靠单一的漏洞往往行不通。很多时候我们需要靠一连串的漏洞才能完成一起完整的利用,致使系统“妥协”。
2. 团队工作
在一个团队中工作能够有效帮助自己了解自己不知道的知识,以及提高自己已知的知识。不过在团队中要需要注意工作的方式方法,知之为知之,不知为不知,永远不要强行假装精通你不熟悉的东西,因为精通的人可以很轻易的指出你的症结。如果一个团队里面大家都不坦诚相待,那么这不是一个合格的团队,你可以尽早更换。在优秀的团队中,不要指望别人会把所有的知识交给你,要学会如何高效的学习,并在团队交流与合作中不断提高。
写在最后
感谢花时间将我的文章读完,我希望我的文章在可以帮助你解开你的一些对于漏洞挖掘的谜团。在学习和研究漏洞挖掘的过程中遇到困难并感到不知所措是非常正常的。不过学习的过程就是这样,只有不断的去尝试才会进步。祝你在漏洞挖掘的路上走的越来越远。