Groovy
依赖
1 | <!-- Groovy : 1.7.0-2.4.3 --> |
MethodClosure中有一个 doCall 方法,调用 InvokerHelper.invokeMethod() 方法进行方法调用。
1 | public MethodClosure(Object owner, String method) { |
那么这样就可以执行命令;
1 | MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec"); |
ProcessGroovyMethods.execute()方法
1 | public static Process execute(final String self) throws IOException { |
String.execute() 方法
Groovy 为 String 类型添加了 execute() 方法,以便执行 shell 命令,这个方法会返回一个 Process 对象。也就是说,在 Groovy 中,可以直接使用 "ls".execute() 这种方法来执行系统命令 “ls”。
在 Java 中,就可以直接写做:就是时调用”calc”字符串的execute函数;最终执行calc命令
1 | MethodClosure methodClosure = new MethodClosure("calc", "execute"); |
doCall方法由protected修饰,所以调用call方法;MethodClosure是没有call方法的,最终调用其父类Closure.call();
但是看代码得知,最终还是调回到MethodClosure.doCall(args)
1 | public V call(Object... args) { |
ConvertedClosure的父类是ConversionHandler;
ConversionHandler这个类实现了InvocationHandler接口;思路是这样的,为ConvertedClosure类搞一个代理;当调用方法时就会执行到ConversionHandler.invoke();ConversionHandler.invoke()调用了invokeCustom方法
1 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
ConvertedClosure.invokeCustom方法中,如果要代理的函数名和String method相同,就会调用call;
而((Closure) getDelegate())其实就是得到ConvertedClosure构造函数的第一个参数,很明显;这些值都是可控的,那么我们就可以去调用一个MethodClosure的call函数,从而导致命令执行;
1 | public ConvertedClosure(Closure closure, String method) { |
Poc
1 | package groovy; |
gadget:
1 | AnnotationInvocationHandler.readObject() |
或者(就是入口链不一样)
1 | PriorityQueue.readObject() |
Click1
依赖
1 | <dependency> |
Column类的内部类:ColumnComparator类调用了compare方法

this.column是可控的;使他调用到Column.getProperty();

然后会调用到PropertyUtils.getValue;

getObjectPropertyValue方法:”source”形参和”name”形参都是可控的;如果使得”source”形参为恶意的TemplatesImpl对象,”name”为outputProperties;最后method.invoke(source);就会调用TemplatesImpl.getoutputProperties;这就和CB链很像;
1 | private static Object getObjectPropertyValue(Object source, String name, Map cache) { |
1 | public static String toGetterName(String property) { |
gadget
1 | PriorityQueue.readObject() |
Poc
1 | package click; |
FileUpload1
依赖
1 | <!-- commons-fileupload : 1.3.x --> |
FileUpload 组件是 Apache 提供的上传组件,它本身依赖于 commons-io 组件,ysoserial 中利用了这个组件来任意写、读文件或者目录。但是具体是对文件还是目录操作与 FileUpload 以及 JDK 的版本有关。
受空字节截断影响的JDK版本范围:JDK<1.7.40;FileUpload 在 1.3.1 版本中,官方对空字节截断进行了修复,在 readObject 中,判断成员变量 repository 是否为空,不为空的情况下判断是不是目录,并判断目录路径中是否包含 \0 空字符。
org.apache.commons.fileupload.disk.DiskFileItem类的readObject;
getOutputStream方法:由于dfos无法反序列化(transient),只能为空;getTempFile方法返回一个File对象,其中repository是可以控制的,也就是目录是可控的,但是文件名不可控(低版本可以用空字符绕过,使得文件名也可控);
最终返回一个DeferredFileOutputStream对象;cachedContent可以通过反射改为我们想要写的内容,那么就可以任意目录写
1 | private void readObject(ObjectInputStream in) |
1 | public OutputStream getOutputStream() |
writeObject;
要使得cachedContent可控,就要使得dfos.isInMemory()返回True;
1 | private void writeObject(ObjectOutputStream out) throws IOException { |
其实也就是通过判断 written 的长度和 threshold 阈值长度大小,如果写入大于阈值,则会被写出到文件中,那就不是存在内存中了
1 | public boolean isInMemory() |
Poc
1 | package fileupload; |
Wicket1
Apache Wicket 抄了 FileUpload 的代码,就是说包名不同,攻击手法和FileUpload1是一样的
