T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- smart-backup.js:355
- Finding
- Restore Path Traversal Through Untrusted Manifest Entries<![CDATA[ ## Vulnerability Details **File Location**: `smart-backup.js`, lines 355–377 **Vulnerability Type**: Unrestricted path traversal during backup restoration **Risk Level**: High ### Vulnerable Code ```javascript const manifest = loadJSON(manifestPath, {}); let restored = 0; let overwritten = 0; let skipped = 0; for (const file of manifest.files || []) { const sourcePath = path.join(backupFile, path.relative(manifest.source, file.path)); const relPath = path.relative(manifest.source, file.path); const destPath = path.join(destination, relPath); ensureDir(path.dirname(destPath)); if (fs.existsSync(destPath)) { if (forceMode) { console.log(`[smart-backup] OVERWRITE: ${destPath}`); fs.copyFileSync(sourcePath, destPath); overwritten++; } else { console.log(`[smart-backup] WARNING: Skipping existing file (use --force to overwrite): ${destPath}`); skipped++; } } else { fs.copyFileSync(sourcePath, destPath); restored++; } } ``` ### Technical Analysis The restore operation trusts the `source` and `files[].path` properties of `manifest.json`. It derives a relative path using `path.relative()` and appends that value to the backup and destination roots with `path.join()`. There is no validation that the resulting relative path: - Is free from `..` traversal components. - Is not absolute. - Resolves beneath the requested backup directory. - Resolves beneath the requested restoration destination. - Does not traverse symbolic links. A malicious manifest can therefore produce paths that escape the selected directory boundaries. The `--force` option increases the impact by allowing existing out-of-scope files to be overwritten. ### Attack Path 1. An attacker creates or modifies a backup directory and its `manifest.json`. 2. The attacker sets `manifest.source` and one or more `files[].path` values so that `path.relative()` produces traversal components such as `../../`. 3. A victim runs the restore ...[truncated 658 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Treat every manifest field as untrusted input and validate its type and format. - Require manifest file paths to be normalized relative paths without `..` or absolute prefixes. - Resolve candidate paths with `path.resolve()` and verify that they remain under the canonical root: ```javascript function resolveWithin(root, relativePath) { if (typeof relativePath !== 'string' || path.isAbsolute(relativePath)) { throw new Error('Invalid manifest path'); } const canonicalRoot = fs.realpathSync(root); const candidate = path.resolve(canonicalRoot, relativePath); if (candidate !== canonicalRoot && !candidate.startsWith(canonicalRoot + path.sep)) { throw new Error('Manifest path escapes the allowed root'); } return candidate; } ``` - Store normalized relative paths in newly generated manifests instead of full source paths. - Reject symlinked destination components or explicitly enforce a documented symlink policy. - Validate all entries before copying any files, preventing partial restoration from a malformed manifest. - Continue requiring explicit overwrite authorization, but do not treat `--force` as authorization to escape the destination root. ]]>
