MCP Security Series: Command Injection
MCP Security Series: Command Injection
Section titled “MCP Security Series: Command Injection”Introduction
Section titled “Introduction”Command injection means the MCP server itself is vulnerable to command injection and similar flaws. The malicious commands it executes can come from many sources: a user supplying them through a prompt, the model picking them up from its context, the model reading them from an external file, or data pushed down from an upstream service, to name a few.
Reproduction
Section titled “Reproduction”Let’s walk through two scenarios from OWASP to reproduce this.
Scenario 1: Consider an MCP service that lists directory contents. It takes a parameter and concatenates it directly into a dir command. Here is the code:
from mcp.server.fastmcp import FastMCPimport subprocess
mcp = FastMCP("VulnServer")
@mcp.tool()def list_dir(path: str) -> str: """List the files in a directory. Pass the path as requested by the user.""" command = f"dir {path}" try: # shell=True is the key here — it lets cmd.exe interpret & and && res = subprocess.run(command, shell=True, capture_output=True, text=True, encoding='gbk') return res.stdout except Exception as e: return str(e)
if __name__ == "__main__": mcp.run()We pass a parameter through the prompt, aiming to chain it with & and similar symbols so the backend executes something other than dir. The result:

Scenario 2: This one simulates SQL injection. We initialize a SQLite database, and the query statement concatenates the supplied name before executing. Here is the code:
from mcp.server.fastmcp import FastMCPimport sqlite3
mcp = FastMCP("UserDatabase")
# Initialize a SQLite database for testingdef init_db(): conn = sqlite3.connect(':memory:') cursor = conn.cursor() cursor.execute("CREATE TABLE users (id INTEGER, username TEXT, secret TEXT)") cursor.execute("INSERT INTO users VALUES (1, 'alice', 'alice_token_123')") cursor.execute("INSERT INTO users VALUES (2, 'admin', 'admin_super_secret')") conn.commit() return conn
db_conn = init_db()
@mcp.tool()def query_user(username: str) -> str: """Look up basic information about a regular user by username."""
cursor = db_conn.cursor()
# Vulnerability: the LLM-generated parameter is concatenated straight into the SQL query query = f"SELECT * FROM users WHERE username = '{username}'"
try: cursor.execute(query) result = cursor.fetchall() return f"Query result: {result}" except Exception as e: return f"Query failed: {str(e)}"
if __name__ == "__main__": mcp.run()For our test we pass in a SQL injection payload to retrieve every record. The result:

Mitigation
Section titled “Mitigation”For this class of issue, start by auditing the MCP server code to make sure no such vulnerabilities exist. Second, if the service executes commands, you can introduce an allowlist. If a sandbox is available, ideally execution should happen inside it. Finally, apply the general MCP hardening measures — enforce least privilege. For example, a server that only lists directories should not also have permission to read files. See the earlier article on excessive permissions for details.
Xiaozao InfoSec: focused on AI security, including but not limited to LLM security, agent security, robotics security, and AI for cybersecurity. Welcome to learn together.