返回

CSP 绕过与 XSS 攻击详解:原理、方法与防御

javascript

内容安全策略(CSP)绕过与跨站脚本攻击(XSS)实施

实施内容安全策略(CSP)是提升Web应用安全的重要手段。通过设定规则, 能限制浏览器加载和执行特定来源的资源, 有效地减少XSS等攻击的风险。然而, 即便存在严格的CSP规则, 攻击者依然有可能尝试通过多种技术手段来绕过防护, 实施XSS攻击。

问题剖析: CSP 限制下的 XSS 攻击可能性

应用场景:假设一个Web应用程序,设置了CSP来防范XSS攻击,并采用了Helmet中间件。设置了以下CSP规则:

.use(
 helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: [
      "'self'",
      "use.fontawesome.com",
      "ajax.googleapis.com",
      "cdnjs.cloudflare.com",
    ],
  },
})
)

这条策略指定了默认来源是同源。 也允许从use.fontawesome.com, ajax.googleapis.com, 和 cdnjs.cloudflare.com加载脚本。

在这样的设置下,传统的<script>alert(1)</script>这种简单脚本注入会被阻止。用户如果输入了HTML代码,代码本身会被显示出来。可是,即使可以提交JavaScript代码,由于CSP限制,并不能执行。尽管如此, 在允许用户输入HTML内容, 且缺乏合适的过滤或编码机制情况下, 攻击者仍可能寻找机会来绕过限制,执行恶意脚本。

绕过CSP与实施XSS的途径

CSP机制存在弱点时, 攻击者有多种策略可以绕过这些限制,并执行XSS攻击。以下几种方法较为常见:

1. 利用白名单中的第三方服务

CSP中 script-src 指令常常会引入第三方脚本服务, 如 ajax.googleapis.comcdnjs.cloudflare.com
攻击者可以尝试在这些被允许的服务中, 寻找已知的具有执行脚本能力或者存在漏洞的版本, 尝试发起XSS攻击。
譬如,如果网站允许了某个CDN的域名,而该CDN上恰好托管了一个存在漏洞的旧版本JavaScript库(例如存在XSS漏洞的AngularJS版本), 攻击者便可利用此漏洞。

操作步骤:

  1. 确认应用程序使用的任何白名单内第三方JavaScript库的具体版本。
  2. 研究这些库是否存在已知漏洞。
  3. 构造恶意payload, 例如通过链接加载含有漏洞的版本。
    https://ajax.googleapis.com/ajax/libs/angularjs/1.1.1/angular.min.js。 这个版本存在CSP绕过漏洞。

代码示例:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.1/angular.min.js"></script>
<div ng-app ng-csp>{{$on.constructor('alert("XSS")')()}}</div>

这个例子中使用了angular,如果目标站点引入并错误地使用了老版本Angular, 通过 AngularJS 的特性执行了任意代码 alert("XSS")

2. 利用HTML标签的特性执行脚本

即便是严格的CSP,在不恰当处理HTML输入时,仍存在绕过的可能性。
如果某些可能执行脚本的HTML标签和事件属性(如 onerroronload 等)没有被充分限制或过滤,就存在利用可能。

操作步骤:

  1. 尝试提交多种包含内联事件处理器的HTML标签。
  2. 通过反复试验确定哪些标签和属性可以成功执行。

代码示例:

<img src='x' onerror='alert(1)'>

即使 script-src 设置限制了外部脚本加载, <img src='x' onerror='alert(1)'> 依旧尝试执行alert(1), 如果服务器对 <img> 标签的事件处理器过滤不严, 此攻击可能成功。

3. 寻找JSONP端点的漏洞

应用程序使用的第三方API如果是通过JSONP方式进行通信的, JSONP回调函数可能会成为一个弱点。

操作步骤:

  1. 审查所有使用JSONP方式进行通信的外部接口。
  2. 尝试修改回调函数来执行攻击载荷。

代码示例:

假定网站上使用了如下JSONP URL:

https://example.com/api/data?callback=parseResponse

修改请求, 让回调函数成为攻击代码:

https://example.com/api/data?callback=alert(1);

这个请求如果服务器没有充分验证 callback 参数, 可能直接拼接 callback 的值到响应中, 从而导致脚本执行。

4. 内部脚本执行

即便设置了正确的 script-src 指令, 应用程序内部自身的脚本逻辑缺陷,也有可能执行不该执行的逻辑, 触发 XSS 漏洞。比如, 如果对用户输入的数据处理不当,再进行页面输出, 也会有被利用的风险。

代码示例:

设想页面中存在以下 JavaScript 代码段:

let userInput = getUserInput(); // 从用户获取的输入, 未正确过滤或转义
let div = document.createElement('div');
div.innerHTML = "<img src=x onerror=alert(userInput)>"; // 这里尝试根据用户输入渲染 HTML
document.body.appendChild(div);

尽管设置了 CSP, 这段代码自身创建了一个可导致 XSS 的环境。

安全建议及最佳实践

1. 输入数据净化和转义: 对所有用户输入数据, 都要进行正确的编码或转义,再进行输出,这才能确保用户提供的数据不会被当做代码执行。

2. 严格的白名单: 定期审查和更新CSP中的白名单列表, 减少可能的风险源, 避免不必要的外部资源引入。使用可信的CDN, 且限制具体的脚本版本, 如通过子资源完整性(SRI)机制来加强。

代码示例(SRI):

<script src="https://use.fontawesome.com/releases/v5.15.3/js/all.js" integrity="sha384-XXX" crossorigin="anonymous"></script>

使用 integrity 属性确保只有预期版本的脚本能被加载。

3. 对动态内容添加过滤: 当应用场景不允许采用彻底的输入过滤,需要保持一部分HTML格式输出时, 使用可信的库来清除 HTML 中不安全的部分,如 DOMPurify。

代码示例:

import DOMPurify from 'dompurify';

let userInput = getUserInput(); // 获取用户输入
let clean = DOMPurify.sanitize(userInput); // 清理用户输入, 只留下安全的内容
// ... 使用 clean 变量进行页面输出 ...

4. 避免使用内联事件处理: 遵循最佳实践,将 JavaScript 代码从 HTML 结构中分离,避免使用内联事件处理程序。

5. JSONP的安全应用: 严格校验 JSONP 回调函数名称,避免随意执行第三方服务传递过来的参数值。