前言
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 | 带有用户保留数据的网站,如:论坛发帖、商品评论、用户私信 |
反射型(非长久型) | URL | HTML | 网站搜寻、跳转 |
DOM 型 | 后端数据库 / 前端存储 /URL | js | 前端 js 执行,如写了 eval 等 |
XSS 进攻计划
XSS 预防次要分为两大部分:
- 避免攻击者提交恶意代码
- 浏览器执行恶意代码
从这两大部分能够整顿出不同的防备思路:
办法 | 步骤 | 类型 |
---|---|---|
利用模板引擎 | 1 | 存储型、反射型 |
限度及本义输出 | 1 | 存储型、反射型 |
限度 js 执行办法 | 2 | DOM 型 |
Content Security Policy | 1、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-src | 1 | 大部分资源未定义时会读取这个配置,多数不会,比方:frame-ancestors,优先级低 |
script-src | 1 | 定义无效的 js 资源 |
style-src | 1 | 定义无效的 css 资源 |
img-src | 1 | 定义无效的图片资源 |
connect-src | 1 | 加载 XMLHttpRequest、WebSocket、fetch、<a ping> 以及 EventSource 资源,如果不被容许则返回 400 状态码 |
font-src | 1 | 定义无效的字体资源,通过 @font-face 加载 |
object-src | 1 | 定义无效的插件资源,比方:<object> ,<embed> 或者<applet> |
media-src | 1 | 定义无效的音频及视频资源,比方:<audio> ,<video> 等元素 |
frame-src | 1 | 定义无效的 frame 加载资源,在 csp2 中,frame-src 被废除,应用 child-src;在 csp3 中,又被启用,与 child-src 同时存在,如果 child-src 不存在,frame-src 也会起作用 |
sandbox | 1 | 容许 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-uri | 1 | 向这个 uri 发送失败报告,也能够应用 Content-Security-Policy-Report-Only 来作为 http header 进行发送但不阻塞网络资源的解析,在 csp3 中 report-uri 被废除,改用 report-to 指令 |
child-src | 2 | 定义 web workers 以及蕴含嵌套上下文的资源加载,比方 <frame> 以及<iframe> |
form-action | 2 | 定义无效的 html 标签 <form> 的 action 资源加载 |
frame-ancestors | 2 | 定义无效内嵌标签诸如 <frame> 、<iframe> 、<object> 、<embd> 、<applet> 资源加载,当值为 ’none’ 时,大抵能够和 X -Frame-Options: DENY 相当 |
plugin-types | 2 | 定义通过 <object> 及<embd> 的 MIME 资源类型加载,对于 <applet> 则必须明确 MIME 为 application/x-java-applet |
base-uri | 2 | 定义通过 <base> 的 html 标签中的 src 属性援用的 url 资源加载 |
report-to | 3 | 定义 Report-To 的 http 响应头字段 |
worker-src | 3 | 限度通过 Worker、SharedWorker 以及 ServiceWorker 的 url 资源加载 |
manifest-src | 3 | 限度 manifests 的 url 资源加载 |
prefetch-src | 3 | 定义预渲染及预加载申请的资源加载,比方通过 <link> 标签的 rel=”prefetch” 或 rel=”prerender” 属性 |
navigate-to | 3 | 限度 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.com | 1 | 容许明确域名的资源加载 | img-src domain.example.com |
*.xxx.com | 1 | 容许加载任何例如:example.com 子域下的资源 | img-src *.example.com |
https://xxx.com | 1 | 仅容许 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 如何落地,理解一下