Python HTTPS代理拦截请求用重定向替代HTTP403的实现方案
HTTPS代理拦截重定向实现方案
问题根因
HTTPS请求先发送CONNECT方法请求代理建立TCP隧道,浏览器默认只有收到200 Connection Established响应后,才会开始后续TLS握手流程。如果在CONNECT阶段直接返回403、3xx等状态码,浏览器不会解析响应内容,直接抛出隧道连接失败错误,这就是之前方案失效的核心原因。
可落地方案
方案一:无证书重定向(改动最小,适配当前代码结构)
不需要客户端安装任何证书,仅需调整拦截逻辑,利用现代浏览器的容错机制实现跳转:
- 对普通HTTP请求,命中黑名单时直接返回307重定向响应,携带拦截提示页地址即可
- 对HTTPS CONNECT请求,命中黑名单时不要连接目标服务器,先返回
200 Connection Established假装隧道建立成功,等待浏览器发送TLS Client Hello包后,直接返回明文307重定向响应,浏览器会自动识别并跳转到拦截提示页,不会报隧道连接错误。
具体代码修改
- 首先改造原有
response函数,支持自定义响应头:
def response(status, message, headers=None): reply = f"HTTP/1.1 {status} {message}\r\n" reply += "Proxy-agent: Sinktrap\r\n" if headers: for k, v in headers.items(): reply += f"{k}: {v}\r\n" reply += "\r\n" browser_conn.sendall(reply.encode())
- 替换原有的黑名单拦截判断逻辑:
# 替换为自己的拦截提示页地址 BLOCK_PAGE = "自定义拦截提示页地址" # Check if request is allowed or not if not ishostAllowed(req['host']): csv_logger.info("blocked",extra=req) thread_logger.warn("Block REQUEST:{0}".format(raw_data),extra=req) if req['method'] == 'CONNECT': # HTTPS请求先伪装隧道建立成功 response(200, "Connection established") # 短暂等待接收浏览器发送的Client Hello包,无需处理内容 time.sleep(0.1) try: browser_conn.recv(config.max_request_len) except: pass # 发送重定向响应 response(307, "Temporary Redirect", { "Location": BLOCK_PAGE, "Content-Length": "0" }) else: # HTTP请求直接重定向 response(307, "Temporary Redirect", { "Location": BLOCK_PAGE, "Content-Length": "0" }) browser_conn.close() return
方案优缺点
- 优点:代码改动量极小,不需要客户端预装证书,兼容Chrome、Edge、Firefox等所有主流现代浏览器,不会再出现隧道连接失败错误
- 缺点:跳转后地址栏会显示拦截页域名,无法保留原被拦截站点域名;极个别老旧浏览器不兼容该逻辑
方案二:MITM中间人拦截(体验最优)
如果需要在原域名下直接展示拦截内容、不跳转第三方地址,就需要实现中间人代理逻辑:
- 生成自签CA根证书,将证书安装到所有使用代理的客户端信任列表中
- 拦截到HTTPS CONNECT请求时,动态签发对应域名的伪造证书,和浏览器完成TLS握手
- 解密浏览器发送的HTTP请求,直接返回自定义拦截页内容即可
该方案需要实现TLS握手、动态证书生成逻辑,不建议从零手写,可以直接基于成熟的中间人代理库二次开发,减少重复工作量。
原有代码问题说明
- 之前尝试直接在CONNECT阶段返回307、或者手动修改host连接拦截页的逻辑不可行:浏览器在CONNECT阶段不会处理3xx状态码,且跨域名的TLS证书会被浏览器直接拦截报安全错误
- 现有host解析正则对IPv6地址、无路径的CONNECT请求兼容性不足,建议单独对CONNECT方法做host、port解析逻辑
内容的提问来源于stack exchange,提问作者AzlanCoding
相关产品推荐
相关产品推荐

