Content Security Policy(CSP)是现代浏览器提供的一种重要的安全机制,它能有效防止XSS攻击、数据注入等安全威胁。今天我们就来详细了解一下CSP,并通过几个可以直接运行的示例来掌握它的用法。
Content Security Policy(内容安全策略,简称 CSP)是现代浏览器提供的一项重要安全机制,主要作用是帮助网站防范多种常见的客户端攻击,尤其是跨站脚本攻击(XSS)和数据注入攻击。
除了 Internet Explorer 之外,目前所有主流现代浏览器均已原生支持 CSP。所以在绝大多数场景下,开发者可以放心地在项目中启用并使用这一安全功能。
CSP 的核心机制是通过设置 HTTP 响应头 Content-Security-Policy
(或通过 HTML 的 <meta>
标签),定义哪些外部资源可以被加载和执行。当浏览器解析页面时,会先检查该策略配置,再决定是否允许加载指定的资源。
例如,当页面需要加载 JavaScript 文件、图片、样式表、字体或 iframe 等资源时,浏览器会依据 CSP 策略进行校验:只有符合策略规定的资源才会被加载;不符合的则会被阻止,并在开发者控制台中记录相关警告或错误信息。
为什么需要CSP?
想象一下,如果你的网站允许用户输入评论,而恶意用户输入了这样的脚本:
<script>alert('你的数据被我偷走了!')</script>
传统的防御手段虽然有效,但CSP提供了更深层的保护——它通过白名单机制,告诉浏览器只加载和执行来自特定来源的资源。
通过HTTP响应头设置(推荐)
Content-Security-Policy: default-src 'self'
通过Meta标签设置
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
default-src
:默认策略
script-src
:控制JavaScript
style-src
:控制CSS
img-src
:控制图片
connect-src
:控制Ajax请求
下面我们通过两个个可以直接运行的示例来理解CSP的工作原理。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>示例1:没有CSP保护的页面</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.container { border: 1px solid #ddd; padding: 20px; margin: 20px 0; border-radius: 5px; }
button { padding: 8px 15px; margin: 5px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #45a049; }
input { padding: 8px; width: 300px; margin-right: 10px; }
.warning { background: #ffebee; color: #c62828; padding: 10px; border-radius: 4px; }
.safe { background: #e8f5e9; color: #2e7d32; padding: 10px; border-radius: 4px; }
</style>
</head>
<body>
<h1>示例1:没有CSP保护的页面</h1>
<div class="container">
<h2>XSS攻击演示</h2>
<p>这个页面没有CSP保护,尝试输入恶意脚本:</p>
<input type="text" id="userInput" placeholder="尝试输入: <script>alert('XSS')</script>">
<button id="displayButton">显示输入内容</button>
<div id="output" style="margin-top: 15px; min-height: 50px; padding: 10px; border: 1px dashed #ccc;"></div>
<div class="warning">
<p><strong>警告:</strong>这个页面没有CSP保护,恶意脚本可以执行!</p>
</div>
</div>
<div class="container">
<h2>外部资源加载</h2>
<button id="loadScriptButton">加载外部脚本</button>
<button id="loadImageButton">加载外部图片</button>
<div id="external-results" style="margin-top: 15px;"></div>
</div>
<script>
// 显示用户输入(不安全的方式)
document.getElementById('displayButton').addEventListener('click', function() {
const userInput = document.getElementById('userInput').value;
// 危险:直接插入HTML,可能导致XSS攻击
document.getElementById('output').innerHTML =
"你输入的是: " + userInput;
});
// 加载外部脚本
document.getElementById('loadScriptButton').addEventListener('click', function() {
const resultDiv = document.getElementById('external-results');
try {
// 动态创建脚本标签
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js';
script.onload = function() {
resultDiv.innerHTML = '<p style="color:green">外部脚本加载成功!</p>';
};
script.onerror = function() {
resultDiv.innerHTML = '<p style="color:red">外部脚本加载失败</p>';
};
document.head.appendChild(script);
} catch(e) {
resultDiv.innerHTML = '<p style="color:red">错误: ' + e.message + '</p>';
}
});
// 加载外部图片
document.getElementById('loadImageButton').addEventListener('click', function() {
const resultDiv = document.getElementById('external-results');
try {
const img = document.createElement('img');
img.src = 'https://via.placeholder.com/150';
img.onload = function() {
resultDiv.innerHTML = '<p style="color:green">外部图片加载成功!</p><br>';
resultDiv.appendChild(img);
};
img.onerror = function() {
resultDiv.innerHTML = '<p style="color:red">外部图片加载失败</p>';
};
} catch(e) {
resultDiv.innerHTML = '<p style="color:red">错误: ' + e.message + '</p>';
}
});
// 模拟恶意脚本(这个脚本会执行,因为没有CSP)
setTimeout(() => {
console.log("恶意脚本已执行 - 因为没有CSP保护");
// 尝试执行alert
try {
eval('console.log("eval函数可以执行")');
} catch(e) {
console.log("eval错误:", e);
}
}, 2000);
</script>
</body>
</html>
具体效果如下图:
加载外部图片失败
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>示例2:启用CSP保护的页面</title>
<!-- 启用CSP保护 - 使用nonce解决内联脚本问题 -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'nonce-random123';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
object-src 'none';
base-uri 'self'">
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.container { border: 1px solid #ddd; padding: 20px; margin: 20px 0; border-radius: 5px; }
button { padding: 8px 15px; margin: 5px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #45a049; }
input { padding: 8px; width: 300px; margin-right: 10px; }
.warning { background: #ffebee; color: #c62828; padding: 10px; border-radius: 4px; }
.safe { background: #e8f5e9; color: #2e7d32; padding: 10px; border-radius: 4px; }
</style>
</head>
<body>
<h1>示例2:启用CSP保护的页面</h1>
<div class="container">
<h2>XSS攻击防护演示</h2>
<p>这个页面有CSP保护,尝试输入恶意脚本:</p>
<input type="text" id="userInput" placeholder="尝试输入: <script>alert('XSS')</script>">
<button id="displayButton">显示输入内容</button>
<div id="output" style="margin-top: 15px; min-height: 50px; padding: 10px; border: 1px dashed #ccc;"></div>
<div class="safe">
<p><strong>安全:</strong>这个页面有CSP保护,恶意脚本会被阻止!</p>
</div>
</div>
<div class="container">
<h2>外部资源加载限制</h2>
<button id="loadScriptButton">加载外部脚本</button>
<button id="loadImageButton">加载外部图片</button>
<button id="tryEvalButton">尝试使用eval()</button>
<div id="external-results" style="margin-top: 15px;"></div>
</div>
<!-- 使用nonce允许特定脚本执行 -->
<script nonce="random123">
// 显示用户输入(安全的方式)
document.getElementById('displayButton').addEventListener('click', function() {
const userInput = document.getElementById('userInput').value;
// 安全:使用textContent而不是innerHTML
document.getElementById('output').textContent =
"你输入的是: " + userInput;
});
// 尝试加载外部脚本(会被CSP阻止)
document.getElementById('loadScriptButton').addEventListener('click', function() {
const resultDiv = document.getElementById('external-results');
try {
// 动态创建脚本标签
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js';
script.onload = function() {
resultDiv.innerHTML = '<p style="color:green">外部脚本加载成功!</p>';
};
script.onerror = function() {
resultDiv.innerHTML = '<p style="color:red">外部脚本被CSP阻止</p>';
};
document.head.appendChild(script);
} catch(e) {
resultDiv.innerHTML = '<p style="color:red">错误: ' + e.message + '</p>';
}
});
// 尝试加载外部图片(会被CSP允许,因为我们设置了img-src https:)
document.getElementById('loadImageButton').addEventListener('click', function() {
const resultDiv = document.getElementById('external-results');
try {
const img = document.createElement('img');
img.src = 'https://via.placeholder.com/150';
img.onload = function() {
resultDiv.innerHTML = '<p style="color:green">外部图片加载成功!</p><br>';
resultDiv.appendChild(img);
};
img.onerror = function() {
resultDiv.innerHTML = '<p style="color:red">外部图片加载失败</p>';
};
} catch(e) {
resultDiv.innerHTML = '<p style="color:red">错误: ' + e.message + '</p>';
}
});
// 尝试使用eval(会被CSP阻止)
document.getElementById('tryEvalButton').addEventListener('click', function() {
const resultDiv = document.getElementById('external-results');
try {
eval('resultDiv.innerHTML = "<p style=\"color:green\">eval执行成功!</p>"');
} catch(e) {
resultDiv.innerHTML = '<p style="color:red">CSP阻止了eval执行: ' + e.message + '</p>';
}
});
// 尝试执行内联事件处理程序(会被CSP阻止)
// 注意:这个脚本不会执行,因为CSP阻止了内联事件处理程序
console.log("这个脚本可以执行,因为它有nonce属性");
</script>
</body>
</html>
nonce
属性允许特定脚本执行eval()
等危险函数textContent
而不是innerHTML
具体如下图:
加载外部脚本
加载外部图片
启动eval
'self'
:只允许同源资源
'none'
:禁止所有资源
'unsafe-inline'
:允许内联脚本/样式(不推荐)
'unsafe-eval'
:允许eval等动态代码执行(不推荐)
https:
:允许所有HTTPS资源
CSP是一项强大的安全技术,实际应用过程中应该记住以下原则:
渐进实施:从报告模式开始,逐步转向强制执行
最小权限原则:只授予必要的权限
持续监控:定期检查违规报告,优化策略
平衡安全与功能:在安全性和开发便利性之间找到平衡点
大家如果使用过程中有问题的话欢迎评论区沟通交流!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。