Files
reverse-skill/burp-mcp-full/mcp-bridge.js
T
2026-08-08 18:32:33 +08:00

380 lines
18 KiB
JavaScript

#!/usr/bin/env node
/**
* BurpSuite MCP Stdio Bridge
*
* Bridges the custom HTTP API (port 9876) to standard MCP JSON-RPC 2.0 stdio protocol.
* This allows any MCP client (Claude Code, Kiro, Cursor, Cline, etc.) to use all 78 Burp tools.
*
* Cross-platform: Works on Windows, Linux (Kali), macOS.
*
* Usage in MCP config (all platforms):
* { "command": "node", "args": ["<path-to-this-file>/mcp-bridge.js"] }
*
* Environment variables:
* BURP_MCP_HOST - Burp HTTP API host (default: 127.0.0.1)
* BURP_MCP_PORT - Burp HTTP API port (default: 9876)
*/
const http = require('http');
const readline = require('readline');
const fs = require('fs');
const os = require('os');
const path = require('path');
const BURP_HOST = process.env.BURP_MCP_HOST || '127.0.0.1';
const BURP_PORT = parseInt(process.env.BURP_MCP_PORT || '9876', 10);
function resolveToken() {
if (process.env.BURP_MCP_TOKEN) return process.env.BURP_MCP_TOKEN;
try {
const tokenFile = path.join(os.homedir(), '.burp-mcp-token');
if (fs.existsSync(tokenFile)) return fs.readFileSync(tokenFile, 'utf8').trim();
} catch (e) { /* ignore */ }
return null;
}
const BURP_TOKEN = resolveToken();
const AUTH_HEADERS = BURP_TOKEN ? { 'Authorization': `Bearer ${BURP_TOKEN}` } : {};
let TOOLS = null;
let toolsRequest = null;
async function fetchTools() {
return new Promise((resolve, reject) => {
const req = http.request({
hostname: BURP_HOST, port: BURP_PORT, path: '/tools', method: 'GET',
headers: AUTH_HEADERS
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode !== 200) {
reject(new Error(`Cannot fetch tools: HTTP ${res.statusCode} ${data.slice(0, 200)}`));
return;
}
try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
});
});
req.on('error', reject);
req.end();
});
}
function disconnectedResponse(id) {
return {
jsonrpc: '2.0',
id,
error: {
code: -32000,
message: `Burp MCP not connected at ${BURP_HOST}:${BURP_PORT}. Start Burp with the "MCP Full Control" extension loaded, then retry.`
}
};
}
async function ensureTools() {
if (TOOLS) return TOOLS;
if (!toolsRequest) {
toolsRequest = fetchTools()
.then(tools => {
TOOLS = tools;
process.stderr.write(`[burp-mcp-bridge] Connected to Burp. ${TOOLS.length} tools available.\n`);
return TOOLS;
})
.finally(() => {
toolsRequest = null;
});
}
return toolsRequest;
}
async function callTool(toolName, params) {
return new Promise((resolve, reject) => {
const body = JSON.stringify({ tool: toolName, params: params || {} });
const req = http.request({
hostname: BURP_HOST, port: BURP_PORT, path: '/', method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), ...AUTH_HEADERS }
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode === 403) {
reject(new Error('Burp MCP rejected the request: missing or invalid BURP_MCP_TOKEN. Set BURP_MCP_TOKEN to match the token in ~/.burp-mcp-token.'));
return;
}
try { resolve(JSON.parse(data)); } catch (e) { resolve({ error: data }); }
});
});
req.on('error', (err) => {
reject(new Error(
`Cannot reach Burp MCP at ${BURP_HOST}:${BURP_PORT} (${err.code || err.message}). ` +
`Ensure Burp Suite is running with the "MCP Full Control" extension loaded. ` +
`If the port differs, set BURP_MCP_PORT and -Dburp.mcp.port=<same> on Burp.`
));
});
req.write(body);
req.end();
});
}
function buildToolDefinitions(toolNames) {
return toolNames.map(name => ({
name: `burp_${name}`,
description: getToolDescription(name),
inputSchema: {
type: 'object',
properties: getToolParams(name),
}
}));
}
function getToolDescription(name) {
const descriptions = {
proxy_history: 'Get Burp proxy history with optional filtering by URL, method, status code',
proxy_detail: 'Get full request/response details for a specific proxy history item by index',
proxy_websocket: 'Get WebSocket message history',
proxy_clear: 'Clear proxy history',
proxy_history_filtered: 'Filter proxy history by annotation color or notes',
send_request: 'Send an HTTP request through Burp and get the response',
send_to_repeater: 'Send a raw request to Burp Repeater tab',
repeater_send: 'Send a request and get response (like Repeater)',
repeater_modify_send: 'Modify headers/body of a request then send it',
send_to_intruder: 'Send a request to Burp Intruder',
intruder_attack: 'Run a numeric range brute force attack (synchronous)',
intruder_attack_async: 'Run a multi-threaded numeric range brute force attack',
intruder_attack_wordlist: 'Run a wordlist-based attack',
intruder_pitchfork: 'Run a Pitchfork attack (parallel multi-param)',
intruder_cluster_bomb: 'Run a Cluster Bomb attack (cartesian product multi-param)',
intruder_battering_ram: 'Run a Battering Ram attack (same payload all positions)',
intruder_with_options: 'Run attack with advanced options (throttle, encoding, grep, timing)',
sitemap: 'Get site map entries with optional URL prefix filter',
target_info: 'Get target information (hosts, technologies detected)',
intercept_toggle: 'Enable or disable proxy intercept',
encode: 'Encode a string (base64, url, hex)',
decode: 'Decode a string (base64, url)',
convert_request: 'Convert HTTP request method (e.g. GET to POST)',
export_request: 'Export a request as curl command',
generate_csrf_poc: 'Generate a CSRF proof-of-concept HTML page',
extract_from_response: 'Extract data from a response using regex',
payload_process: 'Process a payload (hash, encode, reverse, etc.)',
scan: 'Start a vulnerability scan',
scan_active: 'Start active scan on a specific request',
scan_results: 'Get scan results (discovered vulnerabilities)',
scan_issue_detail: 'Get detailed information about a specific scan issue',
crawl: 'Start crawling a URL (adds to scope)',
get_scope: 'Check if a URL is in Burp scope',
add_to_scope: 'Add a URL to Burp scope',
remove_from_scope: 'Remove a URL from Burp scope',
collaborator_generate: 'Generate Burp Collaborator payloads for OOB testing',
collaborator_poll: 'Poll for Collaborator interactions (DNS/HTTP callbacks)',
search_history: 'Search proxy history with regex (in URL, request, or response)',
highlight: 'Highlight a proxy history item with a color',
annotate: 'Add a note/annotation to a proxy history item',
compare: 'Compare two proxy history responses (diff)',
export_config: 'Export Burp project configuration as JSON',
import_config: 'Import Burp project configuration from JSON',
set_upstream_proxy: 'Set upstream proxy (SOCKS/HTTP) for all Burp traffic',
set_dns_override: 'Override DNS resolution for a hostname',
set_http2: 'Enable or disable HTTP/2',
cookie_jar: 'View cookies in Burp cookie jar (with optional domain filter)',
token_analysis: 'Analyze token entropy and randomness',
sequencer: 'Analyze a batch of tokens for randomness quality',
save_project: 'Save the current Burp project',
burp_version: 'Get Burp Suite version information',
add_issue: 'Manually add a vulnerability issue to the site map',
register_http_handler: 'Register an auto-modify rule for HTTP requests (add header or replace text)',
remove_http_handler: 'Remove/clear HTTP handler rules',
register_proxy_rule: 'Register a proxy intercept rule (intercept URLs containing a string)',
remove_proxy_rule: 'Remove/clear proxy intercept rules',
extensions_list: 'Get information about loaded extensions',
log: 'Write a message to Burp extension output log',
audit_log: 'View audit log entries',
privacy_mode: 'Set privacy mode (strict/off)',
scope_gate: 'Enable/disable scope gate',
inline_fuzzer: 'FUZZ marker fuzzing',
race_condition: 'Race condition testing',
access_control_sweep: 'Test different auth levels',
injection_probe: 'SQLi/SSTI/LFI probe',
jwt_attack: 'JWT attacks (alg:none)',
jwt_decode: 'Decode JWT tokens',
session_remove_rule: 'Remove session rule',
session_list_rules: 'List session rules',
session_create_rule: 'Create session handling rule',
passive_intel: 'Extract secrets from proxy history',
websocket_list: 'List active WebSockets',
websocket_close: 'Close WebSocket',
websocket_send_binary: 'Send binary on WebSocket',
websocket_send_text: 'Send text on WebSocket',
websocket_create: 'Create WebSocket connection',
send_request_parallel: 'Send parallel HTTP requests',
cookie_jar_set: 'Set cookie in Burp cookie jar',
};
return descriptions[name] || `Burp Suite tool: ${name}`;
}
function getToolParams(name) {
// Common parameter schemas
const schemas = {
proxy_history: { limit: {type:'number',description:'Max items (default 100)'}, offset: {type:'number'}, url_filter: {type:'string'}, method_filter: {type:'string'}, status_filter: {type:'number'} },
proxy_detail: { index: {type:'number',description:'History item index'} },
proxy_websocket: { limit: {type:'number'} },
proxy_history_filtered: { has_notes: {type:'string'}, color: {type:'string'}, limit: {type:'number'} },
send_request: { method: {type:'string'}, url: {type:'string',description:'Full URL'}, body: {type:'string'}, headers: {type:'object'} },
repeater_send: { request: {type:'string',description:'Raw HTTP request'}, host: {type:'string'}, port: {type:'number'}, https: {type:'boolean'} },
repeater_modify_send: { request: {type:'string'}, host: {type:'string'}, port: {type:'number'}, https: {type:'boolean'}, replace_header: {type:'object'}, add_header: {type:'object'}, replace_body: {type:'string'} },
send_to_repeater: { request: {type:'string'}, tab_name: {type:'string'} },
send_to_intruder: { request: {type:'string'} },
intruder_attack: { url_template: {type:'string',description:'URL with @@ placeholder'}, from: {type:'number'}, to: {type:'number'}, pad_digits: {type:'number'}, method: {type:'string'}, headers: {type:'object'}, success_length_not: {type:'number'}, success_contains: {type:'string'} },
intruder_attack_async: { url_template: {type:'string'}, from: {type:'number'}, to: {type:'number'}, pad_digits: {type:'number'}, method: {type:'string'}, headers: {type:'object'}, success_length_not: {type:'number'}, threads: {type:'number'} },
intruder_attack_wordlist: { url_template: {type:'string'}, wordlist: {type:'array',items:{type:'string'}}, method: {type:'string'}, headers: {type:'object'}, success_length_not: {type:'number'}, body_template: {type:'string'} },
intruder_pitchfork: { url_template: {type:'string'}, placeholders: {type:'object'}, method: {type:'string'}, headers: {type:'object'}, success_length_not: {type:'number'} },
intruder_cluster_bomb: { url_template: {type:'string'}, placeholders: {type:'object'}, method: {type:'string'}, headers: {type:'object'}, success_length_not: {type:'number'}, max_requests: {type:'number'} },
intruder_battering_ram: { url_template: {type:'string'}, wordlist: {type:'array',items:{type:'string'}}, placeholder: {type:'string'}, method: {type:'string'}, headers: {type:'object'}, success_length_not: {type:'number'} },
intruder_with_options: { url_template: {type:'string'}, from: {type:'number'}, to: {type:'number'}, pad_digits: {type:'number'}, method: {type:'string'}, headers: {type:'object'}, success_length_not: {type:'number'}, throttle_ms: {type:'number'}, payload_prefix: {type:'string'}, payload_suffix: {type:'string'}, payload_encoding: {type:'string'}, grep_extract: {type:'string'}, record_time: {type:'boolean'} },
sitemap: { url_prefix: {type:'string'}, limit: {type:'number'} },
target_info: { url: {type:'string'} },
intercept_toggle: { enable: {type:'boolean'} },
encode: { input: {type:'string'}, type: {type:'string',description:'base64, url, or hex'} },
decode: { input: {type:'string'}, type: {type:'string',description:'base64 or url'} },
convert_request: { request: {type:'string'}, convert_to: {type:'string'} },
export_request: { request: {type:'string'}, host: {type:'string'}, format: {type:'string',description:'curl or python'}, https: {type:'boolean'} },
generate_csrf_poc: { request: {type:'string'}, host: {type:'string'}, https: {type:'boolean'} },
extract_from_response: { index: {type:'number'}, regex: {type:'string'} },
payload_process: { input: {type:'string'}, operation: {type:'string',description:'base64_encode/decode, url_encode/decode, md5, sha1, sha256, hex_encode, lowercase, uppercase, reverse, length'} },
scan_active: { request: {type:'string'}, host: {type:'string'}, port: {type:'number'}, https: {type:'boolean'} },
scan_results: { limit: {type:'number'} },
scan_issue_detail: { index: {type:'number'} },
crawl: { url: {type:'string'} },
get_scope: { url: {type:'string'} },
add_to_scope: { url: {type:'string'} },
remove_from_scope: { url: {type:'string'} },
collaborator_generate: { count: {type:'number'} },
search_history: { regex: {type:'string'}, search_in: {type:'string',description:'url, request, or response'}, limit: {type:'number'} },
highlight: { index: {type:'number'}, color: {type:'string'} },
annotate: { index: {type:'number'}, note: {type:'string'} },
compare: { index1: {type:'number'}, index2: {type:'number'} },
import_config: { config: {type:'string'} },
set_upstream_proxy: { proxy_host: {type:'string'}, proxy_port: {type:'number'}, type: {type:'string'} },
set_dns_override: { hostname: {type:'string'}, ip: {type:'string'} },
set_http2: { enable: {type:'boolean'} },
cookie_jar: { limit: {type:'number'}, domain: {type:'string'} },
token_analysis: { tokens: {type:'array',items:{type:'string'}} },
sequencer: { tokens: {type:'array',items:{type:'string'}} },
add_issue: { name: {type:'string'}, url: {type:'string'}, detail: {type:'string'}, severity: {type:'string'}, confidence: {type:'string'} },
register_http_handler: { header_name: {type:'string'}, header_value: {type:'string'}, match: {type:'string'}, replace: {type:'string'} },
register_proxy_rule: { url_contains: {type:'string'} },
log: { message: {type:'string'}, level: {type:'string'} },
audit_log: { limit: {type:'number'} },
privacy_mode: { mode: {type:'string'} },
scope_gate: { action: {type:'string'} },
inline_fuzzer: { template: {type:'string'}, host: {type:'string'}, wordlist: {type:'array'} },
race_condition: { request: {type:'string'}, host: {type:'string'}, count: {type:'number'} },
access_control_sweep: { request: {type:'string'}, host: {type:'string'}, auth_headers: {type:'string'} },
injection_probe: { url: {type:'string'}, param: {type:'string'}, type: {type:'string'} },
jwt_attack: { token: {type:'string'}, attack: {type:'string'} },
jwt_decode: { token: {type:'string'} },
session_remove_rule: {},
session_list_rules: {},
session_create_rule: { find: {type:'string'}, replace: {type:'string'} },
passive_intel: { limit: {type:'number'} },
websocket_list: {},
websocket_close: { id: {type:'string'} },
websocket_send_binary: { id: {type:'string'}, data: {type:'string'} },
websocket_send_text: { id: {type:'string'}, text: {type:'string'} },
websocket_create: { host: {type:'string'}, port: {type:'number'} },
send_request_parallel: { requests: {type:'array'} },
cookie_jar_set: { url: {type:'string'}, name: {type:'string'}, value: {type:'string'} },
};
return schemas[name] || {};
}
async function handleRequest(msg) {
const { method, id, params } = msg;
switch (method) {
case 'initialize':
return { jsonrpc: '2.0', id, result: {
protocolVersion: '2024-11-05',
capabilities: { tools: {} },
serverInfo: { name: 'burpsuite-mcp', version: '2.0.0' }
}};
case 'notifications/initialized':
return null; // No response needed
case 'tools/list':
try {
const tools = await ensureTools();
return { jsonrpc: '2.0', id, result: { tools: buildToolDefinitions(tools) } };
} catch (err) {
return disconnectedResponse(id);
}
case 'tools/call': {
try {
await ensureTools();
} catch (err) {
return disconnectedResponse(id);
}
if (!params || typeof params.name !== 'string') {
return { jsonrpc: '2.0', id, error: { code: -32602, message: 'tools/call requires params.name' } };
}
const toolName = params.name.replace(/^burp_/, '');
const toolArgs = params.arguments || {};
try {
const result = await callTool(toolName, toolArgs);
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } };
} catch (err) {
TOOLS = null;
return { jsonrpc: '2.0', id, error: { code: -1, message: err.message || 'Tool call failed' } };
}
}
default:
return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}` } };
}
}
// Main stdio loop
async function main() {
try {
await ensureTools();
} catch (e) {
process.stderr.write(`[burp-mcp-bridge] WARNING: Cannot connect to Burp at ${BURP_HOST}:${BURP_PORT}. Start Burp first.\n`);
}
const rl = readline.createInterface({ input: process.stdin, terminal: false });
let pending = 0;
let stdinClosed = false;
rl.on('line', (line) => {
if (!line.trim()) return;
let msg;
try {
msg = JSON.parse(line);
} catch (e) {
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }) + '\n');
return;
}
pending++;
handleRequest(msg)
.then(response => {
if (response) process.stdout.write(JSON.stringify(response) + '\n');
})
.catch(err => {
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: msg.id ?? null, error: { code: -1, message: err.message || 'Handler error' } }) + '\n');
})
.finally(() => {
pending--;
if (stdinClosed && pending === 0) process.exit(0);
});
});
rl.on('close', () => {
stdinClosed = true;
if (pending === 0) process.exit(0);
});
}
main();