HTTPS 中间人攻击与预防
理解 HTTPS 中间人攻击原理及常见防护措施
问题
解释 HTTPS 中间人攻击(MITM)的原理,以及如何预防。
解答
HTTPS 正常通信流程
客户端 服务器
| |
|------ ClientHello ---->|
|<----- ServerHello -----|
|<----- 证书 --------------|
|------ 验证证书 -------->|
|------ 密钥交换 -------->|
|<===== 加密通信 =======>|
中间人攻击原理
攻击者插入客户端和服务器之间,分别与双方建立连接:
客户端 攻击者 服务器
| | |
|-- 请求 ----->| |
| |--- 请求 ------>|
| |<-- 响应 -------|
|<-- 响应 -----| |
| | |
| (攻击者可以查看/篡改所有数据) |
攻击者实施步骤:
- ARP 欺骗 - 让流量经过攻击者设备
- 伪造证书 - 向客户端出示假证书
- 双向代理 - 分别与客户端、服务器建立 HTTPS 连接
- 解密转发 - 解密客户端数据,重新加密发给服务器
前端预防措施
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 请求 - 敏感操作应添加二次验证,降低攻击成功后的损失
目录