DVWA靶场——Content Security Policy Bypass(CSP 内容安全策略)
CSP(Content Security Policy)是一种浏览器安全机制,通过 HTTP 响应头告诉浏览器:
“这个页面只能加载哪些来源的资源(脚本、样式、图片等),其他的一律禁止。”主要是为了缓解潜在的跨站脚本问题(XSS),浏览器的扩展程序系统引入了内容安全策略这个概念。原来应对XSS攻时,主要采用函数过滤转义输入中的特殊字符、标签、文本来规避攻击。CSP的实质就是白名单制度,开发人员明确告诉客户端,哪些外部资源可以加载和执行。开发者只需要提供配置,实现和执行全部由浏览器完成。
这对我们来说是一个全新的内容,所以务必理解它的原理,下面给出一个具体的场景带大家理解一下
CSP 说:来自 pastebin.com 的脚本是安全的 攻击者:在 pastebin.com 上放恶意脚本 浏览器:因为是 pastebin.com,所以执行 结果:CSP 被绕过这是浏览器配置CSP后的工作流程
用户访问页面 ↓ 浏览器发起 Request(请求页面) ↓ 服务器处理请求 ↓ 服务器返回 Response(包含 HTML + 响应头) ↓ 浏览器解析 Response Headers ↓ 发现 Content-Security-Policy ↓ 浏览器按照策略限制后续资源加载明白了它的原理,这个漏洞最重要的点就在于找到那个它信任的来源
Low
源码分析
<?php $headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics. header($headerCSP); # These might work if you can't create your own for some reason # https://pastebin.com/raw/R570EE00 # https://hastebin.com/raw/ohulaquzex ?> <?php if (isset ($_POST['include'])) { $page[ 'body' ] .= " <script src='" . $_POST['include'] . "'></script> "; } $page[ 'body' ] .= ' <form name="csp" method="POST"> <p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p> <input size="50" type="text" name="include" value="" id="include" /> <input type="submit" value="Include" /> </form> ';后端完全信任用户输入,没有做任何校验,只依赖 CSP 来防御,并且开发者给了我们两个URL示例
https://pastebin.com/raw/R570EE00 https://hastebin.com/raw/ohulaquzex回到关卡中
我们先抓个包,放行,在返回头里找找看有没有信息
直接告诉了我们CSP 策略
Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;我们来解读一下
来源 | 是否允许 |
| 允许 |
| 允许 |
| 允许(注意:没有 https:// 前缀) |
| 允许 |
| 允许 |
| 允许 |
我们就试试https://pastebin.com这个网站,它是一个快速文本分享网站
构造弹框
将URL复制进输入框,include
我的浏览器报错了
了解了一下,原因如下
Pastebin 返回给浏览器的文件类型是"纯文本"(text/plain),但浏览器执行脚本需要"JavaScript"类型(application/javascript),类型不匹配,所以拒绝执行。
这是现代浏览器为提升安全性而采取的统一标准行为,因此我们不用纠结是否弹框,了解其原理即可。
Medium
源码分析
<?php $headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';"; header($headerCSP); // Disable XSS protections so that inline alert boxes will work header ("X-XSS-Protection: 0"); # <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script> ?> <?php if (isset ($_POST['include'])) { $page[ 'body' ] .= " " . $_POST['include'] . " "; } $page[ 'body' ] .= ' <form name="csp" method="POST"> <p>Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.</p> <input size="50" type="text" name="include" value="" id="include" /> <input type="submit" value="Include" /> </form> ';移除了所有第三方域名(Pastebin、hastebin 等全部删除),并且script-src 还可以设置一些特殊值,unsafe-inline 允许执行页面内嵌的 <script> 标签和事件监听函数, 引入了 nonce(一次性随机数) 机制
CSP策略
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";指令 | 含义 |
| 允许同源脚本 |
| 允许内联脚本( |
| 允许带有指定 nonce 值的脚本标签 |
Nonce 机制
nonce= "Number used ONCE"(一次性数字)
服务器生成一个随机值,同时:
- 放在 CSP 头里:
nonce-abc123 - 放在合法的
<script>标签里:<script nonce="abc123">
浏览器只会执行nonce 值匹配的内联脚本。
开发者给了我们提示,能够直接拿到nonce
通过内连注入
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert("1")</script>成功弹窗了
High
源码分析
vulnerabilities/csp/source/high.php
<?php $headerCSP = "Content-Security-Policy: script-src 'self';"; header($headerCSP); ?> <?php if (isset ($_POST['include'])) { $page[ 'body' ] .= " " . $_POST['include'] . " "; } $page[ 'body' ] .= ' <form name="csp" method="POST"> <p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p> <p>1+2+3+4+5=<span id="answer"></span></p> <input type="button" id="solve" value="Solve the sum" /> </form> <script src="source/high.js"></script> ';vulnerabilities/csp/source/high.js
function clickButton() { var s = document.createElement("script"); s.src = "source/jsonp.php?callback=solveSum"; document.body.appendChild(s); } function solveSum(obj) { if ("answer" in obj) { document.getElementById("answer").innerHTML = obj['answer']; } } var solve_button = document.getElementById ("solve"); if (solve_button) { solve_button.addEventListener("click", function() { clickButton(); }); }1.在high.php中设置了csp头,规定只允许从同源的脚本加载
2.提交表单的时候,如果存在POST形式的include参数
3.游览器执行clickbutton,动态创建<script>元素,将src属性设置为‘source/jsonp.php?callback=solveSum’(json .php是一个远程脚本的url)
4.加载远程脚本,返回数据给solveSum,展示到页面
CSP策略
$headerCSP = "Content-Security-Policy: script-src 'self';";
| 仅允许同源脚本 |
无 | 内联脚本被禁止 |
无第三方域名 | 外部脚本被禁止 |
我们打开开发者工具输入
var s = document.createElement("script"); // 创建 <script> 标签 s.src = "source/jsonp.php?callback=alert(1)//"; // 设置 src 属性 document.body.appendChild(s); // 把 <script> 添加到页面中其效果是在页面中动态插入了一个脚本标签:
<script src="source/jsonp.php?callback=alert(1)//"></script>浏览器工作流程
我们的 Console 代码 ↓ 创建 <script src="source/jsonp.php?callback=alert(1)//"> ↓ 浏览器请求该 URL ↓ jsonp.php 返回:alert(1)//({"answer": 15}) ↓ 浏览器执行这段 JavaScript ↓ alert(1) 被执行 → 弹窗因为source/jsonp.php是同源的(都在http://192.168.158.130:5432下),因此被成功执行
impossible
源码分析
vulnerabilities/csp/source/impossible.php
<?php $headerCSP = "Content-Security-Policy: script-src 'self';"; header($headerCSP); ?> <?php if (isset ($_POST['include'])) { $page[ 'body' ] .= " " . $_POST['include'] . " "; } $page[ 'body' ] .= ' <form name="csp" method="POST"> <p>Unlike the high level, this does a JSONP call but does not use a callback, instead it hardcodes the function to call.</p><p>The CSP settings only allow external JavaScript on the local server and no inline code.</p> <p>1+2+3+4+5=<span id="answer"></span></p> <input type="button" id="solve" value="Solve the sum" /> </form> <script src="source/impossible.js"></script> ';vulnerabilities/csp/source/impossible.js
function clickButton() { var s = document.createElement("script"); s.src = "source/jsonp_impossible.php"; document.body.appendChild(s); } function solveSum(obj) { if ("answer" in obj) { document.getElementById("answer").innerHTML = obj['answer']; } } var solve_button = document.getElementById ("solve"); if (solve_button) { solve_button.addEventListener("click", function() { clickButton(); }); }这个级别通过"不接受用户输入"和"硬编码返回内容",彻底封死了 JSONP callback 注入的攻击路径,结合 CSP 的'self'限制,使得该模块在当前环境下真正无法绕过
攻击方式 | 能否成功 | 原因 |
输入框注入内联脚本 | ❌ | CSP 禁止 |
输入框注入外部脚本 | ❌ | CSP 只允许 |
输入框注入同源脚本 | ❌ | 后端虽接收 ,但前端无输入框 |
修改 参数 | ❌ | JavaScript 代码已固定,用户无法控制 |
修改 的响应 | ❌ | 需要服务器文件写入权限 |
Console 执行 | ❌ |
不接受 callback 参数 |
