T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- index.js:22
- Finding
- Allowed-Path Validation Can Be Bypassed to Read Unauthorized Files<![CDATA[ ## Vulnerability Details **File Location**: `index.js:22-48` **Vulnerability Type**: Improper path authorization and directory traversal **Risk Level**: High ### Vulnerable Code ```javascript module.exports = async function(context) { const filePath = context.args.path; const ext = path.extname(filePath).toLowerCase(); // 安全检查:限制可访问路径 const allowedRoots = [ process.env.OPENCLAW_WORKSPACE, 'D:\\个人' // 用户授权的路径 ]; if (!allowedRoots.some(root => filePath.startsWith(root))) { return { error: '路径不在允许范围内' }; } // 根据扩展名选择读取方式 switch (ext) { case '.txt': case '.md': case '.json': return readTextFile(filePath); case '.docx': return await readDocx(filePath); case '.pdf': return await readPdf(filePath); default: return { error: '不支持的文件格式' }; } }; ``` ### Technical Analysis The authorization check applies `String.prototype.startsWith()` directly to an attacker-controlled path. It does not normalize or canonicalize the target before comparing it with an allowed root. This permits several forms of authorization bypass: - **Directory traversal:** A path such as `/workspace/../secret.json` begins with `/workspace` as a string but resolves outside that directory. - **Prefix collision:** If `/workspace` is allowed, a path such as `/workspace-backup/secret.txt` also passes the prefix check. - **Symbolic-link traversal:** A symbolic link located under the workspace can refer to a file outside it. The submitted path passes the string comparison while the filesystem resolves it to an unauthorized target. - **Undefined workspace behavior:** If `OPENCLAW_WORKSPACE` is unset, JavaScript converts the `undefined` search value to the string `"undefined"` when evaluating `startsWith(undefined)`. The implementation therefore does not reliably fail closed. - **Overly broad hard-coded authorization:** The fixed `D:\个人` root grants access independently of the configured workspace and withou ...[truncated 1670 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Require `OPENCLAW_WORKSPACE` to be present and valid. Fail closed if it is absent. 2. Remove the hard-coded `D:\个人` root unless it is supplied through an explicit, trusted configuration mechanism. 3. Validate that the submitted path is a non-empty string. 4. Canonicalize the allowed root and target with `fs.realpathSync()` or their asynchronous equivalents. 5. Verify containment using `path.relative()` rather than string-prefix comparison. 6. Reject targets whose relative path is absolute, equals `..`, or begins with `..` followed by a path separator. 7. Perform containment validation after symbolic links have been resolved. 8. Reject non-regular files before reading them. 9. Account for Windows path separator and case-insensitivity behavior. Example containment approach: ```javascript const fs = require('fs'); const path = require('path'); function resolveAuthorizedFile(inputPath, configuredRoot) { if (typeof configuredRoot !== 'string' || configuredRoot.length === 0) { throw new Error('Workspace root is not configured'); } if (typeof inputPath !== 'string' || inputPath.length === 0) { throw new Error('Invalid file path'); } const root = fs.realpathSync(configuredRoot); const target = fs.realpathSync(path.resolve(root, inputPath)); const relative = path.relative(root, target); if ( relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) ) { throw new Error('Path is outside the allowed workspace'); } const stat = fs.statSync(target); if (!stat.isFile()) { throw new Error('Target is not a regular file'); } return target; } ``` If absolute input paths must be supported, resolve them independently and apply the same canonical containment test before any read or parser operation. ]]>
