397 lines
11 KiB
Bash
Executable file
397 lines
11 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Script Name: dvmcp
|
|
# Description: Damn Vulnerable MCP Server launcher
|
|
# Usage: dvmcp start|stop|status|logs|build
|
|
|
|
VERSION="1.1.0"
|
|
|
|
# Colors
|
|
readonly RED='\033[0;31m'
|
|
readonly GREEN='\033[0;32m'
|
|
readonly YELLOW='\033[1;33m'
|
|
readonly CYAN='\033[0;36m'
|
|
readonly BOLD='\033[1m'
|
|
readonly NC='\033[0m'
|
|
|
|
# Container settings
|
|
CONTAINER_NAME="dvmcp"
|
|
IMAGE="dvmcp:latest"
|
|
PORT_RANGE="9001-9010"
|
|
|
|
# Config file paths
|
|
SETTINGS_DIR="${HOME}/.claude"
|
|
PROD_CONFIG="${SETTINGS_DIR}/settings.json"
|
|
CTF_CONFIG="${SETTINGS_DIR}/settings-ctf.json"
|
|
BACKUP_CONFIG="${SETTINGS_DIR}/settings-prod-backup.json"
|
|
CONFIG_STATE_FILE="${SETTINGS_DIR}/.dvmcp-config-state"
|
|
|
|
show_help() {
|
|
echo -e "${BOLD}dvmcp${NC} - Damn Vulnerable MCP Server Launcher v${VERSION}"
|
|
echo
|
|
echo -e "${BOLD}USAGE:${NC}"
|
|
echo " dvmcp <command>"
|
|
echo
|
|
echo -e "${BOLD}COMMANDS:${NC}"
|
|
echo -e " ${CYAN}build${NC} Build Docker image from Dockerfile"
|
|
echo -e " ${CYAN}start${NC} Start DVMCP server"
|
|
echo -e " ${CYAN}stop${NC} Stop DVMCP server"
|
|
echo -e " ${CYAN}restart${NC} Restart DVMCP server"
|
|
echo -e " ${CYAN}status${NC} Check if running"
|
|
echo -e " ${CYAN}logs${NC} Show container logs"
|
|
echo -e " ${CYAN}shell${NC} Open shell in container"
|
|
echo
|
|
echo -e "${BOLD}EXAMPLES:${NC}"
|
|
echo " dvmcp build # Build image (first time setup)"
|
|
echo " dvmcp start # Launch DVMCP server"
|
|
echo " dvmcp stop # Stop DVMCP server"
|
|
echo " dvmcp logs # View logs"
|
|
echo
|
|
echo -e "${BOLD}ACCESS:${NC}"
|
|
echo " Ports: ${BOLD}9001-9010${NC} (10 challenge instances)"
|
|
echo " Test: ${BOLD}curl http://localhost:9001${NC}"
|
|
echo
|
|
echo -e "${BOLD}ABOUT:${NC}"
|
|
echo " Damn Vulnerable MCP Server - Intentionally vulnerable MCP implementation"
|
|
echo " Perfect for testing MCP security vulnerabilities"
|
|
echo " GitHub: https://github.com/harishsg993010/damn-vulnerable-MCP-server"
|
|
echo
|
|
echo -e "${BOLD}SECURITY:${NC}"
|
|
echo " This script automatically manages Claude Code config isolation:"
|
|
echo " - ${GREEN}start${NC}: Backs up production config, loads CTF-only config"
|
|
echo " - ${GREEN}stop${NC}: Restores production config automatically"
|
|
echo " - ${YELLOW}Your production MCP servers are protected${NC}"
|
|
echo
|
|
echo -e "${BOLD}FIRST TIME SETUP:${NC}"
|
|
echo " 1. Clone repo: git clone https://github.com/harishsg993010/damn-vulnerable-MCP-server.git"
|
|
echo " 2. cd damn-vulnerable-MCP-server/"
|
|
echo " 3. Build: dvmcp build"
|
|
echo " 4. Start: dvmcp start"
|
|
echo " 5. ${BOLD}Restart Claude Code${NC} to load CTF config"
|
|
}
|
|
|
|
check_docker() {
|
|
if ! command -v docker &>/dev/null; then
|
|
echo -e "${RED}Error:${NC} Docker not installed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
check_image_exists() {
|
|
if ! docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "^${IMAGE}$"; then
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
create_ctf_config_template() {
|
|
if [[ ! -f "$CTF_CONFIG" ]]; then
|
|
echo -e "${CYAN}[*]${NC} Creating CTF config template at ${CTF_CONFIG}..."
|
|
cat > "$CTF_CONFIG" <<'EOF'
|
|
{
|
|
"mcpServers": {
|
|
"Challenge 1": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9001/sse"]
|
|
},
|
|
"Challenge 2": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9002/sse"]
|
|
},
|
|
"Challenge 3": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9003/sse"]
|
|
},
|
|
"Challenge 4": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9004/sse"]
|
|
},
|
|
"Challenge 5": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9005/sse"]
|
|
},
|
|
"Challenge 6": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9006/sse"]
|
|
},
|
|
"Challenge 7": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9007/sse"]
|
|
},
|
|
"Challenge 8": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9008/sse"]
|
|
},
|
|
"Challenge 9": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9009/sse"]
|
|
},
|
|
"Challenge 10": {
|
|
"command": "npx",
|
|
"args": ["mcp-remote", "http://127.0.0.1:9010/sse"]
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
echo -e "${GREEN}✓${NC} CTF config created"
|
|
fi
|
|
}
|
|
|
|
swap_to_ctf_config() {
|
|
if [[ ! -f "$PROD_CONFIG" ]]; then
|
|
echo -e "${RED}Error:${NC} Production config not found at ${PROD_CONFIG}"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if we're already in CTF mode
|
|
if [[ -f "$CONFIG_STATE_FILE" ]]; then
|
|
echo -e "${YELLOW}⚠${NC} Already in CTF mode"
|
|
return 0
|
|
fi
|
|
|
|
# Create CTF config if it doesn't exist
|
|
create_ctf_config_template
|
|
|
|
echo -e "${CYAN}[*]${NC} Backing up production config..."
|
|
cp "$PROD_CONFIG" "$BACKUP_CONFIG"
|
|
|
|
echo -e "${CYAN}[*]${NC} Switching to CTF config..."
|
|
cp "$CTF_CONFIG" "$PROD_CONFIG"
|
|
|
|
# Mark that we're in CTF mode
|
|
echo "CTF_MODE_ACTIVE" > "$CONFIG_STATE_FILE"
|
|
|
|
echo -e "${GREEN}✓${NC} Switched to CTF config"
|
|
echo -e "${YELLOW}Note:${NC} Production MCP servers are disabled until you stop DVMCP"
|
|
}
|
|
|
|
restore_prod_config() {
|
|
if [[ ! -f "$CONFIG_STATE_FILE" ]]; then
|
|
echo -e "${YELLOW}⚠${NC} Already using production config"
|
|
return 0
|
|
fi
|
|
|
|
if [[ ! -f "$BACKUP_CONFIG" ]]; then
|
|
echo -e "${RED}Error:${NC} Backup config not found at ${BACKUP_CONFIG}"
|
|
echo -e "${YELLOW}Warning:${NC} Cannot restore production config!"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${CYAN}[*]${NC} Restoring production config..."
|
|
cp "$BACKUP_CONFIG" "$PROD_CONFIG"
|
|
|
|
# Clean up backup and state file
|
|
rm -f "$BACKUP_CONFIG" "$CONFIG_STATE_FILE"
|
|
|
|
echo -e "${GREEN}✓${NC} Production config restored"
|
|
}
|
|
|
|
cleanup_on_exit() {
|
|
# If script exits unexpectedly while in CTF mode, restore production config
|
|
if [[ -f "$CONFIG_STATE_FILE" ]]; then
|
|
echo
|
|
echo -e "${YELLOW}[!]${NC} Script interrupted, restoring production config..."
|
|
restore_prod_config
|
|
fi
|
|
}
|
|
|
|
# Register cleanup trap
|
|
trap cleanup_on_exit EXIT INT TERM
|
|
|
|
build_dvmcp() {
|
|
# Check if Dockerfile exists in current directory
|
|
if [[ ! -f "Dockerfile" ]]; then
|
|
echo -e "${RED}Error:${NC} Dockerfile not found in current directory"
|
|
echo
|
|
echo -e "${YELLOW}Expected location:${NC} ./Dockerfile"
|
|
echo
|
|
echo -e "${CYAN}To build the image:${NC}"
|
|
echo " 1. cd ~/path/to/damn-vulnerable-MCP-server/"
|
|
echo " 2. Run: dvmcp build"
|
|
echo
|
|
echo -e "${CYAN}Or clone the repo:${NC}"
|
|
echo " git clone https://github.com/harishsg993010/damn-vulnerable-MCP-server.git"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${CYAN}[*]${NC} Building DVMCP Docker image..."
|
|
echo -e "${YELLOW}Note:${NC} This may take several minutes..."
|
|
|
|
if docker build -t "$IMAGE" . ; then
|
|
echo -e "${GREEN}✓${NC} DVMCP image built successfully"
|
|
echo -e "${CYAN}[*]${NC} You can now run: ${BOLD}dvmcp start${NC}"
|
|
else
|
|
echo -e "${RED}✗${NC} Build failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
start_dvmcp() {
|
|
# Check if image exists
|
|
if ! check_image_exists; then
|
|
echo -e "${RED}Error:${NC} DVMCP image not found"
|
|
echo -e "${YELLOW}Run:${NC} ${BOLD}dvmcp build${NC} first"
|
|
exit 1
|
|
fi
|
|
|
|
# Swap to CTF config BEFORE starting container
|
|
swap_to_ctf_config
|
|
echo
|
|
|
|
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
echo -e "${YELLOW}⚠${NC} DVMCP already running"
|
|
echo -e "${CYAN}[*]${NC} Access at: ${BOLD}http://localhost:9001-9010${NC}"
|
|
return 0
|
|
else
|
|
echo -e "${CYAN}[*]${NC} Starting existing container..."
|
|
docker start "$CONTAINER_NAME"
|
|
fi
|
|
else
|
|
echo -e "${CYAN}[*]${NC} Starting DVMCP server..."
|
|
echo -e "${YELLOW}Note:${NC} Mapping ports 9001-9010 for challenge instances"
|
|
docker run -d --name "$CONTAINER_NAME" \
|
|
-p 9001:9001 \
|
|
-p 9002:9002 \
|
|
-p 9003:9003 \
|
|
-p 9004:9004 \
|
|
-p 9005:9005 \
|
|
-p 9006:9006 \
|
|
-p 9007:9007 \
|
|
-p 9008:9008 \
|
|
-p 9009:9009 \
|
|
-p 9010:9010 \
|
|
"$IMAGE"
|
|
fi
|
|
|
|
sleep 3
|
|
|
|
echo -e "${GREEN}✓${NC} DVMCP server started"
|
|
echo
|
|
echo -e "${BOLD}Challenge Instances:${NC}"
|
|
echo -e " ${CYAN}Port 9001:${NC} http://localhost:9001"
|
|
echo -e " ${CYAN}Port 9002:${NC} http://localhost:9002"
|
|
echo -e " ${CYAN}Port 9003:${NC} http://localhost:9003"
|
|
echo -e " ${CYAN}Port 9004:${NC} http://localhost:9004"
|
|
echo -e " ${CYAN}Port 9005:${NC} http://localhost:9005"
|
|
echo -e " ${CYAN}Port 9006:${NC} http://localhost:9006"
|
|
echo -e " ${CYAN}Port 9007:${NC} http://localhost:9007"
|
|
echo -e " ${CYAN}Port 9008:${NC} http://localhost:9008"
|
|
echo -e " ${CYAN}Port 9009:${NC} http://localhost:9009"
|
|
echo -e " ${CYAN}Port 9010:${NC} http://localhost:9010"
|
|
echo
|
|
echo -e "${YELLOW}Tip:${NC} Test with: ${BOLD}curl http://localhost:9001${NC}"
|
|
echo -e "${YELLOW}Security:${NC} ${GREEN}Production MCP servers isolated${NC}"
|
|
echo
|
|
echo -e "${BOLD}⚠ IMPORTANT:${NC} Restart Claude Code to load CTF config"
|
|
}
|
|
|
|
stop_dvmcp() {
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
echo -e "${CYAN}[*]${NC} Stopping DVMCP server..."
|
|
docker stop "$CONTAINER_NAME"
|
|
echo -e "${GREEN}✓${NC} DVMCP server stopped"
|
|
else
|
|
echo -e "${YELLOW}⚠${NC} DVMCP not running"
|
|
fi
|
|
|
|
# Restore production config AFTER stopping container
|
|
echo
|
|
restore_prod_config
|
|
echo
|
|
echo -e "${BOLD}⚠ IMPORTANT:${NC} Restart Claude Code to load production config"
|
|
}
|
|
|
|
restart_dvmcp() {
|
|
stop_dvmcp
|
|
sleep 2
|
|
start_dvmcp
|
|
}
|
|
|
|
show_status() {
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
echo -e "${GREEN}●${NC} DVMCP server is ${GREEN}running${NC}"
|
|
echo
|
|
echo -e "${BOLD}Active Ports:${NC}"
|
|
docker port "$CONTAINER_NAME" 2>/dev/null | while IFS= read -r line; do
|
|
echo -e " ${CYAN}${line}${NC}"
|
|
done
|
|
echo
|
|
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(NAMES|${CONTAINER_NAME})"
|
|
else
|
|
echo -e "${RED}●${NC} DVMCP server is ${RED}stopped${NC}"
|
|
|
|
if ! check_image_exists; then
|
|
echo
|
|
echo -e "${YELLOW}Note:${NC} Image not built yet. Run: ${BOLD}dvmcp build${NC}"
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
echo -e "${BOLD}Config Status:${NC}"
|
|
if [[ -f "$CONFIG_STATE_FILE" ]]; then
|
|
echo -e " ${CYAN}Mode:${NC} CTF (isolated)"
|
|
echo -e " ${CYAN}Active:${NC} ${CTF_CONFIG}"
|
|
echo -e " ${CYAN}Backup:${NC} ${BACKUP_CONFIG}"
|
|
else
|
|
echo -e " ${CYAN}Mode:${NC} Production"
|
|
echo -e " ${CYAN}Active:${NC} ${PROD_CONFIG}"
|
|
fi
|
|
}
|
|
|
|
show_logs() {
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
docker logs -f "$CONTAINER_NAME"
|
|
else
|
|
echo -e "${RED}Error:${NC} DVMCP server not running"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
open_shell() {
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
echo -e "${CYAN}[*]${NC} Opening shell in DVMCP container..."
|
|
docker exec -it "$CONTAINER_NAME" /bin/bash || docker exec -it "$CONTAINER_NAME" /bin/sh
|
|
else
|
|
echo -e "${RED}Error:${NC} DVMCP server not running"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Main
|
|
check_docker
|
|
|
|
if [[ $# -eq 0 ]] || [[ "$1" =~ ^(-h|--help|help)$ ]]; then
|
|
show_help
|
|
exit 0
|
|
fi
|
|
|
|
case "$1" in
|
|
build)
|
|
build_dvmcp
|
|
;;
|
|
start|up)
|
|
start_dvmcp
|
|
;;
|
|
stop|down)
|
|
stop_dvmcp
|
|
;;
|
|
restart)
|
|
restart_dvmcp
|
|
;;
|
|
status)
|
|
show_status
|
|
;;
|
|
logs)
|
|
show_logs
|
|
;;
|
|
shell|sh|bash)
|
|
open_shell
|
|
;;
|
|
*)
|
|
echo -e "${RED}Error:${NC} Unknown command: $1"
|
|
echo "Run 'dvmcp --help' for usage"
|
|
exit 1
|
|
;;
|
|
esac
|