基于http-mitm-proxy的MITM代理因自签名证书通信失败求助
解决MITM代理与自签名HTTPS服务器的证书信任问题
看起来你在MITM代理的证书逻辑和服务器信任配置上踩了几个典型的坑,我来一步步帮你梳理并解决:
核心错误分析
你的问题主要出在两个关键环节:
- 代理连接目标HTTPS服务器时的证书验证:Node.js默认会拒绝自签名证书,所以代理作为客户端连接你的HTTPS文件服务器时,抛出了
DEPTH_ZERO_SELF_SIGNED_CERT错误。 - MITM代理的证书签发逻辑:你直接复用了目标服务器的证书给客户端,这不符合中间人攻击的原理——代理应该用自己的根CA动态签发目标域名的证书,让客户端信任这个根CA即可。
步骤1:修复代理对目标服务器的证书信任问题
代理作为客户端连接你的自签名HTTPS服务器时,需要跳过证书验证(测试环境)或者添加信任的根CA(生产环境)。在http-mitm-proxy的onRequest钩子中修改请求配置:
proxy.onRequest(function(ctx, callback) { console.log('onRequest'); // 测试环境:禁用目标服务器的证书验证(快速解决) ctx.proxyToServerRequestOptions.rejectUnauthorized = false; // 生产环境:添加目标服务器的根CA到信任列表 // ctx.proxyToServerRequestOptions.ca = fs.readFileSync('目标服务器的根CA.crt'); callback(); });
步骤2:重构MITM代理的证书体系
你需要生成一个独立的MITM根CA,用来动态签发目标域名的证书,而不是复用目标服务器的证书:
1. 生成MITM根CA(用OpenSSL命令)
# 生成根CA私钥 openssl genrsa -out mitm-ca.key 2048 # 生成自签名根CA证书(有效期10年) openssl req -x509 -new -nodes -key mitm-ca.key -sha256 -days 3650 -out mitm-ca.crt
2. 修改代理代码,使用MITM根CA
移除你自定义的onCertificateRequired钩子(http-mitm-proxy会自动用根CA动态生成证书),并在启动时指定根CA的密钥和证书:
// 移除原来的proxy.onCertificateRequired钩子 // 启动代理时配置MITM根CA proxy.listen({ port: port, sslCaKey: path.resolve('mitm-ca.key'), sslCaCert: path.resolve('mitm-ca.crt') });
3. 在Firefox中导入正确的证书
把刚才生成的mitm-ca.crt导入Firefox的信任证书库(而不是目标服务器的证书),这样Firefox会信任代理签发的所有证书。
步骤3:确认域名与证书的一致性
确保:
- 目标HTTPS服务器的证书CN(通用名称)或SAN(主题备用名称)和你在Hosts中配置的域名(比如
myupdateproxy.com)完全匹配 - 客户端访问的域名和Hosts映射的IP一致,这样代理生成的证书域名才能匹配
最终修正后的代理代码
'use strict'; var port = 8081; var path = require('path'); var Proxy = require('http-mitm-proxy'); var proxy = Proxy(); const fs = require('fs'); var chunks1 = []; var chunks2 = []; proxy.onError(function(ctx, err, errorKind) { var url = (ctx && ctx.clientToProxyRequest) ? ctx.clientToProxyRequest.url : ''; console.error(errorKind + ' on ' + url + ':', err); }); proxy.onRequest(function(ctx, callback) { console.log('onRequest'); // 禁用目标服务器证书验证(测试用) ctx.proxyToServerRequestOptions.rejectUnauthorized = false; callback(); }); proxy.onRequestData(function(ctx, chunk, callback) { console.log('onRequestData'); chunks1.push(chunk); callback(null, chunk); }); proxy.onRequestEnd(function(ctx, callback) { if (ctx.clientToProxyRequest.socket.remoteAddress !== undefined && ctx.proxyToServerRequest.socket.remoteAddress !== undefined && (Buffer.concat(chunks1)).length > 0) { console.log('From: ' + ctx.clientToProxyRequest.socket.remoteAddress); console.log('To: ' + ctx.proxyToServerRequest.socket.remoteAddress); console.log('Size: ' + (Buffer.concat(chunks1)).length); console.log(''); } chunks1 = []; callback(); }); proxy.onResponse(function(ctx, callback) { callback(null); }); proxy.onResponseData(function(ctx, chunk, callback) { chunks2.push(chunk); callback(null, chunk); }); proxy.onResponseEnd(function(ctx, callback) { var total_size=(Buffer.concat(chunks2)).length; if (ctx.serverToProxyResponse.socket.remoteAddress !== undefined && ctx.proxyToClientResponse.socket.remoteAddress !== undefined && total_size > 0) { console.log('From: ' + ctx.serverToProxyResponse.socket.remoteAddress); console.log('To: ' + ctx.proxyToClientResponse.socket.remoteAddress); console.log('Size: ' + total_size); console.log(''); } console.log((Buffer.concat(chunks2)).toString()); chunks2 = []; callback(); }); // 使用MITM根CA启动代理 proxy.listen({ port: port, sslCaKey: path.resolve('mitm-ca.key'), sslCaCert: path.resolve('mitm-ca.crt') }); console.log('listening on ' + port);
总结
你的核心问题是混淆了"目标服务器的自签名证书"和"MITM代理的根CA证书"的作用:
- 目标服务器的证书用来让代理信任它
- MITM代理的根CA用来让客户端信任代理签发的证书
按照上面的步骤调整后,代理就能正常拦截、解密HTTPS流量了。
内容的提问来源于stack exchange,提问作者conectionist
相关产品推荐
相关产品推荐

