📋 文档说明: 本文档专门介绍Bettercap HTTP代理模块中所有JavaScript回调函数的参数结构、字段定义和使用方法。所有内容都经过源码验证,确保准确性。
Bettercap的HTTP代理模块提供了四个JavaScript回调函数(源码:modules/http_proxy/http_proxy_script.go):
function onLoad() // 脚本加载时执行
function onRequest(req, res) // 请求拦截回调
function onResponse(req, res) // 响应拦截回调
function onCommand(cmd) // 命令处理回调
| 回调函数 | 执行时机 | 参数 | 返回值 | 描述 |
|---|---|---|---|---|
onLoad() |
脚本加载时 | 无 | 无 | 初始化脚本,设置环境 |
onRequest(req, res) |
每次HTTP请求 | req, res |
req对象 |
拦截和修改HTTP请求 |
onResponse(req, res) |
每次HTTP响应 | req, res |
res对象 |
拦截和修改HTTP响应 |
onCommand(cmd) |
接收命令时 | cmd字符串 |
boolean |
处理外部命令 |
执行时机:脚本首次加载时执行一次
用途:
示例:
function onLoad() {
log_info("HTTP代理脚本已加载");
// 初始化统计变量
stats = {
totalRequests: 0,
totalResponses: 0,
tamperedRequests: 0,
tamperedResponses: 0
};
log_info("初始化完成");
}
执行时机:当用户在Bettercap交互式命令行中输入未被识别的命令时调用
命令输入方式:
# 启动Bettercap后,在交互式命令行中输入
bettercap> stats
bettercap> reset
bettercap> custom-command
参数:
cmd: 字符串,用户输入的完整命令行返回值:
true: 命令已处理,阻止进一步处理false: 命令未处理,传递给其他模块或显示错误源码机制:
// session/session.go - 命令处理流程
func (s *Session) Run(line string) error {
// 1. 检查核心命令
// 2. 检查模块命令
// 3. 检查caplet命令
// 4. 调用UnkCmdCallback(未识别命令回调)
if s.UnkCmdCallback != nil && s.UnkCmdCallback(line) {
return nil
}
// 5. 显示"未知命令"错误
}
// http_proxy_base.go - 注册回调
p.Sess.UnkCmdCallback = func(cmd string) bool {
if p.Script != nil {
return p.Script.OnCommand(cmd) // 调用JavaScript的onCommand
}
return false
}
示例:
function onCommand(cmd) {
// 处理统计命令
if (cmd === "stats") {
log_info("=== 代理统计 ===");
log_info("总请求: " + stats.totalRequests);
log_info("总响应: " + stats.totalResponses);
log_info("篡改请求: " + stats.tamperedRequests);
log_info("篡改响应: " + stats.tamperedResponses);
return true; // 命令已处理,阻止显示错误
}
// 处理重置命令
if (cmd === "reset") {
stats = {totalRequests: 0, totalResponses: 0, tamperedRequests: 0, tamperedResponses: 0};
log_info("统计已重置");
return true;
}
// 处理自定义命令
if (cmd.startsWith("set-threshold ")) {
var threshold = parseInt(cmd.substring(14));
if (!isNaN(threshold)) {
maxRequests = threshold;
log_info("请求阈值设置为: " + threshold);
return true;
}
}
return false; // 命令未识别,显示"未知命令"错误
}
使用说明:
bettercap>后输入命令true阻止错误信息,返回false允许其他处理set-threshold 100)基于 modules/http_proxy/http_proxy_js_request.go 中的结构体定义:
type JSRequest struct {
Client map[string]string // 客户端信息
Method string // HTTP方法
Version string // HTTP版本
Scheme string // 协议
Path string // 路径
Query string // 查询字符串
Hostname string // 主机名
Port string // 端口
ContentType string // Content-Type
Headers string // 头部字符串
Body string // 请求体
req *http.Request // 内部字段
refHash string // 内部字段
bodyRead bool // 内部字段
}
| 字段名 | 类型 | 描述 | 源码位置 | 读取示例 | 修改示例 |
|---|---|---|---|---|---|
Client |
object |
客户端信息对象 | JSRequest.Client |
req.Client.IP |
// 只读字段 |
Client.IP |
string |
客户端IP地址 | NewJSRequest() |
"192.168.1.100" |
- |
Client.MAC |
string |
客户端MAC地址 | NewJSRequest() |
"aa:bb:cc:dd:ee:ff" |
- |
Client.Alias |
string |
客户端别名 | NewJSRequest() |
"admin-pc" |
- |
Method |
string |
HTTP请求方法 | JSRequest.Method |
"GET", "POST" |
req.Method = "POST" |
Version |
string |
HTTP版本 | JSRequest.Version |
"1.1" |
// 通常不修改 |
Scheme |
string |
协议类型 | JSRequest.Scheme |
"http", "https" |
req.Scheme = "https" |
Path |
string |
请求路径 | JSRequest.Path |
"/api/login" |
req.Path = "/api/auth" |
Query |
string |
查询字符串 | JSRequest.Query |
"user=admin&p=123" |
req.Query = "user=test" |
Hostname |
string |
目标主机名 | JSRequest.Hostname |
"example.com" |
req.Hostname = "evil.com" |
Port |
string |
目标端口 | JSRequest.Port |
"80", "443" |
req.Port = "8080" |
ContentType |
string |
Content-Type头部 | JSRequest.ContentType |
"application/json" |
req.ContentType = "text/plain" |
Headers |
string |
完整头部字符串 | JSRequest.Headers |
"User-Agent: Mozilla...\r\n" |
// 使用方法修改 |
Body |
string |
请求体内容 | JSRequest.Body |
// 初始为空 |
req.Body = "modified data" |
| 方法名 | 参数 | 返回值 | 描述 | 源码位置 | 示例 |
|---|---|---|---|---|---|
ReadBody() |
无 | string |
读取请求体并重置底层流 | ReadBody() |
var body = req.ReadBody() |
ParseForm() |
无 | object |
解析表单数据 | ParseForm() |
var form = req.ParseForm() |
GetHeader(name, default) |
string, string |
string |
获取单个头部 | GetHeader() |
req.GetHeader("User-Agent", "unknown") |
GetHeaders(name) |
string |
array |
获取多个同名头部 | GetHeaders() |
req.GetHeaders("Accept") |
SetHeader(name, value) |
string, string |
无 | 设置头部 | SetHeader() |
req.SetHeader("X-Custom", "value") |
RemoveHeader(name) |
string |
无 | 移除头部 | RemoveHeader() |
req.RemoveHeader("Referer") |
基于 modules/http_proxy/http_proxy_js_response.go 中的结构体定义:
type JSResponse struct {
Status int // HTTP状态码
ContentType string // Content-Type
Headers string // 头部字符串
Body string // 响应体
refHash string // 内部字段
resp *http.Response // 内部字段
bodyRead bool // 内部字段
bodyClear bool // 内部字段
}
| 字段名 | 类型 | 描述 | 源码位置 | 读取示例 | 修改示例 |
|---|---|---|---|---|---|
Status |
number |
HTTP状态码 | JSResponse.Status |
200, 404, 500 |
res.Status = 403 |
ContentType |
string |
Content-Type头部 | JSResponse.ContentType |
"text/html" |
res.ContentType = "application/json" |
Headers |
string |
完整头部字符串 | JSResponse.Headers |
"Content-Type: text/html\r\n" |
// 使用方法修改 |
Body |
string |
响应体内容 | JSResponse.Body |
// 初始为空 |
res.Body = "<h1>Hacked!</h1>" |
| 方法名 | 参数 | 返回值 | 描述 | 源码位置 | 示例 |
|---|---|---|---|---|---|
ReadBody() |
无 | string |
读取响应体并重置底层流 | ReadBody() |
var body = res.ReadBody() |
ClearBody() |
无 | 无 | 清空响应体 | ClearBody() |
res.ClearBody() |
GetHeader(name, default) |
string, string |
string |
获取单个头部 | GetHeader() |
res.GetHeader("Server", "unknown") |
GetHeaders(name) |
string |
array |
获取多个同名头部 | GetHeaders() |
res.GetHeaders("Set-Cookie") |
SetHeader(name, value) |
string, string |
无 | 设置头部 | SetHeader() |
res.SetHeader("X-Powered-By", "Bettercap") |
RemoveHeader(name) |
string |
无 | 移除头部 | RemoveHeader() |
res.RemoveHeader("X-Frame-Options") |
// 全局变量
var stats = {
totalRequests: 0,
totalResponses: 0,
tamperedRequests: 0,
tamperedResponses: 0
};
// 初始化回调
function onLoad() {
log_info("HTTP代理脚本已加载,初始化完成");
}
// 请求拦截回调
function onRequest(req, res) {
stats.totalRequests++;
// 记录请求信息
log_debug("请求: " + req.Method + " " + req.Path + " from " + req.Client.IP);
// 可以在这里修改请求
// req.SetHeader("X-Proxy", "Bettercap");
return req;
}
// 响应拦截回调
function onResponse(req, res) {
stats.totalResponses++;
// 记录响应信息
log_debug("响应: " + res.Status + " for " + req.Path + " (" + res.Body.length + " bytes)");
// 可以在这里修改响应
// res.SetHeader("X-Modified", "true");
return res;
}
// 命令处理回调
function onCommand(cmd) {
if (cmd === "stats") {
log_info("=== 代理统计 ===");
log_info("总请求: " + stats.totalRequests);
log_info("总响应: " + stats.totalResponses);
return true;
}
if (cmd === "reset") {
stats.totalRequests = 0;
stats.totalResponses = 0;
log_info("统计已重置");
return true;
}
return false; // 命令未处理
}
function onRequest(req, res) {
// 1. 修改请求方法和路径
if (req.Method === "GET" && req.Path === "/admin") {
req.Method = "POST";
req.Path = "/login";
}
// 2. 修改查询参数
if (req.Query.indexOf("password=") >= 0) {
req.Query = req.Query.replace("password=secret", "password=hacked");
}
// 3. 添加自定义头部
req.SetHeader("X-Proxy", "Bettercap");
req.SetHeader("X-Forwarded-For", "127.0.0.1");
// 4. 移除跟踪头部
req.RemoveHeader("Referer");
// 5. 修改请求体(需要先读取)
if (req.Method === "POST" && req.ContentType === "application/json") {
var body = req.ReadBody();
if (body.indexOf('"password"')) {
var json = JSON.parse(body);
json.password = "intercepted";
req.Body = JSON.stringify(json);
req.ContentType = "application/json"; // 确保ContentType同步
}
}
// 6. 记录客户端信息
log_info("Request from: " + req.Client.IP + " (" + req.Client.Alias + ")");
return req; // 必须返回修改后的对象
}
function onResponse(req, res) {
// 1. 修改状态码
if (res.Status === 200 && req.Path === "/admin") {
res.Status = 403; // 禁止访问
}
// 2. 修改响应头部
res.SetHeader("Server", "Bettercap-Proxy");
res.RemoveHeader("X-Frame-Options"); // 移除安全限制
// 3. 修改HTML内容
if (res.ContentType && res.ContentType.indexOf("text/html") >= 0) {
var body = res.ReadBody();
if (body.indexOf("<title>") >= 0) {
body = body.replace("<title>", "<title>[HACKED] ");
res.Body = body;
}
// 注入JavaScript
if (body.indexOf("</head>") >= 0) {
var inject = '<script>alert("This page was modified by Bettercap!");</script>';
body = body.replace("</head>", inject + "</head>");
res.Body = body;
}
}
// 4. 修改JSON响应
if (res.ContentType && res.ContentType.indexOf("application/json") >= 0) {
var body = res.ReadBody();
try {
var json = JSON.parse(body);
json.modified = true;
json.proxy = "bettercap";
res.Body = JSON.stringify(json);
} catch (e) {
log_error("Failed to parse JSON response: " + e);
}
}
// 5. 重定向响应
if (req.Path === "/old-page") {
res.Status = 302;
res.SetHeader("Location", "/new-page");
res.ClearBody(); // 清空body
}
// 6. 记录响应信息
log_info("Response: " + res.Status + " for " + req.Method + " " + req.Path);
return res; // 必须返回修改后的对象
}
ReadBody() 会消耗底层HTTP流的Body,但会自动重置为可再次读取的状态ReadBody(),每次返回相同数据req.Body 或 res.Body 字符串res.ClearBody() 方法(仅Response)\r\n 分隔的原始字符串GetHeader(), SetHeader(), RemoveHeader()req 或 resfunction onRequest(req, res) {
// 第一次调用:读取原始请求体
var body1 = req.ReadBody();
console.log("第一次读取:", body1.length, "bytes");
// 第二次调用:在同一个回调中可以再次读取,返回相同数据
var body2 = req.ReadBody();
console.log("第二次读取:", body2.length, "bytes");
// body1 和 body2 包含相同的数据
console.log("数据相同:", body1 === body2); // true
// 你可以安全地多次调用ReadBody()
return req;
}
底层实现:
ReadBody() 从HTTP流的底层读取数据请求间隔离:
// 每个HTTP请求都是独立的JSRequest对象
function onRequest(req, res) {
// 这个req对象的Body读取不会影响其他HTTP请求
var body = req.ReadBody();
// 你可以根据需要多次读取
if (body.indexOf("password") >= 0) {
var form = req.ParseForm(); // 内部也会调用ReadBody()
console.log("找到密码字段");
}
}
log_debug() 而非 log_info()function onRequest(req, res) {
try {
// 你的代码
return req;
} catch (e) {
log_error("Request processing error: " + e);
return req; // 出错时也要返回原对象
}
}
function onRequest(req, res) {
if (req.Body && req.Body.length > 1000) {
log_warn("Large request body from: " + req.Client.IP);
}
return req;
}
function onResponse(req, res) {
if (res.ContentType && res.ContentType.indexOf("text/html") >= 0) {
var body = res.ReadBody();
if (body.indexOf("badword")) {
res.Status = 403;
res.Body = "<h1>Content Blocked</h1>";
}
}
return res;
}
function onResponse(req, res) {
if (req.Path.indexOf("/api/") === 0) {
log_info("API call: " + req.Method + " " + req.Path + " -> " + res.Status);
}
return res;
}
本文档基于以下Bettercap源码文件:
modules/http_proxy/http_proxy_js_request.go - JSRequest结构体和方法定义modules/http_proxy/http_proxy_js_response.go - JSResponse结构体和方法定义modules/http_proxy/http_proxy_script.go - JavaScript回调函数接口(onLoad, onRequest, onResponse, onCommand)modules/http_proxy/http_proxy_base.go - 代理核心逻辑所有字段、方法和示例都经过源码验证,确保准确无误!
这个参考文档涵盖了所有字段和方法的详细用法,是编写Bettercap JavaScript脚本的完整指南!📚