Resumen
Weblate tiene una página de administración en /manage/ssh/ para gestionar claves SSH. Desde ahí se puede añadir la clave de un servidor indicando su nombre (host) y, opcionalmente, un puerto. El problema estaba en cómo se usaba ese nombre: la función add_host_key de weblate/vcs/ssh.py lo añadía directamente como un argumento más de la orden ssh-keyscan, sin ninguna comprobación.
cmdline = ["ssh-keyscan"]
if port:
cmdline.extend(["-p", str(port)])
cmdline.append(host)
Como subprocess.run recibe una lista, no se pueden encadenar órdenes de shell, pero sí colar opciones de ssh-keyscan. La que aprovecha el investigador es -f, que hace que la herramienta lea los servidores desde un fichero. Cuando el contenido de ese fichero hace fallar a ssh-keyscan, Weblate captura el error y muestra al usuario la salida de error del proceso en un mensaje:
except subprocess.CalledProcessError as exc:
messages.error(
request,
gettext("Could not get host key: %s") % exc.stderr or exc.stdout,
)
El resultado es la lectura de ficheros del servidor a los que tenga acceso el usuario con el que corre la aplicación. El investigador lo confirmó en instalaciones propias con las versiones 5.0.2 y 5.15.2 (la estable en el momento del reporte) y cree que probablemente afecta a todas. El fallo tiene asignado el CVE-2026-24126.
El investigador señala también que el parámetro type de la acción de generar clave (generate_ssh_key) llega sin filtrar a subprocess.run, aunque la prueba de concepto se centra en host. Según el propio reporte, no hay forma directa de ejecutar código, porque ssh-keyscan no tiene opciones que permitan lanzar órdenes arbitrarias: el impacto se limita a la inyección de argumentos.
Pasos de reproducción
- Iniciar sesión en Weblate con un usuario que pueda gestionar las claves SSH.
- Abrir la página de gestión SSH,
/manage/ssh/. - Usar la opción de añadir la clave de un servidor ("Add a host key").
- Interceptar la petición con un proxy, por ejemplo Burp Suite.
- Cambiar en el cuerpo POST el parámetro
hostpor la opción-fseguida de la ruta del fichero que se quiere leer. Payload:
action=add-host&host=-f/app/data/ssh/id_rsa&port=22
- Enviar la petición. La prueba de concepto del reporte, leyendo
/etc/passwd:
POST /manage/ssh/ HTTP/1.1
[...]
Host: 127.0.0.1
[...]
csrfmiddlewaretoken=[REDACTADO]&host=-f/etc/passwd&port=12&action=add-host
- Observar la respuesta: la aplicación devuelve un mensaje de error que contiene el contenido del fichero indicado.
Impacto
Un atacante con permisos de administrador en Weblate podía leer ficheros del servidor a los que tenga acceso el usuario del servicio web. Según el investigador, así se pueden extraer:
- las claves privadas SSH del servidor (
id_rsa); /etc/passwd;- el
settings.pyde Django, con laSECRET_KEY; - el código fuente de la aplicación.
Con las claves SSH o las credenciales filtradas, el atacante podría saltar a otros sistemas o llegar a tener una shell completa.
Qué aprender de este caso
- Pasar argumentos como lista a
subprocess.runevita la inyección de órdenes de shell, pero no la de opciones: si un valor del usuario puede empezar por-, la herramienta lo interpretará como un parámetro. Merece la pena revisar cualquier formulario que acabe enssh-keyscan,git,curl,taro similares. - La defensa habitual es doble: validar el formato esperado (aquí, un nombre de servidor o una IP, que nunca empieza por guion) y colocar
--antes de los argumentos del usuario cuando la herramienta lo admita. - Devolver al navegador el
stderrde un proceso externo convierte un fallo de argumentos en una lectura de ficheros. Los errores de herramientas del sistema deberían registrarse en el servidor y mostrarse al usuario con un mensaje genérico. - Que una función solo esté al alcance de administradores no la hace inocua: aquí el panel web daba acceso a secretos del sistema (claves SSH,
SECRET_KEY) que ese rol no debería poder ver.