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脚本的完整指南!📚