T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/super_inline_html.js:15
- Finding
- Arbitrary Local File Inclusion in Self-Contained HTML Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/super_inline_html.js:15-18, 34-40, 45-51, 57-63, 70-76, 82-90` **Vulnerability Type**: Unrestricted local-file read through path traversal and absolute asset paths **Risk Level**: High ### Vulnerable Code ```js function fileToDataUrl(filePath, mimeType) { const data = fs.readFileSync(filePath); return `data:${mimeType};base64,${data.toString('base64')}`; } // Inline <link rel="stylesheet" href="..."> result = result.replace(/<link\s+[^>]*rel=["']stylesheet["'][^>]*href=["']([^"']+)["'][^>]*\/?>/gi, (match, href) => { if (href.startsWith('http') || href.startsWith('data:')) return match; const filePath = path.resolve(baseDir, href); if (!fs.existsSync(filePath)) return match; const css = fs.readFileSync(filePath, 'utf-8'); return `<style>\n${css}\n</style>`; }); // Inline <script src="..."> result = result.replace(/<script\s+[^>]*src=["']([^"']+)["'][^>]*><\/script>/gi, (match, href) => { if (href.startsWith('http') || href.startsWith('data:')) return match; if (href.includes('unpkg.com') || href.includes('cdn.')) return match; const filePath = path.resolve(baseDir, href); if (!fs.existsSync(filePath)) return match; const js = fs.readFileSync(filePath, 'utf-8'); return `<script>\n${js}\n</script>`; }); // Inline <img src="..."> result = result.replace(/<img\s+[^>]*src=["']([^"']+)["']/gi, (match, src) => { if (src.startsWith('http') || src.startsWith('data:')) return match; const filePath = path.resolve(baseDir, src); if (!fs.existsSync(filePath)) return match; const ext = path.extname(filePath); const dataUrl = fileToDataUrl(filePath, getMimeType(ext)); return match.replace(src, dataUrl); }); // Process <style> blocks result = result.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (match, cssContent) => { const inlined = cssContent.replace(/url\(["']?([^"')]+)["']?\)/gi, (urlMatch, urlPath) => { if (urlPath.startsWith('http') || urlPath.startsWith('dat ...[truncated 2619 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Define an explicit asset root, normally the canonical directory containing the input HTML. 2. Reject absolute paths before resolution: ```js if (path.isAbsolute(assetPath)) { throw new Error('Absolute asset paths are not allowed'); } ``` 3. Canonicalize both the asset root and target with `fs.realpathSync()` to account for symbolic links. 4. Verify that every canonical target remains beneath the authorized root: ```js function resolveAsset(assetRoot, assetPath) { if (path.isAbsolute(assetPath)) { throw new Error('Absolute paths are not allowed'); } const root = fs.realpathSync(assetRoot); const candidate = fs.realpathSync(path.resolve(root, assetPath)); const relative = path.relative(root, candidate); if (relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))) { return candidate; } throw new Error(`Asset escapes authorized root: ${assetPath}`); } ``` 5. Apply the same validation to stylesheet links, script sources, image sources, CSS `url()` values, and inline style attributes. 6. Allowlist supported asset extensions and reject files whose type does not match the relevant HTML context. 7. Consider requiring explicit opt-in for assets outside the document directory instead of permitting implicit traversal. 8. Add regression tests for `../` traversal, absolute paths, encoded traversal, and symlink escapes. ]]>
