前端安全
前言
众所周知,前端开发面试过程往往聊起网络相关的面试题,就不得不提起前端安全相关知识,总体来形容这部分模块其实是十分广发和复杂的,我们面对安全问题,其实最有效的办法是先了解攻击本身的原理,是如何 攻击 ,在这个基础上我们再进行防范,这样我们就无敌了。哈哈哈哈哈,下面我们通过前端术语来说明他们的相关知识。
前端安全相关知识目录
浏览器相关:
- CSRF 跨站请求伪造
- XSS 跨站脚本攻击
- CSP 内容安全策略
- HSTS HTTP严格传输安全
- CDN 劫持
- X-Frame-Options 是否允许当前页面在 iFrame 中展示
- 限制 iframe 使用
- SRI ubresource Integrity - 子资源完整性
- Referrer-Policy 请求的来源地址信息采集
Node(服务器端)相关:
- 本地文件操作相关,路径拼接导致的文件泄露
- ReDos 攻击(Reg正则表达式攻击),让服务器宕机,服务器
- 时序攻击 穷举获取用户数据
CSRF 跨站请求伪造
概念:Cross-site request forgery 跨站请求伪造
顾名思义,跨站请求伪造(CSRF)是一种冒充受信任用户,向服务器发送非预期请求的攻击方式。例如,这些非预期请求可能是通过在跳转链接后的 URL 中加入恶意参数来完成:
<img src="https://www.example.com/index.php?action=delete&id=123">
对于在
https://www.example.com有权限的用户,这个<img>标签会在他们根本注意不到的情况下对https://www.example.com执行这个操作,即使这个标签根本不在https://www.example.com内亦可。
CSRF攻击的流程
- 被害者(前端小白)登录
A网站(这里可以理解成保存了用户敏感信息的网站),并且保存了登录凭证(Cookie) - 攻击者(前端大神)引诱 受害者(前端小白)访问
B网站(存在风险的网站) B网站就会向A网站发送了一个请求(伪造请求包含多种方式)A网站发现了请求是带有用户的登录信息的,所以就会正常处理请求(导致用户信息被窃取)
常见CSRF 伪造 请求的类型
Get 类型 CSRF 攻击
<img src="https://www.example.com/index.php?amount=10086&id=123">
受害者访问这个包含这个 img 的页面以后,浏览器就会自动发起 HTTP 请求,被攻击的网站就会受到一次包含登录信息的跨域请求了。
POST 类型 CSRF 攻击
<form action="http://example.com/account" method="POST" id="forms">
<input type="hidden" name="account" value="kobe" />
<input type="hidden" name="password" value="123456" />
<input type="hidden" name="id" value="hacker" />
</form>
<script> document.getElementById('forms').submit(); </script>
其实和图片的 GET 请求类似,只是变成了表单的形式自动提交了,随便一个人都能写出上面的代码来发送请求,后端API 不能将安全的问题限制在 只允许某种请求方法上的
跳转类型的 CSRF
这种需要通过用户点击才行达到跳转的效果,通常是通过1张镁铝图片引诱用户点击,骗取用户点击跳转到目标网站,进行窃取用户信息
<a href="http://example.com/csrf.php?account=kobe&password=123123123" taget="_blank">
<img src="镁铝图片"/>
<a/>
CSRF 特征
经过上面的分析和描述常见的攻击方式,我们不妨总结一下 CSRF的特征
- 攻击发起在第三方网站,而不是被攻击的网站。
- 攻击者是冒充受害者提交操作,而不是直接窃取用户信息数据
- 整个过程攻击者是没有获取到用户的
Cookie,而是冒充 - 跨站攻击很难防范,因为都发生在第三方的网站
防范 CSRF
同源检测
针对 CSRF 都是来自第三方的网站,那么我们做一下同源检测,直接禁止第三方的域名向我们后端服务器发起请求(或者添加白名单)仅允许白名单的域名请求。
阻止第三方域名的访问跨域同源检测 一般是检测 Request header 请求头中的信息
- Origin Header
- Referer Header
这两个请求头都不能在前端自定义修改,因此不能骗过检测。我们的后端服务器可以通过解析这两个 Header 中 域名,确认请求的来源。
Origin 相当于 Referer 来说,是不带有路径的信息的。Referer 带有路径地址 Path
Cookie 安全设置 SameSite 同源检测
这里提到的 Cookie SameSite 我们可以联动到上一篇我们的 写 Cookie 文章 关于Cookie 你不知道的
业务场景是这样的,当我们登录到网站以后,会从响应头里面返回服务器的 Cookie 信息,而当 Cookie 设置了 SameSite=strict 则表示完全禁用第三方站点请求头携带 Cookie
Cookies samesite 属性介绍
- Strict 严格模式(完全禁用第三方
cookie) - Lax 宽松模式,当使用 POST 请求,img 请求,页面中嵌套 iframe 都不会携带
cookie - None 不限制第三方域名的使用,不限制
Token 验证
业务场景展示介绍
- CSRF Token
- 用户打开页面的时候,服务器利用加密算法生成一个 Token
- 每次页面记载的时候,前端把获取到 Token,加到能够发送请求的 HTML 元素上去,比如Form,a,img
- 每次 js 发起请求 也都携带 Token
- 服务器每次接受请求时候,就校验 Token有效性,第三方的网站发出的请求是无法获取用户页面中 Token值
- 双重 Cookies(不安全)
- 用户在访问网站的时候,服务器向浏览器注入一个额外 Cookies 比如 csrfcookies=xxx
- 每次前端发起请求的时候,都会拼接上一个参数 csrfcookies=xxxxx
- 服务校验 csrfcookies 是否正确
- cookies 不是 HTTP only的
- 如果前端域名和服务器端域名不一致,比如 前端 fe.a.com 服务器端 rd.a.com cookies 只能在 a.com 上,并且 设置为 非 HTTP only
- a.com 下 每一个子域名都可以 获取到 这个 cookies
- 一旦 某个子域名早到 xss 攻击,cookies 很容易就会被窃取或者是篡改
XSS 跨站脚本攻击
跨站脚本攻击(Cross-site scripting,XSS)是一种安全漏洞,攻击者可以利用这种漏洞在网站上注入恶意的客户端代码。若受害者运行这些恶意代码,攻击者就可以突破网站的访问限制并冒充受害者。
XSS 危害性
如果 Web 应用程序没有部署足够的安全验证,那么,这些攻击很容易成功。浏览器无法探测到这些恶意脚本是不可信的,所以,这些脚本可以任意读取 cookie,session tokens,或者其它敏感的网站信息,或者让恶意脚本重写HTML内容。
跨域攻击,攻击者 想尽一切办法把恶意代码注入到网页中
XSS 攻击场景
- 评论区植入 js 代码 (即可以输入的地方)
- url 拼接 js 代码
https://juejin.cn/editor/drafts/7079004494406615053?<script>恶意代码</script>
XSS 攻击的类型
1. 存储型 Server
注入型脚本永久存储在目标服务器上。当浏览器请求数据时,脚本从服务器上传回并执行。
论坛发帖,商品评价,用户私信等等 用户保存数据的网站
存储型 Server 攻击步骤:
- 攻击者将恶意代码提交到 目标网站的数据库中
- 用户打开目标网站的时候,服务器端将 恶意代码(评论),拼接到 html 返回到浏览器上
- 用户浏览器上收到 html 后,混在其中的恶意代码就会执行了
- 窃取用户数据,发送到攻击网站,从而达到获取到用户的数据信息
2. 反射型 Server
当用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。Web服务器将注入脚本,比如一个错误信息,搜索结果等, 返回到用户的浏览器上。由于浏览器认为这个响应来自"可信任"的服务器,所以会执行这段脚本
攻击结合各种手段,诱导用户点击 恶意 URL,从而触发后面一系列的操作
通过 URL 传参的功能,比如网站的搜索或者跳转等
反射型 Server 攻击步骤:
- 攻击者构建出 自己恶意 url
- 用户打开带有恶意的 URL时候,网站服务器端将恶意代码从 URL 中取出,拼接到 HTML 中返回给浏览器
- 浏览器接收到响应后解析直接执行可执行的恶意代码
- 恶意代码窃取用户数据并发送到攻击者网站,或者调用当前网站攻击目标指定的操作
3. DOM 型 Browser
通过修改原始的客户端代码,受害者浏览器的 DOM 环境改变,导致有效载荷的执行。也就是说,页面本身并没有变化,但由于DOM环境被恶意修改,有客户端代码被包含进了页面,并且意外执行。
目前流行前后端分离的项目,反射型XSS 可以说就不生效了,但是对于 DOM 型的攻击,是不需要经过服务器,Js本身就能改变 HTML,正正是利用到了这一点 实现插入恶意脚本。
基于 DOM XSS 攻击步骤:
- 和 反射型是一样的 攻击者构建出 自己恶意 url,里面包含恶意的代码。
- 用户打开带有恶意代码的 URL
- 这里和反射型不同,是前端直接在 URL 取出恶意的代码并且执行。
- 窃取用户数据,发送到攻击网站,从而达到获取到用户的数据信息。
XSS 攻击的特征
- 顾名思义 反射型 的 XSS 恶意脚本存在 URL 里面,存储 XSS 的代码是放在数据库里面的。
- 反射型 XSS 攻击通常通过 URL 传递参数的功能,例如网站或者恶意跳转
- 存储型 XSS 攻击 在 保存用户数据的网站,例如论坛发帖的时候,或者用户私信
- 而基于 DOM 的 XSS 攻击中,主要是因为浏览器端的出现的安全漏洞做成的,因为不需要通过请求服务器就能实现,另外两种是属于服务器端的安全漏洞
如何防范 XSS 攻击
主旨:防止攻击者提交恶意代码,防止浏览器执行恶意代码。 通过我们的主旨进行防范
防范 存储型 XSS & 反射型 XSS 攻击
- 对数据进行严格的输入编码过滤,比如 html 元素,js,css,url。(其实不建议这样做会带来衍生的问题,乱码)
- 改成纯前端渲染数据,代码逻辑和数据分割开
- 对 HTML 做转义,将一下 Script 标签过滤掉
- CSP (Content Security Policy) (X-XSS-Protection) 本文中也有关于 CSP 相关的内容,请在后面查看
- Content-Security-Policy: default-src 'self' 所有加载的内容必须来自站点的同一个源
防范 DOM 型 XSS 攻击
因为本身 Javascript 实际上是不够严谨,存在比较多的安全漏洞,存在很多方法,可以加载脚本,跳转URL等操作
-
加载脚本的方法,存在漏洞将一些不可预计的脚本加载到页面上
- .innerHTML
- .outerHTML
- document.write()
-
Vue/React 技术栈,同样是原理,不过是换了 API 来实现方式
- v-html
- dangerouslySetInnerHTML
-
DOM 中的监听器
- location
- onload
- onclick
- onerror
-
定时器 和 eval 将一些不可信的字段拼接并且传递到 API
- setTimeout
- setInterval
- eval
其他防范方法
- 输入验证 phone,URL,电话号码,邮箱
- 开启浏览器的 XSS防御:Http Only Cookie 浏览器的Cookies 在设置的时候 加入
HttpOnly属性
CSP 内容安全策略
内容安全策略(Content Security Policy)简称 CSP (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS (en-US)) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。
这个是属于安全策略方面的内容,其实严格意义来说不属于 前端安全问题,是为了解决前端安全问题 XSS 所以才存在的安全策略,下面我们描述一下它的策略
- 一个网站管理者想要所有内容均来自站点的同一个源 (不包括其子域名) 同源策略原则
- 所有脚本必须从特定主机服务器获取可信的代码 用户的数据不会发送到外域。禁止外域提交的操作
- 禁止使用内联脚本,禁止未授权的脚本执行
本质上是一个白名单的原理,限制资源那些可以加载,哪些不能加载。开发者只需要通过配置,完全是由客户端进行限制的
如何使用 CSP
- 通过 Meta 配置
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
- 通过请求 Header 设置(存在兼容性)
Content-Security-Policy: default-src 'self' *.mailsite.com; img-src *
- 启用违法例的报告
Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi
然后你需要设置你的服务器能够接收报告,使其能够以你认为恰当的方式存储并处理这些报告。
HSTS 严格安全传输
HSTS 概念
HSTS(HTTP Strcit-Transport-Securit)即 HTTP 严格传输安全, 是一种 Web 安全策略机制,可保护网站免受协议降级攻击和 cookie 劫持、中间人攻击。它允许 Web 服务器声明浏览器(或其他符合要求的用户代理)应用使用安全的 HTTPS 连接与交互,而不是通过不安全的 HTTP 协议。通俗理解的话是 强制客户端使用 HTTPS 与 服务器端建立连接
它的业务场景是这样的,当我们发起一个 HTTP 请求的时候,这样存在中间人攻击潜在威胁,跳转过程可能被恶意网站利用来直接接触用户信息,而不是原来的加密信息。但是开启 HSTS 以后,就会帮 HTTP 请求自动跳转到 HTTPS 从请求响应中返回字段,告诉前端,只能使用 HTTPS 访问网站后端服务器。
HSTS 语法 & 如何正确使用
Strict-Transport-Security: max-age=<expire-time> 过期时间
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains 子域名也能生效
Strict-Transport-Security: max-age=<expire-time>; preload 预加载 HSTS
预加载 HSTS
谷歌维护着一个 HSTS 预加载服务。按照如下指示成功提交你的域名后,浏览器将会永不使用非安全的方式连接到你的域名。虽然该服务是由谷歌提供的,但所有浏览器都有使用这份列表的意向(或者已经在用了)。
HSTS 开启后 浏览器如何处理
这里举例 请求 facebook 地址,使用 HSTS 严格安全传输策略以后,在缓存时间之内,对应的域名请求都将会自动转换为安全的 HTTPS 方法。如果浏览器使用 HTTP 访问的话,浏览器就会通过307跳转为 HTTPS 访问,并且添加 Non-Authoritative-Reason: HSTS
点击劫持 X-Frame-Options
点击劫持是一种欺骗用户点击链接、按钮等的做法,这与用户认为的不同。例如,这可以用来窃取登录凭据或让用户在不知情的情况下安装恶意软件。
X-Frame-OptionsHTTP 响应头是用来给浏览器指示允许一个页面可否在<frame>、<iframe>、<embed>或者<object>中展现的标记。站点可以通过确保网站没有被嵌入到别人的站点里面,从而避免点击劫持攻击。
X-Frame-Options 属性设置描述
- DENY 表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。(可以理解成严格模式)
- SAMEORIGIN 表示该页面可以在相同域名页面的 frame 中展示。规范让浏览器厂商决定此选项是否应用于顶层、父级或整个链,有人认为该选项不是很有用,除非所有的祖先页面都属于同一来源(origin)
- ALLOW-FROM uri 这是一个被弃用的指令,不再适用于现代浏览器,请不要使用它。在支持旧版浏览器时,页面可以在指定来源的 frame 中展示。请注意,在旧版 Firefox 上,它会遇到与
SAMEORIGIN相同的问题——它不会检查 frame 所有的祖先页面来确定他们是否是同一来源。
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
限制 iframe 对当前网站某些操作
sandbox 属性 它对页面的操作施加限制,包括阻止弹出窗口、阻止插件和脚本的执行以及执行同源策略。主要针对 iFrame 合法使用
Content-Security-Policy: sandbox;
Content-Security-Policy: sandbox <value>;
属性详细参考跳转地址
CDN 劫持
这里关于 CDN 逻辑就不一一罗列了。CDN 劫持实际上是通过劫持用户的请求然后实际上跳转到 钓鱼网站(仿写银行网站),然后骗取用户输入的信息,实现数据验证劫持的这么一个操作。被劫持的网站实际上和访问的网站是完全一致,功能一致样子一致,这种网站通常 称为 钓鱼网站
CDN劫持防范措施 SRI 子资源完整性
sub resource intergrity 子资源完整性, 是指浏览器通过验证资源的完整性(通过CDN获取的资源)来判断是否被篡改
CDN 劫持流程 & 防范
- xxx-hash.js 注入到 index.html 并且上传到 cdn
- 用户在请求的时候,根据 xxx.hash.js 请求,而这个文件可能被篡改 (CDN劫持)
- 打包的时候 根据 js 文件内容 生成一个 hash 值,并且作为 intergrity 属性 注入到 Script 上
- 验证引入的CDN 资源,无法直接修改,但是如果劫持修改了 integrity 值,浏览器就会报错,无法正常加载资源
- 会在执行脚本或者应用样式表之前对比所加载文件的哈希值和期望的哈希值
<script
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
src="https://cdn.xxx.xx/js/index.js">
</script>
错误提示
Failed to find a valid digest in the 'integrity' attribute for resource 'https://unpkg.com/vue@3.0.5/dist/vue.global.js' with computed SHA-256 integrity 'Wr5PnkpmZ3oQFRZLfDrI6fsePSMak5h8rW2rqq+mdWg='. The resource has been blocked.
Referer-Policy 请求的来源地址信息采集
这个其实用来控制请求的来源地址的信息采集的
HTTP来源地址(referer,或HTTP referer)是 HTTP 表头的一个字段,用来表示从哪儿链接到目前的网页。换句话说,借着HTTP来源地址,目前的网页可以检查访客从哪里而来,这也常被用来对付伪造的跨网站请求。
Referer-Policy 语法
Referrer-Policy: no-referrer
Referrer-Policy: no-referrer-when-downgrade
Referrer-Policy: origin
Referrer-Policy: origin-when-cross-origin
Referrer-Policy: same-origin
Referrer-Policy: strict-origin
Referrer-Policy: strict-origin-when-cross-origin
Referrer-Policy: unsafe-url
Referer-Policy 字段解释
| 名称 | 描述 |
|---|---|
| no-referrer | referrer 首部会被移除。访问来源信息不随着请求一起发送。不需要检查 |
| no-referrer-when-downgrade(默认值) | 在没有指定任何策略的情况下用户代理的默认行为。在同等安全级别的情况下,引用页面的地址会被发送(HTTPS->HTTPS),但是在降级的情况下不会被发送 (HTTPS->HTTP)。 |
| origin | 在任何情况下,仅发送文件的源作为引用地址。例如 https://example.com/page.html 会将 https://example.com/ 作为引用地址。 |
| origin-when-cross-origin | 对于同源的请求,会发送完整的URL作为引用地址,但是对于非同源请求仅发送文件的源。 |
| same-origin | 同源的请求会发送引用地址,但是对于非同源请求则不发送引用地址信息。 |
| strict-origin | - 在同等安全级别的情况下,发送文件的源作为引用地址(HTTPS->HTTPS),但是在降级的情况下不会发送 (HTTPS->HTTP)。 |
| strict-origin-when-cross-origin | 对于同源的请求,会发送完整的URL作为引用地址;在同等安全级别的情况下,发送文件的源作为引用地址(HTTPS->HTTPS);在降级的情况下不发送此首部 (HTTPS->HTTP) |
| unsafe-url | 无论是同源请求还是非同源请求,都发送完整的 URL(移除参数信息之后)作为引用地址 |
这里一般请求,一般都是验证一下 referrer 建议设置
Referrer-Policy: origin
服务器相关的前端安全问题
这里我们用 Node 来举例,方便理解
本地文件读写存在的安全漏洞
提供静态服务,通过请求的参数 url 来返回给用户、前端想要的资源
- express.static
- koa-static
- resolve-path
保证资源的私密性
const fs = require('fs');
const http = require('http');
const path = require('path');
const resolvePath = require('resolve-path');
http.createServer(function (req, res) {
try {
const rootDir = path.join(__dirname, 'static');
const file = path.join(rootDir, req.url);
fs.readFile(file, function (err, data) {
if (err) {
throw err;
}
res.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"})
res.end(data)
})
} catch (err) {
console.log(err);
res.writeHead(404, {"Content-Type": "text/plain;charset=utf-8"})
res.end('找不到对应资源')
}
}).listen(8080);
console.log('server is Listening 8080')
实现截图服务
(async () => {
const PCR = require('puppeteer-chromium-resolver');
const stats = await PCR();
const browser = await stats.puppeteer
.launch({
headless: true,
args: ['--no-sandbox'],
executablePath: stats.executablePath
}).catch(function (err) {
console.log(err)
})
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
const { url } = ctx.query;
if (!url) {
ctx.body = "Invalid url"
return;
}
ctx.set('Content-Type', 'image/png')
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' })
ctx.body = await page.screenshot({ encoding: 'binary', type: 'png'})
await page.close();
})
app.listen(8083)
console.log(`server listening 8083`)
})();
ReDos 正则表达式攻击
console.time('case-1')
/A(B|C+)+D/.test('ACCCCCCCCD');
console.timeEnd('case-2')
/A(B|C+)+D/.test('ACCCCCCCCDX'); // 匹配不到 花的时间会很长 回溯
比如 ACCCD 可以直接匹配发现成功,耗时就很短
ACCCX,每当一次匹配不成功,就会尝试回溯到上一个字符,看看能不能有其他的组合来匹配到这个字符串
- CCC
- CC + C
- C + CC
- C + CC + C
检查 是否存在Redos 风险网站
时序攻击
匹配数组里面的数据是否相同
function compareArray(realArray, userInputArray) {
for (let i = 0; i < realArray.length; i ++) {
if (realArray[i] !== userInputArray[i]) {
return false;
}
}
return true;
}
下面是 攻击者发起的尝试
inputArray inputArray = [1, 3]
发现两种响应时间几乎一致,则可以认为第一个数字为 1 找到规律
总结
这篇文章到这里就结束了,水平有限难免有纰漏,欢迎纠错。最后希望帮忙点点赞,这对我创作是无比的肯定和动力。希望可以帮到你,祝面试顺利。
文章参考
developer.mozilla.org/zh-CN/docs/…
cheatsheetseries.owasp.org/cheatsheets…
owasp.org/www-project…
juejin.cn/post/706769…
juejin.cn/post/684490…
juejin.cn/post/684490…
developer.mozilla.org/zh-CN/docs/…