前端安全汇总(持续更新)

3,729 阅读16分钟

在移动互联网时代,信息安全越来越重要。前端方面也面临着越来越多的安全挑战,本文收集了网上各位大佬的分析总结,将持续整理总结常见的安全问题及其防御措施。

CSRF

CSRF 概念

CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证(cookies 等),绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

CSRF 攻击流程

  1. 受害者登录 a.com,并保留了登录凭证(Cookie)。
  2. 攻击者引诱受害者访问了 b.com
  3. b.coma.com 发送了一个请求:a.com/act=xx。浏览器会默认携带 a.comCookie
  4. a.com 接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
  5. a.com 以受害者的名义执行了 act=xx
  6. 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让 a.com 执行了自己定义的操作。

CSRF 攻击类型

GET

GET 类型的 CSRF 利用非常简单,只需要一个 HTTP 请求,一般会这样利用:

<img src="http://bank.example/withdraw?amount=10000&for=hacker" />

在受害者访问含有这个 img 的页面后,浏览器会自动向 http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker 发出一次 HTTP 请求。bank.example 就会收到包含受害者登录信息的一次跨域请求。

POST

这种类型的 CSRF 利用起来通常使用的是一个自动提交的表单,如:

<form action="http://bank.example/withdraw" method="POST">
  <input type="hidden" name="account" value="xiaoming" />
  <input type="hidden" name="amount" value="10000" />
  <input type="hidden" name="for" value="hacker" />
</form>
<script>
  document.forms[0].submit();
</script>

访问该页面后,表单会自动提交,相当于模拟用户完成了一次 POST 操作。 POST 类型的攻击通常比 GET 要求更加严格一点,但仍并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许 POST 上面。

链接

链接类型的 CSRF 并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击,例如:

<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
  重磅消息!!
  <a
/></a>

由于之前用户登录了信任的网站 A,并且保存登录状态,只要用户主动访问上面的这个 PHP 页面,则表示攻击成功。

CSRF 特性

  • 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
  • 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
  • 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。
  • 跨站请求可以用各种方式:图片 URL超链接CORSForm 提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。

CSRF 防御措施

CSRF 通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对 CSRF 的防护能力来提升安全性。

同源检测

服务器可通过 request headersoriginreferer 两个字段确定请求的来源域。

如果攻击者设置 Referrer Policy 隐藏了 referer,则建议直接阻止(考虑排除搜索引擎搜索的情况)。

cookie 是不能跨域访问的,为什么还会有 csrf? 浏览器会依据加载的域名附带上对应域名 cookie。如用户在 a 网站登录且生成了授权的 cookies,然后访问 b 网站,b 站故意构造请求 a 站的请求,如删除操作之类的,用不受同源影响的 script,img 或者 iframe 之类的标签加载 a 地址,浏览器会附带上 a 站此登录用户的授权 cookie 信息,这样就构成 crsf,会删除掉当前用户的数据。

Token

CSRF 攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个 CSRF 攻击者无法获取到的 Token。服务器通过校验请求是否携带正确的 Token,来把正常的请求和攻击的请求区分开,也可以防范 CSRF 的攻击。

  1. Token 输出到页面中,对于页面整个 DOM 树中所有的 aform 标签后都加入 Token
  2. 页面提交的请求携带这个 Token
  3. 服务器验证 Token 是否正确

验证码&密码

在关键请求时要求再次输入验证码和密码之类的,打断 csrf 的进程,简单粗暴且有效。

双重 Cookie 验证

利用 CSRF 攻击不能获取到用户 Cookie 的特点,我们可以要求 Ajax 和表单请求携带一个 Cookie 中的值。

  1. 在用户访问网站页面时,向请求域名注入一个 Cookie,内容为随机字符串(例如 csrfcookie=v8g9e4ksfhw)。
  2. 在前端向后端发起请求时,取出 Cookie,并添加到 URL 的参数中(接上例 POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw)。
  3. 后端接口验证 Cookie 中的字段与 URL 参数中的字段是否一致,不一致则拒绝。

Samesite 属性

Chrome 51 开始,浏览器的 Cookie 新增加了一个 SameSite 属性限制第三方 Cookie,用来防止 CSRF 攻击和用户追踪。

SameSite 根据严格程度取值为 Strict, Lax, None

目前兼容性及实用性都不太好(不支持子域),暂时不用。

XSS

XSS 概念

Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 CookieSessionID 等,进而危害数据安全。

XSS 分类

类型 存储区 插入点
存储型 XSS 后端数据库 HTML
反射型 XSS URL HTML
DOM 型 XSS 后端数据库/前端存储/URL 前端 JavaScript

存储型 XSS

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

反射型 XSS

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

DOM 型 XSS

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOMXSS 跟前两种 XSS 的区别:DOMXSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。

XSS 防御

针对攻击者提交恶意代码

输入过滤

对前端而言,输入过滤不可靠。因为一旦攻击者绕过前端过滤,直接构造请求,就可以提交恶意代码了。

输入内容长度控制

对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。

验证码

防止脚本冒充用户提交危险操作。

针对浏览器执行恶意代码

纯前端渲染

在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。

转义 HTML

应当尽量避免转义 HTML。但如果不是纯前端渲染,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分的转义。

预防 DOM 型 XSS 攻击

防范存储型和反射型 XSS 是后端 RD 的责任。而 DOMXSS 攻击不发生在后端。DOMXSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。

在使用 .innerHTML.outerHTMLdocument.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent.setAttribute() 等。

如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTMLXSS 隐患。

DOM 中的内联事件监听器,如 location、onclick、onerror、onload、onmouseover 等,<a> 标签的 href 属性,JavaScripteval()、setTimeout()、setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。

CSP

内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。

通过 HTTP Header 来定义(优先):

"Content-Security-Policy:" 策略集

通过 html meta 标签使用:

<meta http-equiv="content-security-policy" content="策略集" />

更多策略

  • 禁止加载外域代码,防止复杂的攻击逻辑。
  • 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
  • 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
  • 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
  • 合理使用上报可以及时发现 XSS,利于尽快修复问题。
HttpOnly & Secure
  • cookie 中设置了 HttpOnly 属性,禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie
  • cookie 中设置了 Secure 属性,规定 cookie 只能在 https 协议下才能够发送到服务器。防止信息在传递的过程中被监听捕获后信息泄漏。

iframe 嵌套

点击劫持 (ClickJacking) 一般会利用透明 iframe 覆盖原网页诱导用户进行某些操作达成目的。

防止其他页面通过 iframe 引用

//自己网站添加
if (top.location != self.location) {
  top.location.href = 'http://www.baidu.com'; //若被其他网站引用则强制跳转
}

X-FRAME-OPTIONS

或者添加 HTTP 响应头: X-FRAME-OPTIONS

HTTP 响应头信息中的 X-Frame-Options,可以指示浏览器是否应该加载一个 iframe 中的页面。如果服务器响应头信息中没有 X-Frame-Options,则该网站存在 ClickJacking 攻击风险。

防止引用的其他 iframe 篡改自己的页面

添加 sandbox 属性

<iframe src="其他页面.html" frameborder="0" sandbox=""></iframe>

opener

通过以下两种方式打开的页面可以使用 window.opener 来访问源页面的 window 对象,进而进行篡改原页面:

window.open

window.open('http://www.baidu.com');

防御方式:

let newTab = window.open();
newTab.opener = null;
newTab.location = url;

a 标签加 _blank

<a target="_blank" href="http://www.baidu.com"></a>

防御方式:

<a target="_blank" href="http://www.baidu.com" rel="noopener noreferrer"></a>

CDN 劫持

出于性能考虑,前端应用通常会把一些静态资源存放到 CDN(Content Delivery Networks)上面,例如 js 脚本和 style 文件。这么做可以显著提高前端应用的访问速度,但与此同时却也隐含了一个新的安全风险。如果攻击者劫持了 CDN,或者对 CDN 中的资源进行了污染,攻击者可以肆意篡改我们的前端页面,对用户实施攻击。

应对流量劫持,前端能做哪些工作?

SRI

SRI 简介

子资源完整性(SRI)是允许浏览器检查其获得的资源(例如从 CDN 获得的)是否被篡改的一项安全特性。它通过验证获取文件的哈希值是否和你提供的哈希值一样来判断资源是否被篡改。

通过给 link 标签或者 script 标签增加 integrity 属性即可开启 SRI 功能.

<script type="text/javascript" src="/xxxx/aaa.js" integrity="sha256-xxx sha384-yyy" crossorigin="anonymous"></script>

integrity 值分成两个部分,第一部分指定哈希值的生成算法(sha256sha384sha512),第二部分是经过 base64 编码的实际哈希值,两者之间通过一个短横(-)分割。integrity 值可以包含多个由空格分隔的哈希值,只要文件匹配其中任意一个哈希值,就可以通过校验并加载该资源。

crossorigin="anonymous" 的作用是引入跨域脚本,使用 SRI 要保证资源同域或开启跨域。如果不加此属性则表示不开启 CORS 策略。

浏览器处理 SRI 原理

  • 当浏览器在 script 或者 link 标签中遇到 integrity 属性之后,会在执行脚本或者应用样式表之前对比所加载文件的哈希值和期望的哈希值。
  • 当脚本或者样式表的哈希值和期望的不一致时,浏览器必须拒绝执行脚本或者应用样式表,并且必须返回一个网络错误说明获得脚本或样式表失败。

如何使用 SRI

通过使用 webpackhtml-webpack-pluginwebpack-subresource-integrity 可以生成包含 integrity 属性 script 标签。

import SriPlugin from 'webpack-subresource-integrity';

const compiler = webpack({
  output: {
    crossOriginLoading: 'anonymous',
  },
  plugins: [
    new SriPlugin({
      hashFuncNames: ['sha256', 'sha384'],
      enabled: process.env.NODE_ENV === 'production',
    }),
  ],
});

然后通过插件 script-ext-html-webpack-pluginscript 里注入 onerror 事件和 onsuccess 事件。onerror 事件里再次请求一次数据,比较两次数据是否一致来判断是否被 CDN 劫持。

联合 CSP

另外,通过在 CSP 头部添加:

Content-Security-Policy: require-sri-for script;

这条指令规定了所有 JavaScript 都要有 integrity 属性,且通过验证才能被加载。

中间人攻击

攻击原理

用户在访问某个网站的时候,在浏览器里却往往直接输入网站域名。浏览器便向网站发起一次 HTTP 请求,在得到一个重定向响应后,再发起一次 HTTPS 请求并得到最终的响应内容。由于在建立起 HTTPS 连接之前存在一次明文的 HTTP 请求和重定向,使得攻击者可以以中间人的方式劫持这次请求,从而进行后续的攻击,例如窃听数据,篡改请求和响应,跳转到钓鱼网站等。

HSTS

HSTS 的全称是 HTTP Strict-Transport-Security(严格传输安全),它是一个 Web 安全策略机制,通过服务器设置 HTTP Response Header 告诉浏览器只能通过 HTTPS 访问当前资源,而不是 HTTP

HSTS 语法

Strict-Transport-Security: <max-age=>[; includeSubDomains][; preload]
  • max-age 是必选参数,是一个以秒为单位的数值,它代表着 HSTS Header 的过期时间,通常设置为 1 年,即 31536000 秒。
  • includeSubDomains 是可选参数,如果包含它,则意味着当前域名及其子域名均开启 HSTS 保护。
  • preload 是可选参数,只有当你申请将自己的域名加入到浏览器内置列表的时候才需要使用到它

HSTS 更多

  • 只要是在有效期内,浏览器都将直接强制性的发起 HTTPS 请求。
  • HSTS 让浏览器强制拒绝不安全的链接,不给用户选择的机会。
  • 第一次访问网站的时候,依然需要一次明文的 HTTP 请求和重定向才能切换到 HTTPS,以及刷新 HSTS 信息,此时仍然可以进行中间人攻击,对此,浏览器里内置一个列表 Preload List,只要是在这个列表里的域名,无论何时、何种情况,浏览器都只使用 HTTPS 发起连接。

cookie secure

通过 cookie secure 保证你的 session cookie 对于攻击者是不可见的,避免中间人攻击。

其他

HTTPS

HTTPS=HTTP+SSL

确保所有需要的资源都用 HTTPS 引入.

SSL/TLS

传输层安全协议(Transport Layer Security / TLS)及其前身安全套接层(Secure Sockets Layer / SSL)为浏览器和服务器提供了端到端的加密手段,为互联网通信提供了安全可靠性保障。没有 TLS,其他安全手段不堪一击。TLSHTTP 安全的基石。

Cache Control

指定页面的缓存策略。强烈建议手动指定页面缓存策略,否则会由浏览器和代理来控制是否缓存内容。一个不当的缓存策略可能会导致性能问题和安全问题。

Content Type Options

通常浏览器根据响应头的 Content Type 字段分辨资源类型,但假若某些资源 Content Type 是错的或未定义,浏览器会启用 MIME-sniffing 来猜测该资源的类型,解析内容并执行。

X-Content-Type-Options 响应头可以关闭浏览器的资源 MIME-sniffing 功能:

X-Content-Type-Options: nosniff

信息泄露

以下都应删除

  • Server Banner 响应头里加入 Server Banner 来标明自己身份和版本号 如:server:nginx/1.10.0 (Ubuntu)
  • Web 框架信息 很多 Web 框架都会设置响应头来标示自己身份、版本。这些响应头都是非标准的,而且也不影响页面渲染,只是为了框架自身的宣传。如:X-Powered-By, X-Runtime, X-Version, X-AspNet-Version

参考链接