T09 · Insecure Skill Coding Practices
Error
- Location
- extract.js:36
- Finding
- Arbitrary Command Execution Through URL Shell Injection## Vulnerability Details **File Location**: `extract.js`, lines 36-54 **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```js async function main() { const url = process.argv[2]; const outputName = process.argv[3] || 'output'; if (!url) { console.log('用法: node extract.js <URL> [输出文件名]'); process.exit(1); } console.log(`正在提取: ${url}`); // 创建临时目录 if (!fs.existsSync(TEMP_DIR)) { fs.mkdirSync(TEMP_DIR, { recursive: true }); } // 获取网页内容 console.log('正在获取网页内容...'); const html = execSync(`curl -sL -A "Mozilla/5.0" "${url}"`, { encoding: 'utf8' }); ``` ### Technical Analysis The URL is read directly from the command-line arguments and interpolated into a command string passed to `execSync`. By default, `execSync` executes the string through a system shell. Wrapping the URL in double quotes does not make the command safe. POSIX-compatible shells still process command substitutions such as `$(command)` and backtick expressions inside double-quoted strings. Consequently, an attacker-controlled URL can cause arbitrary local commands to execute. No URL validation, shell metacharacter rejection, or argument-safe process invocation separates the untrusted input from the shell command. ### Attack Path 1. An attacker supplies or persuades an operator or Agent to process a crafted URL containing shell command substitution. 2. For example, the script could be invoked with a URL argument containing: ```text https://example.com/$(touch /tmp/skill-command-executed) ``` 3. The value is inserted into the `curl` command string. 4. `execSync` launches a shell to interpret that string. 5. The shell evaluates `$(touch /tmp/skill-command-executed)` before invoking `curl`. 6. The injected command executes with the privileges and environment of the user running the Skill. ...[truncated 744 chars]
- Remediation
- ## Remediation Suggestions Eliminate shell interpretation entirely. Prefer Node.js HTTP APIs already imported by the script, or invoke `curl` with an argument array through `execFileSync` or `spawn`. ```js const { execFileSync } = require('child_process'); let parsedUrl; try { parsedUrl = new URL(url); } catch { throw new Error('Invalid URL'); } if (!['http:', 'https:'].includes(parsedUrl.protocol)) { throw new Error('Only HTTP and HTTPS URLs are allowed'); } const html = execFileSync( 'curl', ['-sL', '-A', 'Mozilla/5.0', parsedUrl.href], { encoding: 'utf8', timeout: 30000, maxBuffer: 10 * 1024 * 1024 } ); ``` Using an argument-array API ensures the URL is passed as one literal process argument rather than interpreted as shell syntax. Do not attempt to solve the issue solely by adding quotation marks or manually escaping a small set of metacharacters. Additional hardening should include: - Restricting destination hosts if this Skill is intended only for specific image providers. - Permitting only `http:` and `https:` schemes. - Applying request timeouts and response-size limits. - Rejecting embedded credentials and malformed URLs. - Running the Skill as an unprivileged account with access only to necessary files. - Adding regression tests using URLs containing `$()`, backticks, semicolons, quotes, and newline characters.
