CSP

Content-Security-Policy (CSP):反JS注入的最后防线

我们习惯于在浏览器中输入一个网址,然后期待内容能够安全、完整地呈现在眼前。然而,这其中存在诸多不确定因素。在用户请求一个网页到最终浏览器渲染的整个链路上,数据包可能要经过多个网络节点,其中就包括一些“中间设备”或“流量网关”。这些设备在设计上可能为了路由优化、流量统计、内容缓存等目的,但在某些情况下,它们也可能成为未经授权修改数据内容的源头。

想象一下,你发出的一个信件,在邮寄过程中被中途打开,并且被悄悄地塞入了一张与你本意无关的广告传单。当你收到这封信时,它看起来似乎没问题,但内容却已经不再纯粹。在网络世界中,这种现象被我们称之为“HTTP劫持”(HTTP Hijacking)。

HTTP劫持:隐形的威胁与用户痛点 #

HTTP劫持,顾名思义,是指在HTTP通信过程中,网络流量被拦截或修改的行为。虽然HTTPS协议在很大程度上解决了传输过程中的内容篡改问题,但并非所有的网络流量都严格使用HTTPS,尤其是在一些初次连接、跳转或特定资源加载的场景。当HTTP劫持发生时,攻击者或某些“中间设备”可能会在合法的网页内容中注入额外的代码,最常见的就是JavaScript代码。

这种未经授权的JavaScript注入可能导致一系列问题:

  • 广告弹窗和强制跳转: 用户访问的页面可能突然弹出无关广告,或者被强制跳转到其他站点,严重干扰用户体验。
  • 数据窃取: 恶意注入的JavaScript可以读取用户的Cookie、会话信息,甚至在用户输入密码时捕获这些敏感数据。
  • 页面内容篡改: 原始页面结构和内容可能被改变,显示错误或虚假信息,影响网站的品牌形象和可信度。
  • 功能破坏: 注入的代码可能与原有页面逻辑冲突,导致页面功能异常或崩溃。

对于网站管理员、运维人员和开发者而言,这些问题带来巨大的困扰。用户体验受损、数据安全面临风险、业务流程被中断,甚至可能面临合规性挑战。这些都指向了一个核心痛点:如何确保用户在与网站交互时,所看到和执行的代码是完全可信的,且未经任何第三方篡改?尤其是在面对“局部局域网环境”中可能出现的“某地区运营商”进行流量修改,或因自身业务需求涉及多域名跳转的场景下,如何保障整个链路的安全性与纯净性,成为了迫切需要解决的技术难题。

解决这一痛点,需要一种机制,它不仅能够检测到篡改,更重要的是,能够在客户端层面对恶意注入的代码进行“免疫”,确保浏览器只执行我们允许的、来自可信源的代码。这正是Content-Security-Policy(CSP)所能发挥的关键作用。

Content-Security-Policy (CSP):客户端反注入的最后防线 #

Content-Security-Policy (CSP) 是一种由Web服务器向浏览器发送的HTTP响应头。它的核心理念是让网站开发者能够明确地告诉浏览器:哪些资源(如脚本、样式表、图片、字体、媒体文件等)是可信的,以及它们可以从哪些源加载。如果浏览器尝试加载或执行不符合这些策略的资源,它将会被阻止。

可以把CSP想象成一个网站的“安全保镖”,它站在用户浏览器的门口,手持一份详细的“白名单”。任何试图进入浏览器(即被加载或执行)的资源,都必须经过这个保镖的核对。如果资源不在白名单上,或者来自非授权的来源,保镖就会立即将其拦截在外,确保只有经过批准的“访客”才能进入。

CSP的核心工作原理 #

CSP通过定义一系列指令(directives)来工作,每个指令都指定了特定类型的资源可以从哪些源加载。例如:

  • script-src:定义JavaScript脚本的允许加载源。
  • style-src:定义CSS样式表的允许加载源。
  • img-src:定义图片的允许加载源。
  • default-src:作为所有未明确指定指令的默认回退策略。
  • connect-src:定义XMLHttpRequest (XHR)、WebSocket等连接的允许目标。
  • frame-src:定义<iframe>标签中内容的允许加载源。

这些指令可以指定多种源,例如:

  • 'self':允许从当前域名加载资源。
  • *.example.com:允许从example.com及其所有子域加载资源。
  • https://cdn.example.com:只允许从特定的CDN安全地加载资源。
  • 'none':禁止加载任何资源。
  • 'unsafe-inline':允许行内脚本或样式(强烈不推荐,除非无法避免)。
  • 'unsafe-eval':允许使用eval()等从字符串创建代码的方法(强烈不推荐)。
  • 'nonce-<base64-value>':允许带有匹配nonce属性的行内脚本或样式。
  • 'sha256-<base64-hash>':允许与指定哈希值匹配的行内脚本或样式。

当浏览器接收到一个包含CSP头的HTTP响应后,它会解析这些策略,并严格遵守。如果页面中存在一个<script>标签,而其src属性指向的域名不在script-src指令的白名单中,或者它是一个未被noncehash授权的行内脚本,浏览器就会拒绝执行该脚本。

CSP:抵抗HTTP劫持的有效手段 #

回到HTTP劫持的问题,如果“中间设备”或“某地区运营商”在HTTP响应中注入了未经授权的JavaScript代码,无论这些代码是来自一个外部的恶意域名,还是直接作为行内脚本被插入,CSP都能发挥其防御作用。

例如,一个网站期望其所有JavaScript都从自己的域名 (example.com) 和一个特定的CDN (cdn.example.com) 加载。它可以在响应头中设置如下CSP:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self';

这条策略告诉浏览器:

  1. 默认所有资源(default-src)只能从当前域名('self')加载。
  2. JavaScript脚本(script-src)只能从当前域名或https://cdn.example.com加载。
  3. 插件(object-src)一律禁止加载。
  4. 页面的base标签的URL(base-uri)只能是当前域名。

现在,假设一个“中间设备”尝试注入一个来自http://malicious-ad.com/inject.js的脚本,或者直接在HTML中插入 <script>alert('You are hijacked!');</script> 这样的行内脚本。

...