CVE-2021-25646: Getting Code Execution on Apache Druid
March 29, 2021 | Trend Micro Research TeamIn 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.