与安全相关的 HTTP 头

标签:无

由于 Web 安全问题越来越严重,各种浏览器都加强了安全策略,也引入了很多新的 HTTP 头。最近在做读读日报的扫码登录时,就被这些玩意折腾了一天,于是记录在此。

首先说下跨域请求。
为了描述的简单,先假设某用户为 A,他用的浏览器是 B,他访问的网站是 C,C 需要访问另一个网站 D 的资源 E,攻击者有个攻击网站 F。
当 C 和 D 的域名、端口或协议不同时,对 E 的访问就称为跨域请求。
以下都属于跨域:
  • http://example.com、http://www.example.com 和 http://abc.example.com
  • http://www.example.com:80 和 http://www.example.com:8080
  • http://www.example.com 和 https://www.example.com

当不跨域时,浏览器并不对其做太多安全限制;但因为跨域请求会存在安全隐患,所以默认会有如下的限制:
  • 仅允许 GET、HEAD 和 POST 请求。
  • 仅允许手动设置 Accept、Accept-Language、Content-Language 和 Content-Type 头。
  • Content-Type 头仅允许使用 application/x-www-form-urlencoded、multipart/form-data 和 text/plain 这三种值。
满足这些限制的请求,这里称为 simple request。

虽然这些限制使得安全性得到了保障,但也极大地限制了其使用场景。而本文要说的第一批 headers 就是用来解决这些跨域请求的限制的,它们以「Access-Control-」开头,最重要的两个 headers 有:
  • Access-Control-Allow-Origin:限制这个请求能从哪些 URI 访问。
    这应该是其中最重要的一个 header,使用跨域 AJAX 时,被调用方 E 需要输出这个头,指明能从哪个网站访问。如果未输出这个头的话,只允许同域名的访问。
    如果是一个公共服务,允许任意调用的话,将其设为「*」即可;如果要严格设置能使用的网站,将其设为「http://www.example.com」即可(http 和 https 不能混用)。
    假如这个头没有正确设置的话,用户 A 在访问攻击网站 F 时,F 可以在 A 不知情的情况下发起一个跨域请求,访问资源 E。
  • Access-Control-Allow-Credentials:允许这个请求使用 Cookie。
    这是另一个极为重要的 header。一般情况下,跨域 AJAX 不会附带用户的 Cookie,也不允许设置用户的 Cookie,因此不能很方便地进行登录等需要用到 Cookie 的操作。
    要使用它的话,首先 C 在构造这个 XMLHttpRequest 对象时,需要设置 withCredentials 属性:
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.withCredentials = true;
    xhr.onreadystatechange = handler;
    xhr.send();
    如果是用 jQuery 的话,需要这样设置:
    $.ajax({
    	'url': url,
    	'type': 'GET',
    	'xhrFields': {'withCredentials': true},
    	'success': handler
    })
    接着,D 在输出响应时,将这个 header 值设为 true,就能使用 Set-Cookie 来设置 cookie 了(由于 cookie 可以设置在根域上,因此可以跨子域设置 cookie)。

另外还有一个概念叫 preflighted request。在不满足 simple request 的限制时,浏览器会先往这个地址发起一个 OPTION 请求,询问是否可用,然后再发起实际的请求。
它会用到这些 headers:
  • Access-Control-Request-Method 和 Access-Control-Allow-Methods:声明所用及允许的 HTTP methods。
    普通的跨域请求只支持 GET、HEAD 和 POST 方法,想用其他方法的话,访问时需要将 Access-Control-Request-Method 设为 DELETE 等其他方法,E 则在 Access-Control-Allow-Methods 中返回所有支持的方法(用逗号隔开)即可。
  • Access-Control-Request-Headers 和 Access-Control-Allow-Headers:声明所用及允许的 HTTP headers。
    类似上一组,用于支持其他的请求头。
  • Access-Control-Max-Age:告诉浏览器多长时间内,不需要发相同的 preflighted request,直接使用缓存的结果。

除此之外,还有个很重要的 header 叫作 Content-Security-Policy,用来定义页面可以加载哪些资源,目前有 2 个 levels。
其中,level 1 可以用这些指令(多个指令之间用分号分隔):
  • default-src:默认的加载策略。
  • script-src:允许加载这个域的 JavaScript。
  • style-src:允许加载这个域的 CSS。
  • img-src:允许加载这个域的图片。
  • connect-src:允许这个域的 AJAX 和 WebSocket 请求。
  • font-src:允许加载这个域的字体。
  • object-src:允许加载这个域的 object、embed 或 applet 对象。
  • media-src:允许加载这个域的 audio 或 video 对象。
  • frame-src:允许加载这个域的 frame 或 iframe。
  • sandbox:对这个资源启用沙箱。
  • report-uri:值为 URI,如果请求的资源不被策略允许,往这个 URI POST 汇报日志。
其中,*-src 的可用指令值有(多个指令值之间用空格分隔):
  • (空):允许任何内容。
  • 'none':不允许允许任何内容。
  • 'self':允许同源的内容。
  • data:允许 data 协议。
  • www.example.com:允许 www.example.com 的内容。
  • https://www.example.com:允许 https://www.example.com 的内容。
  • *.example.com:允许 example.com 及其子域的内容。
  • 127.0.0.1:*:允许 127.0.0.1 所有端口的内容。
  • https::允许 HTTPS 内容。
  • 'unsafe-inline':允许内联内容
  • 'unsafe-eval':允许 eval 等方法从字符串生成可执行代码。
举例来说,如果怕被插入其他域名的 JavaScript 脚本,而又要允许 Google 统计的话,可以设置:
Content-Security-Policy: script-src 'self' *.google-analytics.com
而如果怕启用后有问题,可以用 Content-Security-Policy-Report-Only 这个 header。它并不进行实际的拦截,但遇到问题仍然会往 report-uri 发送报告。

而 level 2 主要增加了这些:
  • child-src:取代 frame-src(适用于多级嵌套的情况)。
  • frame-ancestors:取代 X-Frame-Options(见下文),用于限制页面能被哪些页面嵌套(适用于多级嵌套的情况)。
  • form-action:可以往这些 URIs 提交表单。
  • referrer:可用值有 no-referrer、no-referrer-when-downgrade、origin、origin-when-cross-origin 和 unsafe-url。
  • upgrade-insecure-requests:把页面中所有的 HTTP 请求都替换成 HTTPS 请求。
此外,script-src 指令也增加了 nonce 和 hash 的取值,可以防止意外地插入 inline JavaScript。
  • 前者需要对 inline script 标签输出一个随机的 nonce 属性(假设是 nonce="abcd"),并在 header 里指定 Content-Security-Policy: script-src 'nonce-abcd'。
  • 后者需要对 inline script 标签里的内容(含空白部分)计算 hash 值,支持 sha256、sha384 和 sha512(假设是 abcd...),并在 header 里指定 Content-Security-Policy: script-src 'sha256-abcd...'。

还有一些比较杂乱的 headers:
  • Strict-Transport-Security:强制使用 HTTPS 访问。
    当用户访问过一次这个网站的 HTTPS 页面后,设置了这个 header,以后在访问这个网站的 HTTP 页面时,会自动转换成 HTTPS 请求。
    它可以设置三个值:
    • max-age:声明多长时间内有效。
    • includeSubDomains:可省,子域也启用这个规则。
    • preload:可省,有一批列表限制了部分网站仅允许 HTTPS 访问,不需要访问过一次 HTTPS 页面才启用。
    它比服务器强制跳转 HTTPS 更安全和高效,但假如遇到 HTTPS 证书出问题等情况,就没法临时降级到 HTTP 页面了。
  • X-Frame-Options:设置该页面是否能通过 frame、iframe 和 object 标签,包含其他页面里。
    它可选的值有:
    • DENY:不允许
    • SAMEORIGIN:仅限同域
    • ALLOW-FROM:仅限某个域。
  • X-XSS-Protection:启用 XSS 保护。
    它可选的值有:
    • 0:禁用。
    • 1:启用。
    • 1; mode=block:启用,且在检查到 XSS 攻击时,停止页面渲染。
  • X-Content-Type-Options:值可为 nosniff,用于禁用浏览器的类型猜测,避免把图片当成 JavaScript 代码来加载等情况。

0条评论 你不来一发么↓

    想说点什么呢?