CVE-2021-25646: Getting Code Execution on Apache Druid

March 29, 2021 | Trend Micro Research Team

In this excerpt of a Trend Micro Vulnerability Research Service vulnerability report, Pengsu Cheng and Prosenjit Sinha of the Trend Micro Research Team detail a recent code execution vulnerability in the Apache Druid database. The bug was originally discovered and reported by Litch1 from the Security Team of Alibaba Cloud. The following is a portion of their write-up covering CVE-2021-25646, with a few minimal modifications.


Apache Druid is a high-performance, modern, real-time analytic database. Druid is designed for workflows where fast ad-hoc analytics, instant data visibility, or high concurrency are required. Druid streams data from applications like Kafka and Amazon Kinesis, and batch-loads files from data lakes such as HDFS and Amazon S3. Druid supports most popular file formats for structured and semi-structured data. Some common application areas for Druid include clickstream analytics (web and mobile analytics), network telemetry analytics (network performance monitoring), server metrics storage, supply chain analytics, (manufacturing metrics), application performance metrics, digital marketing/advertising analytics, and business intelligence. 

Apache Druid provides a rich set of APIs via HTTP and JDBC for loading, managing and querying data. Users can also interact with Druid via its built-in console interface. The Apache Druid console can be accessed via HTTP. An HTTP request consists of a request line, various headers, an empty line, and an optional message body: 

where CRLF represents the new line sequence Carriage Return (CR) followed by Line Feed (LF) and SP represents a space character. Parameters can be passed from the client to the server as name-value pairs in either the Request-URI or in the message-body, depending on the Method used and the Content-Type header. For example, a simple HTTP request using the GET method and passing a parameter named param with value 1 would look like this:

A corresponding HTTP request using the POST method might look like:

If there is more than one parameter/value pair, they are encoded as &-delimited name=value pairs:

         var1=value1&var2=value2...

The Vulnerability

Druid offers the ability to execute JavaScript at the server without restrictions. Out of concern for security, JavaScript is disabled by default.

Druid uses Jackson to parse the JSON data. When Druid receives JSON data of type “javascript” it uses JavaScriptDimFilter as the corresponding entity class. The constructor of JavaScriptDimFilter is decorated with @JasonCreator:

The @JsonCreator annotation signifies that when the JavaScriptDimFilter class is deserialized, Jackson will call this constructor. The parameters of the constructor dimension, function, extractionFn, and filterTuning all have @JasonProperty annotation modification; as a result, Jackson will be encapsulated a com.fasterxml.jackson.databind.deser.CreatorProperty type when deserializing and parsing to JavaScriptDimFilter. In the case of config parameters that are not marked @JasonProperty, a com.fasterxml.jackson.databind.deser.CreatorProperty named "" will be created.

According to the Druid documentation, JavaScript execution can be enabled via the flag druid.javascript.config in the configuration, and is disabled by default. org.apache.druid.js.JavaScriptConfig contains JavaScript-related configuration. Druid uses the Jersey framework, and all its configuration information, including JavaScriptConfig, is provided by the Guice framework. In order to execute JavaScript on the Druid server, an attacker would need to enable JavaScript in the JavaScriptDimFilter configuration.

Apache Druid has a remote code execution vulnerability while parsing JSON data of type JavaScript. This vulnerability is mainly based on the Jackson parsing feature. When the name property of the JSON data is resolved to "", the value of that empty key is bound to the corresponding parameter (config) of the object (JavaScriptDimFilter, specified when the type is JavaScript). As a result, an attacker can enable the JavaScript execution settings, resulting in the execution of user-supplied JavaScript using the function key.

In the com.fasterxml.jackson.databind.deser.BeanDeserializer#_deserializeUsingPropertyBased method, the “key name” in the parsed JSON string is used to find the corresponding CreatorProperty in the current parsed object. This functionality of looking at the CreatorProperty is implemented in the findCreatorProperty method. The findCreatorProperty method looks for the property in the _propertyLookup HashMap for the corresponding “key name”. In _propertyLookup, the key of JavaScriptConfig is set to "" as it is not decorated with a @JsonProperty annotation. If the attacker supplies a key in the JSON string as "", findCreatorProperty will match that key and it will select the CreatorProperty corresponding to JavaScriptConfig. For example, an attacker can supply the corresponding segment of the JSON to inject configuration settings into JavaScriptConfig:

Jackson is responsible for injecting org.apache.druid.js.JavaScriptConfig into _propertyLookup. The _deserializeUsingPropertyBased method mentioned earlier is called by deserializeFromObjectUsingNonDefault method from the BeanDeserializerBase class, where the _propertyLookup is stored in _propertyBasedCreator HashMap. The BeanDeserializerBase#deserializeFromObjectUsingNonDefault method gets called from BeanDeserializer#deserializeFromObject, where the HashMap is stored in _propertyBasedCreator. Ultimately in the SettableBeanPropery class, the _propertyBasedCreator is assigned to the _valueTypeDeserializer HashMap. According to Jackson documentation, the _valueTypeDeserializer hashmap contains type information and this is the type deserializer used to handle type resolution.

After extracting the corresponding CreatorProperty of the JavascriptConfig, JavaScriptDimFilter checks if the JavaScript execution is enabled. Finally, it executes the JavaScript. The following code shows JavaScriptDimFilter checking if the JavaScript is enabled or not:

         Preconditions.checkState(this.config.isEnabled(), "JavaScript is disabled");

Therefore, an attacker is able to set the JavaScriptConfig to enable JavaScript execution by sending an empty ("") name when the type is set to JavaScript. The value of the empty name is then parsed and applied to the configuration of JavaScriptDimFilter class. The attacker can then send arbitrary JavaScript as the value of the function key.

A remote attacker can exploit this vulnerability by sending an HTTP request containing crafted JSON data in the request body. Successful exploitation can result in the execution of arbitrary code with the privileges of the vulnerable server.

Source Code Walkthrough

The following code snippet was taken from Apache Druid version 0.19.0. Comments added by Trend Micro have been highlighted.

From org.apache.druid.query.filter.JavaScriptDimFilter:

From com.fasterxml.jackson.databind.deser.BeanDeserializer:

From com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator:

From com.fasterxml.jackson.databind.deser.BeanDeserializerBase:

From com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer:

Conclusion

The Apache Druid team has addressed this vulnerability and recommends users update to version 0.20.1. They also recommend network access to cluster machines be restricted to trusted hosts only. Publicly available proof-of-concept code has been released for this bug, so administrators should upgrade to a non-affected version of Druid as soon as possible.

Special thanks to Pengsu Cheng and Prosenjit Sinha of the Trend Micro Research Team for providing such a thorough analysis of this vulnerability. For an overview of Trend Micro Research services please visit http://go.trendmicro.com/tis/.

The threat research team will be back with other great vulnerability analysis reports in the future. Until then, follow the ZDI team for the latest in exploit techniques and security patches.