Resumen
En IMAP, el comando APPEND lleva el mensaje como un literal: el cliente anuncia primero cuántos bytes va a mandar ({N}) y el servidor lee exactamente esos N bytes como cuerpo. Todo lo que llegue después lo trata como protocolo.
El fallo está en imap_perform_append() (lib/imap.c). Cuando la conversión de saltos de línea está activada (CURLOPT_CRLF, --crlf, o CURLOPT_TRANSFERTEXT=1 en Unix), curl anuncia el tamaño original del fichero, pero lo que envía es el cuerpo convertido, en el que cada LF suelto se ha convertido en CRLF. Cada LF añade un byte, así que curl acaba mandando más de lo que ha anunciado. El servidor corta en el byte N, y lo que queda se ejecuta como comandos IMAP nuevos dentro de la sesión ya autenticada.
El investigador insiste en que esto no es el caso típico de un usuario que mete CRLF en un campo del protocolo: es un error de delimitación del propio curl. Los bytes vienen del cuerpo del mensaje y es la conversión de curl la que los empuja fuera del literal.
Se le asignó el CVE-2026-87864. Se comprobó en el commit c2676bf9e639e31e44b12a2cc82cf2046231b056 (curl 8.22.0-DEV) y en curl 8.6.0, y según el reporte probablemente afecta a todas las versiones desde que existe IMAP APPEND (7.30.0).
Causa
imap_perform_append() prepara el lector de subida y el comando con el mismo valor, data->state.infilesize, que es el tamaño antes de convertir:
result = Curl_creader_set_fread(data, data->state.infilesize);
...
result = imap_sendf(data, imapc, "APPEND %s%s {%" FMT_OFF_T "}",
mailbox, curlx_dyn_ptr(&flags), data->state.infilesize);
El lector que convierte los saltos (cr-lineconv, en lib/sendf.c) devuelve -1 como longitud total porque no sabe cuánto ocupará el resultado. Y Curl_req_send_more() (lib/request.c) sigue leyendo hasta el final del flujo, sin pararse en el límite que se anunció en el comando IMAP. Resultado: una entrada de N bytes con k LF sueltos sale por la red como N+k bytes.
Pasos de reproducción
- Compilar curl en el commit
c2676bf9e639e31e44b12a2cc82cf2046231b056(o cualquier versión desde la 7.30.0) en Linux o macOS. No hace falta TLS: con IMAP sin cifrar basta. - Crear
payload.bin, de 66 bytes:A\nrepetido 22 veces (44 bytes) y, a continuación, el sufijo de 22 bytes\nX CREATE "Injected"\r\n. Los 22 LF sueltos del principio están ahí para que la conversión añada exactamente 22 bytes y el sufijo quede por detrás del límite del literal. - Levantar un servidor IMAP local en el puerto 11430 que responda a
CAPABILITY,LOGINyAPPEND. Al recibirAPPEND, debe leer el tamaño{N}, contestar+para que continúe, consumir exactamente N bytes como cuerpo y, tras ese literal y su CRLF final, guardar la siguiente línea que llegue (el primer comando después del literal). - Subir el fichero con la conversión activada:
curl -sS --user user:pass --upload-file payload.bin --crlf \
imap://127.0.0.1:11430/INBOX
- curl termina con código 0: para él, la subida ha ido bien.
- Mirar qué recibió el servidor después del literal: ha leído
X CREATE "Injected"como un comando IMAP aparte, no como parte del mensaje. - Repetir como control sin
--crlf, con el mismo fichero y otro servidor idéntico en otro puerto. Ahora los 66 bytes caben en el literal de 66 bytes, el primer comando que llega después es elLOGOUTnormal de curl yX CREATE "Injected"se queda dentro del cuerpo del mensaje. - Comparar los dos casos: en ambos curl anunció un literal de 66 bytes, pero con
--crlfmandó 88 por la red. El servidor tomó 66 como literal y los 22 restantes (\r\nX CREATE "Injected"\r\n) los interpretó como protocolo: el CRLF cerró elAPPENDyX CREATE "Injected"se ejecutó como comando nuevo.
Impacto
Quien controle el contenido de un mensaje que una aplicación sube con curl por IMAP puede inyectar comandos IMAP arbitrarios, que se ejecutan con todos los permisos del usuario autenticado: CREATE, DELETE, STORE, COPY, etc.
Tienen que darse a la vez estas condiciones:
- la aplicación activa la conversión de saltos (
CURLOPT_CRLF/--crlf, oCURLOPT_TRANSFERTEXT=1en Unix); - la subida tiene tamaño conocido (curl lo exige para IMAP APPEND);
- curl hace un
APPENDen una sesión autenticada; - el atacante controla los bytes del cuerpo con precisión, para que el comando inyectado caiga justo después del límite.
La conversión viene desactivada por defecto, lo que limita la exposición; pero cuando está activa, el desajuste ocurre siempre.
Remediación
El reporte no detalla el parche final. El investigador propuso dos salidas: rechazar con un error explícito la combinación de CURLOPT_CRLF / CURLOPT_TRANSFERTEXT con IMAP APPEND, o calcular el tamaño ya convertido antes de enviar la cabecera del literal. Descartó cortar la salida en el tamaño anunciado: evitaría la inyección, pero estropearía el mensaje sin avisar, y considera preferible un fallo explícito.
Qué aprender de este caso
- En protocolos con prefijo de longitud (literales
{N}de IMAP,Content-Lengthen HTTP, campos de tamaño en protocolos binarios), comprueba si alguna capa transforma los datos después de haber calculado el tamaño: conversión de saltos de línea, codificaciones, compresión. Ahí es donde se descuadra. - Un lector que devuelve longitud desconocida (
-1) mientras otra parte del código ya ha prometido un tamaño fijo es una señal de alarma. Si la transformación cambia el tamaño, hay que calcularlo antes de anunciarlo o negarse a enviar. - Al auditar clientes de protocolos de texto, prueba las opciones poco usadas (
--crlf, modo texto) combinadas con cada comando que lleve cuerpo. El caso por defecto suele estar bien; los fallos aparecen en las combinaciones. - Para probarlo, sirve un servidor de prueba que respete el tamaño anunciado y registre la primera línea que llega después del cuerpo. Si no es el comando que debería seguir (aquí,
LOGOUT), algo se ha salido del literal.