您现在的位置是:网站首页 > 使用 CSP 防止点击劫持文章详情
使用 CSP 防止点击劫持
陈川
【
前端安全
】
44385人已围观
5386字
什么是点击劫持
点击劫持(Clickjacking)是一种视觉欺骗攻击手段。攻击者通过透明或伪装的iframe覆盖在目标网页上,诱使用户在不知情的情况下点击恶意内容。例如,攻击者可能将一个透明的按钮覆盖在"点赞"按钮上,用户以为自己点击的是点赞,实际触发了转账操作。
<!-- 恶意网站代码示例 -->
<style>
iframe {
opacity: 0.5;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
}
</style>
<iframe src="https://victim-bank.com/transfer"></iframe>
CSP 如何防御点击劫持
内容安全策略(Content Security Policy,CSP)通过HTTP响应头控制资源加载行为。针对点击劫持,CSP提供了frame-ancestors
指令,可以限制页面被哪些父级页面嵌入。
Content-Security-Policy: frame-ancestors 'none'
这个策略会阻止所有框架嵌套尝试,包括iframe、frame、object等元素。当恶意网站尝试嵌入受保护的页面时,浏览器会拒绝加载。
常用 CSP 点击劫持防护策略
完全禁止嵌套
最严格的防护策略是禁止任何形式的框架嵌套:
Content-Security-Policy: frame-ancestors 'none'
允许同源嵌套
如果业务需要内部框架嵌套,可以限制为同源:
Content-Security-Policy: frame-ancestors 'self'
允许特定域名嵌套
对于需要跨域嵌入的场景,可以指定白名单:
Content-Security-Policy: frame-ancestors https://trusted.example.com https://partner.site.org
与其他安全头配合使用
CSP可以与传统的X-Frame-Options
头一起使用,提供向后兼容性:
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
X-Frame-Options
只支持三个值:
- DENY:完全禁止嵌入
- SAMEORIGIN:允许同源嵌入
- ALLOW-FROM uri:允许指定URI嵌入(已废弃)
实际部署示例
Express 中间件配置
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet.frameguard({ action: 'deny' }));
// 或使用CSP
app.use(helmet.contentSecurityPolicy({
directives: {
frameAncestors: ["'none'"]
}
}));
Nginx 配置
add_header Content-Security-Policy "frame-ancestors 'none'";
add_header X-Frame-Options "DENY";
Apache 配置
Header always set Content-Security-Policy "frame-ancestors 'none'"
Header always set X-Frame-Options "DENY"
测试防护效果
部署后可以通过以下方式测试:
- 创建测试HTML文件尝试嵌入受保护页面
<!DOCTYPE html>
<html>
<body>
<iframe src="https://your-protected-site.com"></iframe>
</body>
</html>
- 使用浏览器开发者工具查看控制台错误信息
- 使用在线CSP验证工具检查策略有效性
常见问题与解决方案
业务需要iframe嵌入怎么办
如果业务确实需要被第三方网站嵌入,应该:
- 明确列出所有允许的域名
- 定期审核这些域名的安全性
- 考虑使用postMessage进行安全通信
Content-Security-Policy: frame-ancestors https://partner1.com https://partner2.com
旧浏览器兼容性问题
对于不支持CSP的旧浏览器:
- 同时使用
X-Frame-Options
- 考虑JavaScript防御方案作为补充
// 备用JavaScript防御
if (top !== self) {
top.location = self.location;
}
误报问题处理
如果合法流量被拦截:
- 检查是否有重定向导致源变化
- 验证所有子域名的包含情况
- 使用报告机制收集错误
Content-Security-Policy: frame-ancestors 'self'; report-uri /csp-violation-report-endpoint
CSP 报告机制
启用报告功能可以帮助发现潜在问题:
Content-Security-Policy: frame-ancestors 'self'; report-uri https://your-domain.com/csp-report
服务器端处理示例(Node.js):
app.post('/csp-report', (req, res) => {
console.log('CSP Violation:', req.body);
res.status(204).end();
});
移动端特殊考虑
移动WebView可能需要额外配置:
- Android WebView需要启用CSP支持
- iOS WKWebView默认支持CSP
- 混合应用要确保原生代码不会绕过策略
性能影响评估
CSP对性能的影响主要来自:
- 策略解析时间(通常可以忽略)
- 报告发送开销(在高流量站点需要注意)
- 预加载扫描器可能无法处理复杂策略
优化建议:
- 保持策略简洁
- 对静态资源使用单独的策略
- 考虑使用meta标签作为补充
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'none'">
与其他安全策略的协同
CSP应该与以下安全措施配合使用:
- 同源策略(Same-Origin Policy)
- 跨域资源共享(CORS)配置
- 子资源完整性(SRI)检查
- 安全的Cookie属性(Secure, HttpOnly, SameSite)
完整的安全头示例:
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(self)
框架特定配置
React 应用配置
在React中可以通过自定义服务器中间件或webpack插件添加CSP:
// webpack.config.js
const { DefinePlugin } = require('webpack');
module.exports = {
plugins: [
new DefinePlugin({
'process.env.CSP_HEADER': JSON.stringify("frame-ancestors 'none'")
})
]
}
Vue CLI 配置
vue.config.js中配置安全头:
module.exports = {
devServer: {
headers: {
'Content-Security-Policy': "frame-ancestors 'none'"
}
}
}
Angular 配置
在angular.json中配置响应头:
{
"projects": {
"your-app": {
"architect": {
"serve": {
"options": {
"headers": {
"Content-Security-Policy": "frame-ancestors 'none'"
}
}
}
}
}
}
}
监控与维护
有效的CSP策略需要持续维护:
- 定期审查策略是否仍然符合业务需求
- 监控CSP违规报告
- 随着业务发展调整白名单
- 在新功能上线前测试CSP兼容性
建议的监控方案:
// 前端监控代码示例
document.addEventListener('securitypolicyviolation', (e) => {
analytics.send('CSP Violation', {
violatedDirective: e.violatedDirective,
blockedURI: e.blockedURI,
lineNumber: e.lineNumber
});
});
高级防护技术
对于高安全要求的应用,可以考虑:
动态策略生成
根据请求特征生成不同的策略:
app.use((req, res, next) => {
if (req.path.startsWith('/admin')) {
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
} else {
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'");
}
next();
});
结合用户认证
对已认证用户应用更严格的策略:
app.use((req, res, next) => {
if (req.user && req.user.isAdmin) {
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
}
next();
});
非浏览器环境处理
识别并阻止非浏览器访问:
app.use((req, res, next) => {
const userAgent = req.get('User-Agent');
if (!userAgent || !isBrowser(userAgent)) {
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
}
next();
});