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是一样的