红客解密程序 暗网编程
27.6K subscribers
966 photos
12 videos
1 file
56 links
渗透测试,#删库,数据删除 拿数据库 木马注入 破解,提权,Dns动持
Download Telegram
This media is not supported in your browser
VIEW IN TELEGRAM
第三次几乎 RCE
这个漏洞是我所有发现中最有趣的。因此,在创建了一些产品以访问其相关功能后,我开始研究雅虎小型企业商店。在发现一个功能的过程中,我注意到当我访问电子邮件模板页面时,ajax会发送一些请求来获取页面上显示的产品图片路径。其中一个请求发送到objinfo_data.php,参数是id。我以为这个脚本是用来调整产品图片大小的。所以,这部分很重要。在创建产品时,我注意到用于创建产品的请求包含几个指向图片的url,而不是直接包含二进制图片数据。我的想法是:“如果脚本获取了产品图片的链接并用它来调整大小会怎么样?”。我认为这可能是SSRF漏洞。我编辑了产品,将图片url更改为我的主机和端口,然后运行netcat。之后,我在向objinfo_data.php发送的请求中使用了该产品的产品id。Netcat显示了该请求,我注意到User-Agent是Curl。之后我尝试了所有可能的方法获取 RCE,但没有任何结果 :( 就是不行。在经历了所有的折磨之后,我决定向我的同事请教。其中一位同事建议尝试使用“-A something”,如果我在 netcat 的请求中看到“User-Agent: something”,就有可能注入参数字符串。我试过了,成功了!之后我的另一位同事立即建议我使用标志 -T。此标志从文件系统获取文件并将其附加到请求中。我使用它并能够读取 /etc/passwd。

为什么我连其他命令都执行不了,还说它几乎是 RCE?因为 curl 还有个有趣的参数“-o”,可以用来将输出写入文件系统。所以,如果我能找到服务器上的 Web 目录,我就能在那里写 Web Shell,然后(如果防火墙隔着)通过 curl 执行它 :)
CVE-2018-2628 简单复现与分析

复现:

第一步发送测试PoC,PoC中远程连接的服务器地址就是第二步中所使用的服务器,攻击的ip是192.168.3.103的7001端口上的T3服务,该服务会解包Object结构,通过一步步的readObject去第二步服务器上的1099端口请求恶意封装的代码,然后在本地弹出计算器。
查看weblogic的日志,可以看到如下错误,此时已经弹出计算器

分析:

Weblogic已经将互联网暴露的PoC都已经加入了黑名单,如果要绕过他的黑名单的限制就只能自己动手构造。来看看InboundMsgAbbrev中resolveProxyClass的实现,resolveProxyClass是处理rmi接口类型的,只判断了java.rmi.registry.Registry,其实随便找一个rmi接口即可绕过。

protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] arr$ = interfaces;
int len$ = interfaces.length;

for(int i$ = 0; i$ < len$; ++i$) {
String intf = arr$[i$];
if(intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}

return super.resolveProxyClass(interfaces);
}



其实核心部分就是JRMP(Java Remote Methodprotocol),在这个PoC中会序列化一个RemoteObjectInvocationHandler,它会利用UnicastRef建立到远端的tcp连接获取RMI registry,加载回来再利用readObject解析,从而造成反序列化远程代码执行。
Java反序列 Jdk7u21 Payload

在分析代码之前,我们先来了解一些相关知识,有助于后续理解


javassist
javassist :Java字节码操作库,提供了在运行时操作Java字节码的方法,如在已有 Class 中动态修改和插入Java代码,示例:在 Cat 类中添加包含恶意代码的 static block

生成的.class,反编译后的源码图2

在Jdk7u21 的payload 中,使用了javassist 来构造包含恶意代码的class


Java static initializer
Java Class 中定义的static 代码块被称为 staticinitializer,在class 初始化(initialized) 时会执行该语句块

这里需要重点关注一下ClassLoader.defineClass()方法运行后,并不会执行 static block,而Class.newInstance()会执行,这两个地方会涉及到Jdk7u21 payload 恶意代码的具体执行点
"f5a5a608"的hashCode为0
Java Object 中定义了hashCode()方法,返回一个hash 值,当两个对象equals 时,hashCode需要相同

String 类重写了该方法

有一个特殊的字符串"f5a5a608",hashCode的值为 0,在构造 Jdk7u21 payload 的过程中利用到了这一点

Dynamic Proxy
在ysoserial 的代码中,大量使用了到动态代理机制来构造payload,我们来简单了解一下

当需要增加或者修改某些已存在class的功能时,会使用动态代理机制,通过创建 proxyobject 来代理实际的对象。主要涉及接口为InvocationHandler

接口中只定义了一个方法invoke(),所有 proxy object 的方法调用都会转换为调用 invoke()方法,调用方法和参数通过method和 args来传递

来看一个代理Map 接口的例子,会在所有方法的执行之前打印start 、执行完成后打印finish
Payload分析 & 构造

TemplatesImpl
在利用payload 中,TemplatesImpl类主要的作用为:

使用_bytecodes成员变量存储恶意字节码( 恶意class=> byte array )

提供加载恶意字节码并触发执行的函数,加载在defineTransletClasses()方法中,方法触发为getOutputProperties()或 newTransformer()

我们来具体看一下,该类位于com.sun.org.apache.xalan.internal.xsltc.trax包中,用于xml document 的处理和转换,定义

TemplatesImpl 类实现了Templates和 Serializable两个接口

其中Templates接口定义如下,包含了两个方法,即之前提到触发恶意代码执行所的方法,在TemplatesImpl类中有一个private 方法 defineTransletClasses()

在方法中,调用了ClassLoader.defineClass()方法,参数为实例变量_bytecodes内的元素,该方法会将字节数组转换为Class,并加载

也就是说,通过设置_bytecodes的内容 ,调用 defineTransletClasses() 方法即可加载指定的 Class。

在代码中,一共有三个地方调用了这个方法

getTransletClasses()

getTransletIndex()

getTransletInstance()

在Java static initializer 部分提到 ClassLoader.defineClass() 并不会执行 static 代码块,所以前两个方法不满足条件,再看一下 getTransletInstance()方法
漏洞利用
根据上一节对漏洞原因的分析,我们利用漏洞绕过eBPF verifier机制后,就可以执行任意eBPF支持的指令,当然最直接的就是读写任意内存。漏洞利用步骤如下:

1.构造eBPF指令,利用ALU指令缺陷,绕过eBPF verifier机制;

2.构造eBPF指令,读取内核栈基址;

3.根据泄漏的SP地址,继续构造eBPF指令,读取task_struct地址,进而得到task_struct->cred地址;

4.构造eBPF指令,覆写cred->uid, cred->gid为0,完成提权。

漏洞利用的核心,在于精心构造的恶意eBPF指令,这段指令在Vitaly Nikolenko的exp中是16机制字符串(char *__prog),并不直观,笔者为了方便,写了个小工具,把这些指令还原成比较友好的形式,当然也可以利用eBPF的调试机制,在内核log中打印出eBPF指令的可读形式。

我们来看下这段eBPF程序,共41条指令(笔者写的小工具的输出):

parsing eBPF prog, size 328, len 41
ins 0: code(b4) alu | = | imm, dst_reg 9, src_reg 0, off 0, imm ffffffff
ins 1: code(55) jmp | != | imm, dst_reg 9, src_reg 0, off 2, imm ffffffff
ins 2: code(b7) alu64 | = | imm, dst_reg 0, src_reg 0, off 0, imm 0
ins 3: code(95) jmp | exit | imm, dst_reg 0, src_reg 0, off 0, imm 0
ins 4: code(18) ld | BPF_IMM | u64, dst_reg 9, src_reg 1, off 0, imm 3
ins 5: code(00) ld | BPF_IMM | u32, dst_reg 0, src_reg 0, off 0, imm 0
ins 6: code(bf) alu64 | = | src_reg, dst_reg 1, src_reg 9, off 0, imm 0
ins 7: code(bf) alu64 | = | src_reg, dst_reg 2, src_reg a, off 0, imm 0
ins 8: code(07) alu64 | += | imm, dst_reg 2, src_reg 0, off 0, imm fffffffc
ins 9: code(62) st | BPF_MEM | u32, dst_reg a, src_reg 0, off fffffffc, imm 0
ins 10: code(85) jmp | call | imm, dst_reg 0, src_reg 0, off 0, imm 1
ins 11: code(55) jmp | != | imm, dst_reg 0, src_reg 0, off 1, imm 0
ins 12: code(95) jmp | exit | imm, dst_reg 0, src_reg 0, off 0, imm 0
ins 13: code(79) ldx | BPF_MEM | u64, dst_reg 6, src_reg 0, off 0, imm 0
ins 14: code(bf) alu64 | = | src_reg, dst_reg 1, src_reg 9, off 0, imm 0
ins 15: code(bf) alu64 | = | src_reg, dst_reg 2, src_reg a, off 0, imm 0
ins 16: code(07) alu64 | += | imm, dst_reg 2, src_reg 0, off 0, imm fffffffc
ins 17: code(62) st | BPF_MEM | u32, dst_reg a, src_reg 0, off fffffffc, imm 1
ins 18: code(85) jmp | call | imm, dst_reg 0, src_reg 0, off 0, imm 1
ins 19: code(55) jmp | != | imm, dst_reg 0, src_reg 0, off 1, imm 0
ins 20: code(95) jmp | exit | imm, dst_reg 0, src_reg 0, off 0, imm 0
ins 21: code(79) ldx | BPF_MEM | u64, dst_reg 7, src_reg 0, off 0, imm 0
ins 22: code(bf) alu64 | = | src_reg, dst_reg 1, src_reg 9, off 0, imm 0
ins 23: code(bf) alu64 | = | src_reg, dst_reg 2, src_reg a, off 0, imm 0
ins 24: code(07) alu64 | += | imm, dst_reg 2, src_reg 0, off 0, imm fffffffc
ins 25: code(62) st | BPF_MEM | u32, dst_reg a, src_reg 0, off fffffffc, imm 2
ins 26: code(85) jmp | call | imm, dst_reg 0, src_reg 0, off 0, imm 1
ins 27: code(55) jmp | != | imm, dst_reg 0, src_reg 0, off 1, imm 0
ins 28: code(95) jmp | exit | imm, dst_reg 0, src_reg 0, off 0, imm 0
ins 29: code(79) ldx | BPF_MEM | u64, dst_reg 8, src_reg 0, off 0, imm 0
ins 30: code(bf) alu64 | = | src_reg, dst_reg 2, src_reg 0, off 0, imm 0
ins 31: code(b7) alu64 | = | imm, dst_reg 0, src_reg 0, off 0, imm 0
ins 32: code(55) jmp | != | imm, dst_reg 6, src_reg 0, off 3, imm 0
ins 33: code(79) ldx | BPF_MEM | u64, dst_reg 3, src_reg 7, off 0, imm 0
ins 34: code(7b) stx | BPF_MEM | u64, dst_reg 2, src_reg 3, off 0, imm 0
ins 35: code(95) jmp | exit | imm, dst_reg 0, src_reg 0, off 0, imm 0
ins 36: code(55) jmp | != | imm, dst_reg 6, src_reg 0, off 2, imm 1
ins 37: code(7b) stx | BPF_MEM | u64, dst_reg 2, src_reg a, off 0, imm 0
ins 38: code(95) jmp | exit | imm, dst_reg 0, src_reg 0, off 0, imm 0
ins 39: code(7b) stx | BPF_MEM | u64, dst_reg 7, src_reg 8, off 0, imm 0
ins 40: code(95) jmp | exit | imm, dst_reg 0, src_reg 0, off 0, imm 0
parsed 41 ins, total 41

稍微解释下,ins 0 和 ins 1 一起完成了绕过eBPF verifier机制。ins 0指令后,regs[9] = 0xffffffff,但在verifier中,regs[9].imm = -1,当执行ins 1时,jmp指令判断regs[9] == 0xffffffff,注意regs[9]是64bit integer,因为sign extension,regs[9] == 0xffffffff结果为false,eBPF跳过2(off)条指令,继续往下执行;而在verifier中,jmp指令的regs[9].imm == insn->imm结果为true,程序走另一个分支,会执行ins 3 jmp|exit指令,导致verifier认为程序已结束,不会去检查其余的dead code。

这样因为eBPF的检测逻辑和运行时逻辑不一致,我们就绕过了verifier。后续的指令就是配合用户态exp完成对kernel内存的读写。

这里还需要知道下eBPF的map机制,eBPF为了用户态更高效的与内核态交互,设计了一套map机制,用户态程序和eBPF程序都可以对map区域的内存进行读写,交换数据。利用代码中,就是利用map机制,完成用户态程序与eBPF程序的交互。

ins4-ins5: regs[9] = struct bpf_map *map,得到用户态程序申请的map的地址,注意这2条指令,笔者的静态解析并不准确,获取map指针的指令,在eBPF verifi
漏洞影响范围&修复
因为linux kernel的内核版本众多,对于安全漏洞的影响范围往往并不容易确认,最准确的方式是搞清楚漏洞根因后,从代码层面判断,但这也带来了高成本的问题,快速应急时,我们往往需要尽快确认漏洞影响范围。

从前面的漏洞原理来看,笔者大致给一个全面的linux kernel受影响版本:

3.18-4.4所有版本(包括longterm 3.18,4.1,4.4);

<3.18,因内核eBPF还未引入verifier机制,不受影响。

对于大量用户使用的各个发行版,还需要具体确认,因为该漏洞的触发,还需要2个条件

1.Kernel编译选项CONFIG_BPF_SYSCALL打开,启用了bpf syscall;

2./proc/sys/kernel/unprivileged_bpf_disabled设置为0,允许非特权用户调用bpf syscall

而Ubuntu正好满足以上3个条件。

关于修复,upstream kernel在3月22日发布的4.4.123版已经修复该漏洞[11][12], Ubuntu官方4月5日也正式发布了安全公告和修复版本[13][14],没有修复的同学可以尽快升级了。

但现在距漏洞Exp公开已经过去20多天了,在漏洞应急时,我们显然等不了这么久,回过头看看当初的临时修复方案:

1.设置/proc/sys/kernel/unprivileged_bpf_disabled为1,也是最简单有效的方式,虽然漏洞仍然存在,但会让exp失效;

2.使用Ubuntu的预发布源,更新Ubuntu 4.4的内核版本,因为是非正式版,其稳定性无法确认。
网络安全公司CrowdStrike旗下的猎鹰传感器(Falcon Sensor)的一次软件更新引发了一场全球危机,导致全球安装有Windows系统计算机出现大规模的蓝屏死机(blue screen of death,即BSOD),结果数千架航班被迫停飞、医院陷入混乱、支付系统崩溃,直接影响了数百万用户,成为历史上最大的 IT 故障。初步统计,宕机事件给财富 500 强企业造成高达 54 亿美元的损失。

此次宕机是由于CrowdStrike猎鹰传感器的更新中存在缺陷而引发的,相关更新出现一个逻辑错误进而导致系统崩溃,特别是Windows设备。