前言

Web平安问题始终是前端畛域一个绕不开的话题,但很多前端人员对Web的相干安全策略都只停留在面试过程中,本文次要是对上线过程中遇到的平安问题踩坑进行了一个总结,旨在对Web平安相干问题能有一个更为平面切身的领会,也心愿能给大家提供一些踩坑时候的参考。

背景

XSS vs CSRF

XSS

XSS是Cross-site scripting的缩写,为了和Cascading Style Sheets进行辨别,因此将其简写为XSS:

Cross-site scripting (XSS) is a security exploit which allows an attacker to inject into a website malicious client-side code. This code is executed by the victims and lets the attackers bypass access controls and impersonate users.

从MDN给出的定义能够看出,XSS是通过inject(注入)无害代码来实现攻打计划的,也就是说XSS的攻打计划是注入,这里个别是通过注入脚本,因为浏览器中dom、bom的为js提供了接口,因此js能够操作html和css,其也能够插入html片段等。

CSRF

很多人分不清CSRF,其实他们还是有很大区别的,只不过通常会应用XSS拿到一些权限后才进行CSRF。CSRF是Cross-Site Request Forgery的简写:

CSRF (Cross-Site Request Forgery) is an attack that impersonates a trusted user and sends a website unwanted commands.

从MDN定义能够看进去,CSRF是通过伪造来进行攻打,也就是其本质目标须要用所有伎俩来假装本人获取本不属于它的权限资源

区别

名称目标备注
XSS篡改内容不关怀权限
CSRF获取资源只关怀权限

XSS分类

名称寄存侵入形式场景
存储型(长久型)后端数据库HTML带有用户保留数据的网站,如:论坛发帖、商品评论、用户私信
反射型(非长久型)URLHTML网站搜寻、跳转
DOM 型后端数据库/前端存储/URLjs前端js执行,如写了eval等

XSS进攻计划

XSS预防次要分为两大部分:

  1. 避免攻击者提交恶意代码
  2. 浏览器执行恶意代码

从这两大部分能够整顿出不同的防备思路:

办法步骤类型
利用模板引擎1存储型、反射型
限度及本义输出1存储型、反射型
限度js执行办法2DOM型
Content Security Policy1、2存储型、反射型、DOM型

从上述列表能够看出,Content Security Policy对XSS的防备是比拟好的,那么接下来就到了本文的重点内容,在下一part会重点介绍CSP相干的内容

CSP

内容安全策略(Content Security Policy)是古代浏览器平安机制中的一项重要内容,从图上能够看出除了老IE仅反对了csp的sandbox外,其余支流浏览器都已反对了csp,并且w3c也对整体做了对立的要求,具体能够参看w3c的官网文档Content Security Policy Level 2

简介

Content-Security-Policy 是古代浏览器用来加强document安全性的一个响应头字段,其用来限度诸如js、css以及其余浏览器所需资源的加载。也就是说,csp的实质是一个限度资源加载的策略,其通过解析csp的指令及值进行具体资源的限度,具体的实现能够看最初一part源码中chromium的相干实现。另外除了在header增加csp外,在html的dom中也能够通过meta标签来进行限度,只是当二者所限度的资源指令及值都一样的时候,header中的优先级较高。

指令

指令版本正文
default-src1大部分资源未定义时会读取这个配置,多数不会,比方:frame-ancestors,优先级低
script-src1定义无效的js资源
style-src1定义无效的css资源
img-src1定义无效的图片资源
connect-src1加载XMLHttpRequest、WebSocket、fetch、<a ping>以及 EventSource资源,如果不被容许则返回400状态码
font-src1定义无效的字体资源,通过@font-face加载
object-src1定义无效的插件资源,比方:<object><embed>或者<applet>
media-src1定义无效的音频及视频资源,比方:<audio><video>等元素
frame-src1定义无效的frame加载资源,在csp2中,frame-src被废除,应用child-src;在csp3中,又被启用,与child-src同时存在,如果child-src不存在,frame-src也会起作用
sandbox1容许iframe的sandbox属性,sandbox会采纳同源策略,阻止弹窗、插件及脚本执行,能够通过不设置sandbox属性,而是通过allow-scripts、allow-popups、allow-modals、allow-orientation-lock、allow-pointer-lock、allow-presentation、allow-popups-to-escape-sandbox、allow-top-navigation来透给sandbox字段限度
report-uri1向这个uri发送失败报告,也能够应用Content-Security-Policy-Report-Only来作为http header进行发送但不阻塞网络资源的解析,在csp3中report-uri被废除,改用report-to指令
child-src2定义web workers以及蕴含嵌套上下文的资源加载,比方<frame>以及<iframe>
form-action2定义无效的html标签<form>的action资源加载
frame-ancestors2定义无效内嵌标签诸如<frame><iframe><object><embd><applet>资源加载,当值为'none'时,大抵能够和X-Frame-Options: DENY相当
plugin-types2定义通过<object><embd>的MIME资源类型加载,对于<applet>则必须明确MIME为application/x-java-applet
base-uri2定义通过<base>的html标签中的src属性援用的url资源加载
report-to3定义Report-To的http响应头字段
worker-src3限度通过Worker、SharedWorker以及ServiceWorker的url资源加载
manifest-src3限度manifests的url资源加载
prefetch-src3定义预渲染及预加载申请的资源加载,比方通过<link>标签的rel="prefetch"或rel="prerender"属性
navigate-to3限度document的任何形式跳转url,比方通过link跳转,或者window.location被执行,如果form-action被设置,则本规定会被替换,即对于form而言,form-action优先级更高

版本形容样例
*1未知,容许除了data、blob、filesystem、schemes之外的任何url资源加载img-src *
'none'1不容许加载任何的资源object-src 'none'
'self'1只容许同源资源加载script-src 'self'
'data:'1容许data格局的资源加载,比方:Base64图片编码img-src 'self' data:
xx.xxx.com1容许明确域名的资源加载img-src domain.example.com
*.xxx.com1容许加载任何例如:example.com子域下的资源img-src *.example.com
https://xxx.com1仅容许https协定下域名的资源加载img-src https://cdn.com
https:1容许加载https下任何域名的资源img-src https:
'unsafe-inline'1容许应用内联元素加载资源,诸如:级联款式、句柄办法、script内容标签以及javascript:URIs等script-src 'unsafe-inline'
'unsafe-eval'1容许不平安的动静js代码执行script-src 'unsafe-eval'
'sha256-'2容许内联script及css匹配hash值后的执行script-src 'sha256-xyz...'
'nonce-'2容许应用蕴含nonce属性的内联script及css执行,nonce应该是一个平安的任意值,并且不能被重复使用script-src 'nonce-r@nd0m'
'strict-dynamic'3容许加载非解析型脚本资源,比方:document.createElement('script')script-src 'strict-dynamic'
'unsafe-hashes'3容许应用事件句柄的形式进行资源加载,然而不容许应用内联脚本及javascript:执行的形式script-src 'unsafe-hashes' 'sha256-abc...'

案例

通过上一part的介绍,咱们大抵理解了CSP相干一些用法,上面具体看一下在前端业务中的具体实际

nginx

server {    listen 9080;    server_name localhost;    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; frame-ancestors 'self'; object-src 'none'";    add_header X-Content-Type-Options nosniff;    add_header X-Frame-Options SAMEORIGIN;}

咱们先来看一下nginx中的所有都严格控制的状况,咱们再console中的v8实例下,应用创立一个标签来测试一下

这时咱们发现,浏览器中报了

Refused to apply xxx because it violates the following Content Security Policy directive...

的谬误;同时,咱们关上任意一个network中的申请连贯,咱们发现,在响应头中,呈现了

如上图所示的Content-Security-Policy: xxx的字段

接着,咱们依据返回的谬误,进行适度的csp限度放开

server {    listen 9080;    server_name localhost;    add_header Content-Security-Policy "default-src *; script-src * 'unsafe-inline' 'unsafe-eval'; img-src * data:;";    add_header X-Content-Type-Options nosniff;    add_header X-Frame-Options SAMEORIGIN;}

批改过后,发现之前的报错隐没了,测试胜利

html

最初,咱们来看一下html中meta标签的一个应用状况

<!DOCTYPE html><html lang="en">  <head>    <meta charset="utf-8" />    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta http-equiv="Content-Security-Policy" content="default-src *; script-src 'unsafe-inline' 'unsafe-eval'; img-src *;">    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />    <meta http-equiv="Pragma" content="no-cache" />    <meta http-equiv="Expires" content="0" />    <meta name="format-detection" content="telephone=no" />    <meta name="viewport" content="width=device-width,initial-scale=1.0" />    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />    <title><%= htmlWebpackPlugin.options.title %></title>  </head>  <body>    <noscript>      <strong        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without        JavaScript enabled. Please enable it to continue.</strong      >    </noscript>    <div id="app"></div>    <!-- built files will be auto injected -->  </body></html>

在不应用nginx配置csp的状况下,在html中配置meta也是能够起到同样的作用,然而当二者同时存在且字段统一时,会优先解析header中的响应

源码

这一part咱们通过chromium源码来看一下chrome中是如何实现CSP的

content_security_policy_parsers

bool IsCSPDirectiveNameCharacter(UChar c) {    return IsASCIIAlpanumeric(c) || c == '-';}bool IsCSPDirectiveValueCharacter(UChar c) {    return IsASCIISpace(c) || (IsASCIIPrintable(c) && c != ',' && c != ';');}

在network中通过解析key-value值对Content-Security-Policy中的指令及值进行获取

resource_fetcher

bool ResourceFetcher::ResourceNeedsLoad(Resource* resource, const FetchParameters& params, RevalidationPolicy policy) {    if(archive_)         return false;        if(resource->GetType() == ResourceType::kFont && !params.IsLinkPreload())        return false;        if(resource->GetType() == ResourceType::kImage && (ShouldDeferImageLoad(resource->Url()) || params.GetImageRequestBehavior() == FetchParameters::kDeferImageLoad)) {        return false;    }    return policy != RevalidationPolicy::kUse || resource->StillNeedsLoad();}

对是否进行资源加载进行判断

http_equiv

void HttpEquiv::ProcessHttpEquivContentSecurityPolicy(LocalDOMWindow* window, const AtomicString& equiv, const AtomicString& content) {    if(!window || !window->GetFrame())        return;    if(window->GetFrame()->GetSettings()->GetBypassCSP())        return;    if(EqualIgnoringASCIICase(equiv, "content-security-policy")) {        Vector<network::mojom::blink::ContentSecurityPolicyPtr> parsed = ParseContentSecurityPolicies(content, network::mojom::blink::ContentSecurityPolicyType::kEnforce, network::mojom::blink::ContentSecurityPolicySource::kMeta, *(window->GetSecurityOrigin()));        window->GetContentSecurityPolicy()->AddPolicies(mojo::Clone(parsed));        window->GetPolicyContainer()->AddContentSecurityPolicies(std::move(parsed));    } else if (EqualIgnoringASCIICase(equiv, "content-security-policy-report-only")) {        window->GetContentSecurityPolicy()->ReportReportOnlyInMeta(content);    } else {        NOTREACHED();    }}

咱们看到在Document中执行相干csp的一些逻辑

worker_content_settings_client

bool WorkerContentSettingsClient::AllowScriptFromSource(bool enabled_per_settings, const blink::WebURL& script_url) {    bool allow = enabled_per_settings;    if(allow && content_setting_rules_) {        GURL top_frame_origin_url = top_frame_origin_.GetURL();        for(const auto& rule: content_setting_rules_->script_rules) {            if(rule.primary_pattern.Matches(top_frame_origin_url) && rule.secondary_pattern.Matches(script_url)) {                allow = rule.GetContentSetting() != CONTENT_SETTING_BLOCK;                break;            }        }    }    if(!allow) {        EnsureContentSettingsManager();        content_settings_manager_->OnContentBlocked(render_frame_id_, ContentSettingsType::JAVASCRIPT);        return false;    }    return true;}

最初,咱们能够简略看一下是否容许加载content的一些逻辑,以script加载为例

总结

不管是否是it行业,只有是工程师,都须要对工程项目中的平安问题进行相干的器重,对于前端工程来说,常见的XSS、CSRF等相干常识也须要咱们在理论工程项目中进行原理摸索,以及对常见的解决方案可能做到了熟于心,这样在架构设计及工程实际过程中能力做到利用工程的稳固可继续,最初附上一张chrome中的xss信息的ER图,从中领会一下chrome对于平安问题的架构设计及信息链路把控的优良与谨严

参考

  • Cross-site scripting
  • 前端平安系列(一):如何避免XSS攻打?
  • 前端平安系列(二):如何避免CSRF攻打?
  • xss攻打和csrf攻打的定义及区别
  • 【第849期】如何让前端更平安?——XSS攻打和进攻详解
  • 预测最近面试会考 Cookie 的 SameSite 属性
  • Content-Security-Policy
  • 内容安全策略(CSP)详解
  • CHROME扩大笔记之回绝unsafe-eval求值
  • CSP内容安全策略
  • Module ngx_http_headers_module
  • Content Security Policy Reference
  • Content Security Policy Level 2
  • WebKit技术底细
  • 前端的CSP & CSP如何落地,理解一下