Overview
CVE-2024-29409 describes a file upload vulnerability in NestJS v10.3.2 (CWE-94) where an attacker could abuse the Content-Type header to trigger remote code execution on the server. This class of vulnerability arises when the server uses unvalidated headers to decide how to process uploaded content, potentially loading, evaluating, or executing code derived from user-provided data. In real-world deployments, such flaws can lead to full host compromise, data breach, or persistence in the environment if not properly mitigated. The risk is heightened in microservice architectures where file-handling endpoints are exposed and integrated with dynamic module loading, making attackers’ payloads harder to sandbox. This guide references CVE-2024-29409 to illustrate the concrete impact and the CWE-94 category it maps to.
Affected Versions
10.3.2
Code Fix Example
NestJS API Security Remediation
import { Controller, Post, Req, UseInterceptors, UploadedFile, BadRequestException } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
@Controller('upload')
export class UploadController {
// Vulnerable pattern (conceptual demonstration, do not deploy in production)
@Post('vulnerable')
@UseInterceptors(FileInterceptor('file'))
async uploadVulnerable(@Req() req, @UploadedFile() file: Express.Multer.File) {
const contentType = req.headers['content-type'] || '';
// Insecure: decision based on Content-Type header
if (contentType.includes('application/javascript')) {
// Potential insecure path: uploaded JS payload could be loaded or evaluated
// DO NOT enable eval or dynamic module loading from uploads in real code
const code = file?.buffer?.toString('utf8') ?? '';
// eslint-disable-next-line no-eval
// eval(code); // This is intentionally disabled in this demo
}
return { message: 'vulnerable pattern received' };
}
// Secure fix: safe upload handling without relying on Content-Type for code execution
@Post('secure')
@UseInterceptors(FileInterceptor('file', {
storage: diskStorage({
destination: './uploads',
filename: (req, file, cb) => {
const safeName = Date.now() + '-' + file.originalname.replace(/[^a-z0-9.\-]/gi, '_');
cb(null, safeName);
}
}),
limits: { fileSize: 5 * 1024 * 1024 }, // 5 MB limit
fileFilter: (req, file, cb) => {
const allowed = ['image/png', 'image/jpeg', 'application/pdf', 'text/plain'];
if (allowed.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new BadRequestException('Unsupported file type'), false);
}
}
}))
async uploadSecure(@UploadedFile() file: Express.Multer.File) {
// Safe: file is saved to disk with a sanitized name; no code execution
return { filename: file.filename, size: file.size };
}
}