apiv1

Bettercap HTTP代理回调函数 API 参考文档

📋 文档说明: 本文档专门介绍Bettercap HTTP代理模块中所有JavaScript回调函数的参数结构、字段定义和使用方法。所有内容都经过源码验证,确保准确性。

📋 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 处理外部命令

onLoad 回调函数

执行时机:脚本首次加载时执行一次

用途

示例

function onLoad() {
    log_info("HTTP代理脚本已加载");

    // 初始化统计变量
    stats = {
        totalRequests: 0,
        totalResponses: 0,
        tamperedRequests: 0,
        tamperedResponses: 0
    };

    log_info("初始化完成");
}

onCommand 回调函数

执行时机:当用户在Bettercap交互式命令行中输入未被识别的命令时调用

命令输入方式

# 启动Bettercap后,在交互式命令行中输入
bettercap> stats
bettercap> reset
bettercap> custom-command

参数

返回值

源码机制

// 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; // 命令未识别,显示"未知命令"错误
}

使用说明

  1. 交互式输入:在Bettercap运行后,在命令提示符bettercap>后输入命令
  2. 命令匹配:使用字符串比较或正则表达式匹配
  3. 返回值控制:返回true阻止错误信息,返回false允许其他处理
  4. 参数解析:需要手动解析命令参数(如set-threshold 100

🔍 JSRequest 对象字段详解

源码证据

基于 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"

JSRequest 方法详解

方法名 参数 返回值 描述 源码位置 示例
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")

🔍 JSResponse 对象字段详解

源码证据

基于 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>"

JSResponse 方法详解

方法名 参数 返回值 描述 源码位置 示例
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; // 必须返回修改后的对象
}

⚠️ 重要注意事项

Body操作规则

头部操作规则

返回值要求

Body读取机制详解

function 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;
}

底层实现

  1. ReadBody() 从HTTP流的底层读取数据
  2. 读取完成后,将底层流重置为包含相同数据的buffer
  3. 因此在同一个回调函数中可以安全地多次调用

请求间隔离

// 每个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("找到密码字段");
    }
}

性能考虑

错误处理

function onRequest(req, res) {
    try {
        // 你的代码
        return req;
    } catch (e) {
        log_error("Request processing error: " + e);
        return req; // 出错时也要返回原对象
    }
}

🎯 实际应用场景

1. 安全审计

function onRequest(req, res) {
    if (req.Body && req.Body.length > 1000) {
        log_warn("Large request body from: " + req.Client.IP);
    }
    return req;
}

2. 内容过滤

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;
}

3. API监控

function onResponse(req, res) {
    if (req.Path.indexOf("/api/") === 0) {
        log_info("API call: " + req.Method + " " + req.Path + " -> " + res.Status);
    }
    return res;
}

📚 源码文件参考

本文档基于以下Bettercap源码文件:

所有字段、方法和示例都经过源码验证,确保准确无误!

这个参考文档涵盖了所有字段和方法的详细用法,是编写Bettercap JavaScript脚本的完整指南!📚