reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
181
reverse-skill-repo/burp-mcp-full/README.md
Normal file
181
reverse-skill-repo/burp-mcp-full/README.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# BurpSuite MCP Full Control Extension
|
||||
|
||||
通过 MCP 协议完整控制 BurpSuite 的所有核心功能。跨平台支持 Windows / Linux (Kali) / macOS。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 编译扩展
|
||||
|
||||
**Windows**:
|
||||
```cmd
|
||||
cd burp-mcp-full
|
||||
build.bat
|
||||
```
|
||||
|
||||
**Linux / Kali / macOS**:
|
||||
```bash
|
||||
cd burp-mcp-full
|
||||
chmod +x build.sh
|
||||
./build.sh
|
||||
```
|
||||
|
||||
构建脚本会自动:检测 JDK 21+、下载依赖(montoya-api 2025.5 / gson / nanohttpd)、编译、把扩展描述符(`META-INF/extensions/burp-extension.properties`)打入 jar、打包 fat jar。无需 Gradle。
|
||||
|
||||
输出:`build/libs/burp-mcp-full.jar`。
|
||||
|
||||
### 2. 加载到 Burp
|
||||
|
||||
```
|
||||
Burp Suite → Extensions → Add → Java → 选择 build/libs/burp-mcp-full.jar
|
||||
```
|
||||
|
||||
加载后在 Output 看到:
|
||||
```
|
||||
[MCP] Server started on http://127.0.0.1:9876
|
||||
```
|
||||
|
||||
### 3. 鉴权(v2 起默认启用)
|
||||
|
||||
扩展启动时自动生成随机 token 并写入 `~/.burp-mcp-token`。`mcp-bridge.js` 会自动读取该文件并在每个请求携带 `Authorization: Bearer <token>` 头,无需手动配置。
|
||||
|
||||
需要固定 token 时(例如多个客户端共享),可用:
|
||||
- JVM 参数:`-Dburp.mcp.token=<token>`
|
||||
- 环境变量:`BURP_MCP_TOKEN=<token>`(同时用于 bridge 侧)
|
||||
|
||||
所有 `/health`、`/tools`、`/`(POST)请求均要求携带该头,否则返回 403。CORS 已收敛为仅允许 `http://127.0.0.1` 来源。
|
||||
|
||||
### 4. 配置 MCP 客户端
|
||||
|
||||
在任何 MCP 客户端(Claude Code / Kiro / Cursor / Cline / Windsurf)中添加(stdio 模式):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"burpsuite": {
|
||||
"command": "node",
|
||||
"args": ["<本目录路径>/mcp-bridge.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 开始使用
|
||||
|
||||
对 AI 说:"分析 Burp 代理历史中的请求,找出安全漏洞"
|
||||
|
||||
## 功能列表
|
||||
|
||||
扩展暴露 78 个工具。常用分类如下(完整列表见 `src/main/java/com/burpmcp/McpHttpServer.java` 的 `getToolList()`,或访问 `GET http://127.0.0.1:9876/tools`,需携带 Authorization 头):
|
||||
|
||||
| 分类 | 工具 |
|
||||
|------|------|
|
||||
| Proxy 历史 | `proxy_history`, `proxy_detail`, `proxy_history_filtered`, `proxy_websocket`, `proxy_clear`, `search_history`, `highlight`, `annotate`, `compare` |
|
||||
| 发送请求 | `send_request`, `send_to_repeater`, `repeater_send`, `repeater_modify_send`, `send_to_intruder` |
|
||||
| Intruder 攻击 | `intruder_attack`, `intruder_attack_async`, `intruder_attack_wordlist`, `intruder_pitchfork`, `intruder_cluster_bomb`, `intruder_battering_ram`, `intruder_with_options`, `payload_process` |
|
||||
| 扫描 / 爬取 | `scan`(主动/被动), `scan_active`, `scan_results`, `scan_issue_detail`, `crawl`, `sequencer` |
|
||||
| Scope / Sitemap | `sitemap`, `target_info`, `get_scope`, `add_to_scope`, `remove_from_scope`, `add_issue` |
|
||||
| 拦截 / 规则 | `intercept_toggle`, `register_http_handler`, `remove_http_handler`, `register_proxy_rule`, `remove_proxy_rule` |
|
||||
| 编解码 | `encode`, `decode`, `convert_request`, `export_request`, `generate_csrf_poc`, `extract_from_response`, `token_analysis` |
|
||||
| Collaborator | `collaborator_generate`, `collaborator_poll` |
|
||||
| 配置 | `export_config`, `import_config`, `set_upstream_proxy`, `set_dns_override`, `set_http2`, `cookie_jar`, `save_project`, `burp_version`, `extensions_list`, `log` |
|
||||
|
||||
> 扫描/爬取(`scan`、`scan_active`、`crawl`)需要 **Burp Professional**。Community 版会返回明确的许可证错误。手动添加的 issue(`add_issue`)会写入 Site map。
|
||||
|
||||
## 关键工具参数
|
||||
|
||||
### `intruder_attack` — 自动化枚举攻击
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `url_template` | URL 模板,占位符默认 `@@` |
|
||||
| `placeholder` | 占位符字符串(默认 `@@`) |
|
||||
| `from` / `to` | 枚举起止值 |
|
||||
| `pad_digits` | 补零位数(0 不补) |
|
||||
| `method` | HTTP 方法(默认 GET) |
|
||||
| `body_template` | 请求体模板(含占位符) |
|
||||
| `headers` | 请求头对象 |
|
||||
| `success_length_not` | 命中条件:响应长度 ≠ 此值 |
|
||||
| `success_contains` | 命中条件:响应体包含此字符串 |
|
||||
|
||||
### `scan` — 启动审计
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `url` | 目标 URL(必填,自动加入 scope) |
|
||||
| `mode` | `active`(默认)或 `passive` |
|
||||
|
||||
启动后用 `scan_results` 轮询 issues 与活动审计状态(请求数、错误数、插入点数)。
|
||||
|
||||
### `register_proxy_rule` — 代理请求拦截规则
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `url_contains` | 命中条件:URL 包含此串 |
|
||||
| `intercept` | `true` 拦截 / `false` 放行不拦截(默认 true) |
|
||||
|
||||
通过 `remove_proxy_rule` 注销规则(基于 `Registration.deregister()`,真正从 Burp 卸载)。
|
||||
|
||||
## 调用示例
|
||||
|
||||
### 查看代理历史
|
||||
```json
|
||||
POST http://127.0.0.1:9876
|
||||
{"tool": "proxy_history", "params": {"limit": 10, "url_filter": "personalblog"}}
|
||||
```
|
||||
|
||||
### 发送请求
|
||||
```json
|
||||
POST http://127.0.0.1:9876
|
||||
{"tool": "send_request", "params": {"method": "GET", "url": "https://example.com/api/test"}}
|
||||
```
|
||||
|
||||
### 自动化枚举攻击(核心功能)
|
||||
```json
|
||||
POST http://127.0.0.1:9876
|
||||
{
|
||||
"tool": "intruder_attack",
|
||||
"params": {
|
||||
"url_template": "https://target.com/api/verify?code=@@",
|
||||
"method": "POST",
|
||||
"from": 0,
|
||||
"to": 999999,
|
||||
"pad_digits": 6,
|
||||
"success_length_not": 176,
|
||||
"headers": {"User-Agent": "Mozilla/5.0"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 开关拦截
|
||||
```json
|
||||
POST http://127.0.0.1:9876
|
||||
{"tool": "intercept_toggle", "params": {"enable": false}}
|
||||
```
|
||||
|
||||
## 端口配置
|
||||
|
||||
默认监听 `127.0.0.1:9876`。如需更改(例如与 PortSwigger 官方 MCP 扩展同端口冲突):
|
||||
|
||||
1. **Burp 侧**:启动 Burp 时传 JVM 参数 `-Dburp.mcp.port=9877`,或设环境变量 `BURP_MCP_PORT=9877`。
|
||||
2. **桥接侧**:MCP 客户端配置里设环境变量 `BURP_MCP_PORT=9877` 与 `BURP_MCP_HOST=127.0.0.1`。
|
||||
|
||||
两侧端口必须一致。若 Burp 未运行或端口不通,桥接会在 `tools/list` 与 `tools/call` 返回明确的连接错误指引。
|
||||
|
||||
## 故障排查
|
||||
|
||||
| 现象 | 排查 |
|
||||
|------|------|
|
||||
| Burp Output 无 "[MCP] Server started" | 端口被占用或扩展加载失败,查 Burp Errors 面板 |
|
||||
| MCP 客户端报 "Burp MCP not connected" | 确认 Burp 已运行且扩展已加载;确认两侧端口一致 |
|
||||
| 扫描返回 "requires Burp Professional" | 正常,Community 版不支持 Scanner API |
|
||||
| `remove_http_handler` / `remove_proxy_rule` 无效 | 确认之前 `register_*` 返回 success=true |
|
||||
|
||||
## 源码构建(Gradle 可选)
|
||||
|
||||
```bash
|
||||
cd burp-mcp-full
|
||||
gradle jar # 需本机已装 Gradle 8.7+
|
||||
# 输出: build/libs/burp-mcp-full.jar
|
||||
```
|
||||
|
||||
> 推荐使用 `build.bat` / `build.sh`(零依赖,自动下载 jar)。Gradle 路径仅作备选。
|
||||
94
reverse-skill-repo/burp-mcp-full/build.bat
Normal file
94
reverse-skill-repo/burp-mcp-full/build.bat
Normal file
@@ -0,0 +1,94 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: BurpSuite MCP Full Control - Windows Build Script
|
||||
:: Produces: build\libs\burp-mcp-full.jar (fat jar, Burp-loadable)
|
||||
|
||||
:: ===== Detect JDK (JAVA_HOME -> PATH -> common install dirs) =====
|
||||
set "JAVAC="
|
||||
set "JAR_CMD="
|
||||
|
||||
if defined JAVA_HOME (
|
||||
if exist "%JAVA_HOME%\bin\javac.exe" (
|
||||
set "JAVAC=%JAVA_HOME%\bin\javac.exe"
|
||||
set "JAR_CMD=%JAVA_HOME%\bin\jar.exe"
|
||||
)
|
||||
)
|
||||
|
||||
if not defined JAVAC (
|
||||
where javac >nul 2>nul
|
||||
if not errorlevel 1 (
|
||||
for /f "delims=" %%i in ('where javac') do (
|
||||
set "JAVAC=%%i"
|
||||
goto :javac_found
|
||||
)
|
||||
)
|
||||
)
|
||||
:javac_found
|
||||
|
||||
if not defined JAVAC (
|
||||
for %%P in (
|
||||
"C:\Program Files\Java"
|
||||
"C:\Program Files\Eclipse Adoptium"
|
||||
"C:\Program Files\Microsoft\jdk-21.0.5.11-hotspot"
|
||||
) do (
|
||||
if exist %%P (
|
||||
for /d %%D in (%%P\jdk-2*) do (
|
||||
if exist "%%D\bin\javac.exe" (
|
||||
set "JAVAC=%%D\bin\javac.exe"
|
||||
set "JAR_CMD=%%D\bin\jar.exe"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if not defined JAVAC (
|
||||
echo ERROR: javac not found in JAVA_HOME, PATH, or common install dirs.
|
||||
echo Set JAVA_HOME to your JDK 21+ install directory, e.g.:
|
||||
echo set JAVA_HOME=C:\Program Files\Java\jdk-21
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set "SRC=src\main\java\com\burpmcp"
|
||||
set "RES=src\main\resources"
|
||||
set "LIB=lib"
|
||||
set "OUT=build\classes"
|
||||
set "DIST=build\libs"
|
||||
|
||||
echo [1/4] Downloading dependencies...
|
||||
if not exist %LIB% mkdir %LIB%
|
||||
if not exist %LIB%\montoya-api.jar curl -sL -o %LIB%\montoya-api.jar "https://repo1.maven.org/maven2/net/portswigger/burp/extensions/montoya-api/2025.5/montoya-api-2025.5.jar"
|
||||
if not exist %LIB%\gson.jar curl -sL -o %LIB%\gson.jar "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar"
|
||||
if not exist %LIB%\nanohttpd.jar curl -sL -o %LIB%\nanohttpd.jar "https://repo1.maven.org/maven2/org/nanohttpd/nanohttpd/2.3.1/nanohttpd-2.3.1.jar"
|
||||
|
||||
echo [2/4] Compiling...
|
||||
if not exist %OUT% mkdir %OUT%
|
||||
"%JAVAC%" --release 21 -encoding UTF-8 -cp "%LIB%\montoya-api.jar;%LIB%\gson.jar;%LIB%\nanohttpd.jar" -d %OUT% %SRC%\*.java
|
||||
if errorlevel 1 (
|
||||
echo COMPILE FAILED
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Copy resources (extension descriptor) so the jar is loadable by Burp
|
||||
xcopy /e /i /y "%RES%\META-INF" "%OUT%\META-INF" >nul
|
||||
|
||||
echo [3/4] Packaging fat jar...
|
||||
if not exist %DIST% mkdir %DIST%
|
||||
|
||||
:: Extract dependencies into classes dir
|
||||
cd %OUT%
|
||||
"%JAR_CMD%" xf ..\..\%LIB%\gson.jar com
|
||||
"%JAR_CMD%" xf ..\..\%LIB%\nanohttpd.jar fi
|
||||
cd ..\..
|
||||
|
||||
:: Create jar
|
||||
"%JAR_CMD%" cf %DIST%\burp-mcp-full.jar -C %OUT% .
|
||||
|
||||
echo [4/4] Done!
|
||||
echo Output: %DIST%\burp-mcp-full.jar
|
||||
echo.
|
||||
echo Install: Burp Suite -^> Extensions -^> Add -^> Java -^> Select %DIST%\burp-mcp-full.jar
|
||||
echo.
|
||||
echo MCP config (any client):
|
||||
echo { "command": "node", "args": ["%~dp0mcp-bridge.js"] }
|
||||
35
reverse-skill-repo/burp-mcp-full/build.gradle
Normal file
35
reverse-skill-repo/burp-mcp-full/build.gradle
Normal file
@@ -0,0 +1,35 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'com.burpmcp'
|
||||
version = '1.0.0'
|
||||
sourceCompatibility = '21'
|
||||
targetCompatibility = '21'
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'net.portswigger.burp.extensions:montoya-api:2025.5'
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
implementation 'org.nanohttpd:nanohttpd:2.3.1'
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Implementation-Title': 'BurpMCP Full Control'
|
||||
attributes 'Implementation-Version': version
|
||||
}
|
||||
// Fat jar - include all dependencies
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
archiveFileName = 'burp-mcp-full.jar'
|
||||
}
|
||||
63
reverse-skill-repo/burp-mcp-full/build.sh
Normal file
63
reverse-skill-repo/burp-mcp-full/build.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# BurpSuite MCP Full Control - Linux / macOS Build Script
|
||||
# Requires: JDK 21+, curl
|
||||
# Produces: build/libs/burp-mcp-full.jar (fat jar, Burp-loadable)
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# ===== Auto-detect JAVA_HOME =====
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if command -v javac >/dev/null 2>&1; then
|
||||
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
|
||||
else
|
||||
echo "ERROR: javac not found. Install JDK 21+ or set JAVA_HOME."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
JAVAC="$JAVA_HOME/bin/javac"
|
||||
JAR="$JAVA_HOME/bin/jar"
|
||||
|
||||
if ! "$JAVAC" -version 2>/dev/null | grep -qE '(1\.)?(2[1-9]|[3-9][0-9])'; then
|
||||
echo "WARNING: JDK 21+ recommended. Detected: $("$JAVAC" -version 2>&1)"
|
||||
fi
|
||||
|
||||
SRC="src/main/java/com/burpmcp"
|
||||
RES="src/main/resources"
|
||||
LIB="lib"
|
||||
OUT="build/classes"
|
||||
DIST="build/libs"
|
||||
MONTOYA="https://repo1.maven.org/maven2/net/portswigger/burp/extensions/montoya-api/2025.5/montoya-api-2025.5.jar"
|
||||
GSON="https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar"
|
||||
NANO="https://repo1.maven.org/maven2/org/nanohttpd/nanohttpd/2.3.1/nanohttpd-2.3.1.jar"
|
||||
|
||||
echo "[1/4] Downloading dependencies..."
|
||||
mkdir -p "$LIB"
|
||||
[ -f "$LIB/montoya-api.jar" ] || curl -sL -o "$LIB/montoya-api.jar" "$MONTOYA"
|
||||
[ -f "$LIB/gson.jar" ] || curl -sL -o "$LIB/gson.jar" "$GSON"
|
||||
[ -f "$LIB/nanohttpd.jar" ] || curl -sL -o "$LIB/nanohttpd.jar" "$NANO"
|
||||
|
||||
echo "[2/4] Compiling..."
|
||||
mkdir -p "$OUT"
|
||||
"$JAVAC" --release 21 -encoding UTF-8 -cp "$LIB/montoya-api.jar:$LIB/gson.jar:$LIB/nanohttpd.jar" -d "$OUT" "$SRC"/*.java
|
||||
|
||||
# Copy resources (extension descriptor) so the jar is loadable by Burp
|
||||
cp -r "$RES/META-INF" "$OUT/"
|
||||
|
||||
echo "[3/4] Packaging fat jar..."
|
||||
mkdir -p "$DIST"
|
||||
cd "$OUT"
|
||||
"$JAR" xf "../../$LIB/gson.jar" com
|
||||
"$JAR" xf "../../$LIB/nanohttpd.jar" fi
|
||||
cd ../..
|
||||
"$JAR" cf "$DIST/burp-mcp-full.jar" -C "$OUT" .
|
||||
|
||||
echo "[4/4] Done!"
|
||||
echo "Output: $DIST/burp-mcp-full.jar"
|
||||
echo ""
|
||||
echo "Install: Burp Suite -> Extensions -> Add -> Java -> Select $DIST/burp-mcp-full.jar"
|
||||
echo ""
|
||||
echo "MCP config (any client):"
|
||||
echo " { \"command\": \"node\", \"args\": [\"$SCRIPT_DIR/mcp-bridge.js\"] }"
|
||||
BIN
reverse-skill-repo/burp-mcp-full/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
reverse-skill-repo/burp-mcp-full/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
reverse-skill-repo/burp-mcp-full/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
reverse-skill-repo/burp-mcp-full/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
252
reverse-skill-repo/burp-mcp-full/gradlew
vendored
Normal file
252
reverse-skill-repo/burp-mcp-full/gradlew
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
reverse-skill-repo/burp-mcp-full/gradlew.bat
vendored
Normal file
94
reverse-skill-repo/burp-mcp-full/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
379
reverse-skill-repo/burp-mcp-full/mcp-bridge.js
Normal file
379
reverse-skill-repo/burp-mcp-full/mcp-bridge.js
Normal file
@@ -0,0 +1,379 @@
|
||||
#!/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();
|
||||
1
reverse-skill-repo/burp-mcp-full/settings.gradle
Normal file
1
reverse-skill-repo/burp-mcp-full/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'burp-mcp-full'
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.burpmcp;
|
||||
|
||||
import burp.api.montoya.BurpExtension;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.logging.Logging;
|
||||
|
||||
/**
|
||||
* BurpSuite MCP Full Control Extension
|
||||
* Exposes ALL Burp functionality via MCP protocol on HTTP port 9876
|
||||
*
|
||||
* Features:
|
||||
* - Proxy history access & filtering
|
||||
* - Intruder attack control (create, configure payloads, start, get results)
|
||||
* - Repeater tab management
|
||||
* - Scanner control
|
||||
* - Sitemap access
|
||||
* - Send HTTP requests through Burp
|
||||
* - Intercept control
|
||||
* - Encoding/decoding utilities
|
||||
*/
|
||||
public class BurpMcpExtension implements BurpExtension {
|
||||
|
||||
private MontoyaApi api;
|
||||
private Logging logging;
|
||||
private McpHttpServer server;
|
||||
|
||||
@Override
|
||||
public void initialize(MontoyaApi api) {
|
||||
this.api = api;
|
||||
this.logging = api.logging();
|
||||
|
||||
api.extension().setName("MCP Full Control");
|
||||
|
||||
int port = resolvePort(logging);
|
||||
try {
|
||||
server = new McpHttpServer(api, port);
|
||||
server.start();
|
||||
logging.logToOutput("[MCP] Server started on http://127.0.0.1:" + port);
|
||||
logging.logToOutput("[MCP] Configure port via -Dburp.mcp.port=<n> or BURP_MCP_PORT env var (default 9876)");
|
||||
logging.logToOutput("[MCP] Auth token: ~/.burp-mcp-token (override with -Dburp.mcp.token or BURP_MCP_TOKEN)");
|
||||
logging.logToOutput("[MCP] Tools: proxy_history, send_request, intruder_attack, repeater, scanner, sitemap, intercept, encode/decode");
|
||||
} catch (Exception e) {
|
||||
logging.logToError("[MCP] Failed to start server on port " + port + ": " + e.getMessage());
|
||||
logging.logToError("[MCP] If port is in use, set -Dburp.mcp.port=<other> and restart Burp.");
|
||||
}
|
||||
|
||||
api.extension().registerUnloadingHandler(() -> {
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
logging.logToOutput("[MCP] Server stopped");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int resolvePort(Logging logging) {
|
||||
String prop = System.getProperty("burp.mcp.port");
|
||||
String env = System.getenv("BURP_MCP_PORT");
|
||||
String raw = prop != null ? prop : env;
|
||||
if (raw == null || raw.isBlank()) return 9876;
|
||||
try {
|
||||
int p = Integer.parseInt(raw.trim());
|
||||
if (p < 1 || p > 65535) throw new IllegalArgumentException("out of range");
|
||||
if (prop != null) logging.logToOutput("[MCP] Port from -Dburp.mcp.port: " + p);
|
||||
else logging.logToOutput("[MCP] Port from BURP_MCP_PORT: " + p);
|
||||
return p;
|
||||
} catch (Exception e) {
|
||||
logging.logToError("[MCP] Invalid port '" + raw + "', falling back to 9876. " + e.getMessage());
|
||||
return 9876;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
extension.name=MCP Full Control
|
||||
extension.class=com.burpmcp.BurpMcpExtension
|
||||
141
reverse-skill-repo/burp-mcp-full/test/mcp-bridge.test.js
Normal file
141
reverse-skill-repo/burp-mcp-full/test/mcp-bridge.test.js
Normal file
@@ -0,0 +1,141 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { once } = require('node:events');
|
||||
const http = require('node:http');
|
||||
const net = require('node:net');
|
||||
const { spawn } = require('node:child_process');
|
||||
const { test } = require('node:test');
|
||||
const path = require('node:path');
|
||||
|
||||
class LineReader {
|
||||
constructor(stream) {
|
||||
this.lines = [];
|
||||
this.waiters = [];
|
||||
this.buffer = '';
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', chunk => {
|
||||
this.buffer += chunk;
|
||||
const parts = this.buffer.split('\n');
|
||||
this.buffer = parts.pop();
|
||||
for (const line of parts) {
|
||||
this.push(line.replace(/\r$/, ''));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
push(line) {
|
||||
const waiter = this.waiters.shift();
|
||||
if (waiter) {
|
||||
waiter.resolve(line);
|
||||
} else {
|
||||
this.lines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
next(timeoutMs = 5000) {
|
||||
if (this.lines.length > 0) return Promise.resolve(this.lines.shift());
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
const index = this.waiters.indexOf(waiter);
|
||||
if (index >= 0) this.waiters.splice(index, 1);
|
||||
reject(new Error('Timed out waiting for a line'));
|
||||
}, timeoutMs);
|
||||
const waiter = {
|
||||
resolve: line => {
|
||||
clearTimeout(timeout);
|
||||
resolve(line);
|
||||
}
|
||||
};
|
||||
this.waiters.push(waiter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getFreePort() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = net.createServer();
|
||||
server.once('error', reject);
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const { port } = server.address();
|
||||
server.close(() => resolve(port));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function close(server) {
|
||||
if (!server) return Promise.resolve();
|
||||
return new Promise(resolve => server.close(() => resolve()));
|
||||
}
|
||||
|
||||
test('reconnects after Burp starts and handles a later tool call', async () => {
|
||||
const port = await getFreePort();
|
||||
const bridgePath = path.join(__dirname, '..', 'mcp-bridge.js');
|
||||
const bridge = spawn(process.execPath, [bridgePath], {
|
||||
env: {
|
||||
...process.env,
|
||||
BURP_MCP_HOST: '127.0.0.1',
|
||||
BURP_MCP_PORT: String(port),
|
||||
BURP_MCP_TOKEN: 'test-token'
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
const stdout = new LineReader(bridge.stdout);
|
||||
const stderr = new LineReader(bridge.stderr);
|
||||
let server;
|
||||
const requests = [];
|
||||
|
||||
try {
|
||||
const warning = await stderr.next();
|
||||
assert.match(warning, /Cannot connect to Burp/);
|
||||
|
||||
server = http.createServer((request, response) => {
|
||||
if (request.method === 'GET' && request.url === '/tools') {
|
||||
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
response.end(JSON.stringify(['proxy_history']));
|
||||
return;
|
||||
}
|
||||
|
||||
let body = '';
|
||||
request.on('data', chunk => { body += chunk; });
|
||||
request.on('end', () => {
|
||||
requests.push(JSON.parse(body));
|
||||
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
response.end(JSON.stringify({ ok: true }));
|
||||
});
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once('error', reject);
|
||||
server.listen(port, '127.0.0.1', resolve);
|
||||
});
|
||||
|
||||
bridge.stdin.write(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: 'tools/list',
|
||||
params: {}
|
||||
}) + '\n');
|
||||
const listResponse = JSON.parse(await stdout.next());
|
||||
assert.equal(listResponse.id, 1);
|
||||
assert.equal(listResponse.result.tools[0].name, 'burp_proxy_history');
|
||||
|
||||
bridge.stdin.write(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 2,
|
||||
method: 'tools/call',
|
||||
params: { name: 'burp_proxy_history', arguments: { limit: 1 } }
|
||||
}) + '\n');
|
||||
const callResponse = JSON.parse(await stdout.next());
|
||||
assert.equal(callResponse.id, 2);
|
||||
assert.deepEqual(requests[0], { tool: 'proxy_history', params: { limit: 1 } });
|
||||
assert.match(callResponse.result.content[0].text, /"ok": true/);
|
||||
} finally {
|
||||
const childClosed = bridge.exitCode === null && bridge.signalCode === null
|
||||
? once(bridge, 'close')
|
||||
: Promise.resolve();
|
||||
bridge.stdin.end();
|
||||
bridge.kill();
|
||||
await close(server);
|
||||
await childClosed.catch(() => {});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user