HTTPS 中间人攻击与预防

理解 HTTPS 中间人攻击原理及常见防护措施

问题

解释 HTTPS 中间人攻击(MITM)的原理,以及如何预防。

解答

HTTPS 正常通信流程

客户端                    服务器
   |                        |
   |------ ClientHello ---->|
   |<----- ServerHello -----|
   |<----- 证书 --------------|
   |------ 验证证书 -------->|
   |------ 密钥交换 -------->|
   |<===== 加密通信 =======>|

中间人攻击原理

攻击者插入客户端和服务器之间,分别与双方建立连接:

客户端          攻击者           服务器
   |              |                |
   |-- 请求 ----->|                |
   |              |--- 请求 ------>|
   |              |<-- 响应 -------|
   |<-- 响应 -----|                |
   |              |                |
   |  (攻击者可以查看/篡改所有数据)   |

攻击者实施步骤:

  1. ARP 欺骗 - 让流量经过攻击者设备
  2. 伪造证书 - 向客户端出示假证书
  3. 双向代理 - 分别与客户端、服务器建立 HTTPS 连接
  4. 解密转发 - 解密客户端数据,重新加密发给服务器

前端预防措施

1. 启用 HSTS

// 服务端设置响应头
// Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

// Nginx 配置
// add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

2. 证书固定(Certificate Pinning)

<!-- 通过 HTTP 头实现(已废弃,了解即可) -->
<!-- Public-Key-Pins: pin-sha256="base64=="; max-age=expireTime -->
// 现代方案:使用 fetch 时检查证书
// 浏览器端无法直接实现,需要依赖浏览器的证书验证机制

// Node.js 中可以实现证书固定
const https = require('https');
const crypto = require('crypto');

const expectedFingerprint = 'XX:XX:XX:...'; // 预期的证书指纹

const options = {
  hostname: 'example.com',
  port: 443,
  path: '/',
  method: 'GET',
  checkServerIdentity: (host, cert) => {
    // 计算证书指纹
    const fingerprint = cert.fingerprint256;
    if (fingerprint !== expectedFingerprint) {
      throw new Error('证书指纹不匹配,可能存在中间人攻击');
    }
  }
};

https.request(options, (res) => {
  // 处理响应
}).end();

3. 检测混合内容

// 检查页面是否存在混合内容(HTTPS 页面加载 HTTP 资源)
function checkMixedContent() {
  const resources = performance.getEntriesByType('resource');
  const insecure = resources.filter(r => r.name.startsWith('http://'));
  
  if (insecure.length > 0) {
    console.warn('发现不安全资源:', insecure.map(r => r.name));
  }
  
  return insecure;
}

4. 使用 CSP 强制 HTTPS

<!-- 升级所有请求为 HTTPS -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
// 服务端设置
// Content-Security-Policy: upgrade-insecure-requests

5. 敏感操作二次验证

// 关键操作添加额外验证,即使被中间人攻击也能降低损失
async function sensitiveOperation(data) {
  // 1. 请求服务器生成一次性 token
  const { token } = await fetch('/api/generate-token').then(r => r.json());
  
  // 2. 用户输入验证码或二次密码
  const verifyCode = await getUserInput('请输入验证码');
  
  // 3. 带上 token 和验证码提交
  return fetch('/api/sensitive-action', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ data, token, verifyCode })
  });
}

6. 前端检测异常证书(有限)

// 通过 SecurityPolicyViolationEvent 监听安全策略违规
document.addEventListener('securitypolicyviolation', (e) => {
  console.error('安全策略违规:', {
    blockedURI: e.blockedURI,
    violatedDirective: e.violatedDirective
  });
  
  // 上报安全事件
  reportSecurityIncident(e);
});

关键点

  • 中间人攻击通过伪造证书,在客户端和服务器之间建立两个独立的加密连接
  • HSTS 强制浏览器使用 HTTPS,防止 SSL 剥离攻击
  • 证书固定可以防止伪造证书,但浏览器端实现受限
  • CSP 的 upgrade-insecure-requests 自动升级 HTTP 请求
  • 敏感操作应添加二次验证,降低攻击成功后的损失