可能引起前端安全的问题
- 跨站脚本(Cross-Site Scripting,XSS):一种代码注入方式,为了与 CSS 区分被称作 XSS。早期常见于论坛,起因是网站没有对用户的输入进行严格的限制,使得攻击者可以将脚本上传到帖子让其他人浏览到有恶意脚本的页面,其注入方式很简单,包括但不限于 JavaScript/VBScript/CSS/Flash 等
- iframe 的滥用:iframe 中的内容是由第三方来提供的,默认情况下他们不受控制,他们可以在 iframe 中运行 JavaScript, Flash 插件、弹出对话框等,会破坏用户体验
- 跨站点伪造请求(Cross-Site Request Forgeries,CSRF):指攻击者通过设置好的陷阱,强制对已经完成认证的用户进行非预期的个人信息或设定信息等某些状态更新,属于被动攻击
- 恶意第三方库:无论是后端服务器应用还是前端应用开发,绝大多数时都是在借助开发框架和各种类库进行快速开发,一旦第三方库被植入恶意代码很容易引起安全问题
XSS
XSS 分类
根据攻击的来源,XSS 攻击可以分为存储型、反射型和 DOM 型。
存储区:恶意代码存放的位置
插入点:由谁取得恶意代码,并插入到网页上
存储型 XSS
攻击步骤:- 攻击者将恶意代码提交到目标网站的数据库中
- 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等
反射型 XSS
攻击步骤:- 攻击者构造出特殊的 URL,其中包含恶意代码
- 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
常见于通过 URL 传递参数的功能,如网站搜索、跳转等。由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。
反射型和存储型的区别是:存储型的恶意代码存储在数据库里,反射型的恶意代码存在 URL 里。
DOM 型 XSS
攻击步骤:- 攻击者构造出特殊的 URL,其中包含恶意代码
- 用户打开带有恶意代码的 URL
- 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞
XSS 预防
XSS 攻击有两大要素:
- 攻击者提交恶意代码
- 浏览器执行恶意代码
输入过滤
输入过滤在后端完成,因为前端可以绕开校验。输入侧过滤能够在某些情况下解决特定的 XSS 问题,但会引入很大的不确定性和乱码问题。在防范 XSS 攻击时应避免此类防范。但是,对于明确的输入类型,例如数字、URL、电话号码、邮件地址等等内容,可以进行输入过滤。
还可以通过防止浏览器执行恶意代码来防范 XSS,分为:- 防止 HTML 中出现注入
- 防止 JavaScript 执行时,执行恶意代码。
预防存储型和反射型 XSS 攻击
存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应的 HTML 中的,攻击者刻意编写的“数据”被内嵌到“代码”中,被浏览器所执行。
预防这两种漏洞,有两种常见做法:改成纯前端渲染,把代码和数据分割开
过程- 浏览器先加载一个静态的 HTML,此 HTML 中不包含任何跟业务相关的数据
- 然后浏览器执行 HTML 中的 JavaScript
- JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上
在纯前端渲染中,会明确告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute)等。浏览器不会轻易被欺骗,执行预期外的代码。但纯前端需要避免 DOM 型 XSS 漏洞。
在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,仍然需要面对拼接 HTML 的问题。对 HTML 做充分转义
如果拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板中各处插入点进行充分地转义。
常用的模板引擎,如 doT.js、ejs、FreeMarker等,对于 HTML 转义通常只有一个规则,就是把& < > " '
这几个字符转义掉,确实能起到一定的 XSS 防护作用,但并不完善:XSS 安全漏洞 简单转义是否有防护作⽤ HTML 标签⽂字内容 有 HTML 属性值 有 CSS 内联样式 ⽆ 内联 JavaScript ⽆ 内联 JSON ⽆ 跳转链接 ⽆
- 预防 DOM 型 XSS 攻击
DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当做代码执行了。
在使用 .innerHTML
、.outerHTML
、 document.write()
时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent
、.setAttribute()
等。
如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML
、outerHTML
的 XSS 隐患。
DOM 中的内联事件监听器,如 location
, onclick
, onerror
, onload
, onmouseover
等,<a>
标签的 href
属性,JavaScript 的 eval()
, setTimeout()
, setInterval()
等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。如果项目中用到,一定要避免在字符串中拼接不可信数据。
其他 XSS 防范措施
虽然在渲染页面和执行 JavaScript 时,通过谨慎地转义可以防止 XSS 的发生,但完全依靠开发的谨慎仍然是不够的。可以采取一些通用方案,降低 XSS 带来的风险和后果:Content Security Policy
严格的 CSP 在 XSS 的防范中可以起到以下作用:- 禁止加载外域代码,防止复杂的攻击逻辑
- 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域
- 禁止内联脚本执行(规则较严格)
- 禁止未授权的脚本执行(新特性)
- 合理使用上报可以及时发现 XSS,利于尽快修复问题
- 输入内容长度控制
对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。 其他安全措施
- HTTP-only Cookie:禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie
- 验证码:防止脚本冒充用户提交危险操作
过滤 HTML 标签能否防止 XSS?不能。
用户除了上传<script>
,还可以使用图片 url 等方式来上传脚本进行攻击<img src="javascript:alert('xss')">
还可以使用各种方式来回避检查,例如空格,回车,Tab
<img src="java script: alert('xss')">
还可以通过各种编码转换(URL 编码,Unicode 编码,HTML 编码,ESCAPE 等)来绕过检查
<img src="java script: alert('xss')"> <img%20src=%22javascript:alert('xss')%22>
CSRF
Cross-site request forgery(跨站请求伪造):攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
典型流程
- 受害者登录
a.com
,并保留了登录凭证(Cookie) - 攻击者引诱受害者访问了
b.com
b.com
向a.com
发送一个请求:a.com/atc=xx
,浏览器会默认携带a.com
的 Cookiea.com
接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求a.com
以受害者的名义执行了act=xxx
- 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让
a.com
执行了自己定义的操作
攻击类型
GET 类型的 CSRF
如<img src="http://bank.example/withdraw?amount=10000&for=hacker" >
在受害者访问这个含有 img 的页面后,浏览器会自动向
http://bank.example/withdraw?amount=10000&for=hacker
发送一次 HTTP 请求。bank.example 会收到包含受害者登录信息的一次跨域请求。POST 类型的 CSRF
这种类型的 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
链接类型的的 CSRF 并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。通常是在论坛发布的图片中嵌入恶意链接或者以广告形式诱导用户点击。
如<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank"> 重磅消息!! </a>
CSRF 预防
CSRF 通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对 CSRF 的防护能力来提升安全性。
CSRF 特点:
- CSRF 通常发生在第三方域名
- CSRF 攻击者不能获取到 Cookie 等信息,只是使用
针对特点可以专门制定防护策略,如下:
阻止不明外域的访问
- 同源检测
- Samesite Cookie
提交时要求附加本域才能获取的信息
- CSRF Token
- 双重 Cookie 验证
同源检测
禁止外域(或者不受信任的域名)发起请求- 使用 Origin Header 确定来源域名:在部分与 CSRF 有关的请求中,请求的 Header 中会携带 Origin 字段,如果 Origin 存在,那么直接使用 Origin 中的字段来确认来源域名即可
- 使用 Referer Header 确定来源域名:根据 HTTP 协议,在 HTTP 头中的 Referer 字段记录该 HTTP 请求的来源地址
Samesite Cookie 属性
Google 起草了一份草案来改进 HTTP 协议,那就是为 Set-Cookie 响应头新增 Samesite 属性,用来标明这个 Cookie 是 “同站 Cookie”,同站 Cookie 只能作为第一方 Cookie,不能作为第三方 Cookie,Samesite 有两个属性值:- Samesite=Strict 严格模式,表明这个 Cookie 在任何情况下都不可能作为第三方 Cookie
- Samesite=Lax 宽松模式,比 Strict 宽松
CSRF Token
要求所有的用户请求都携带一个 CSRF 攻击者无法获取到 Token。服务器通过校验是否携带正确的 Token,来把正常的请求和攻击的请求区分开,可以防范 CSRF 的攻击:
防护步骤:- 将 CSRF Token 输出到页面中
- 页面提交的请求携带这个 Token
- 服务器验证 Token 是否正确
双重 Cookie 验证
在会话中存储 CSRF Token 比较繁琐,而且不能在通用的拦截上统一处理所有的接口
利用 CSRF 攻击不能获取到用户 Cookie 的特点,可以要求 Ajax 和表单请求携带一个 Cookie 中的值
流程:- 在用户访问网站页面时,向请求域名下注入一个 Cookie,内容为随机字符串(如
csrfcookie=v8g9e4ksfhw
) - 在前端向后端发起请求时,取出 Cookie,并添加到 URL 的参数中(如
POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw
) - 后端接口验证 Cookie 中的字段与 URL参数中的字段是否一致,不一致则拒绝
- 在用户访问网站页面时,向请求域名下注入一个 Cookie,内容为随机字符串(如
网络劫持
网络劫持种类
DNS 劫持
- DNS 强制解析:通过修改运营商的本地 DNS 记录,来引导用户流量到缓存服务器
- 302 跳转的方式:通过监控网络出口的流量,分析判断哪些内容是可以进行劫持处理的,再对劫持的内容发起 302 跳转的回复,引导用户获取内容
- HTTP 劫持
由于 HTTP 明文传输,运营商会修改 HTTP 响应内容(如加广告)
网络劫持应对方案
- DNS 劫持由于涉嫌违法,已被监管,现在很少会有
- HTTP 劫持依然非常盛行,最有效的办法就是 全站 HTTPS,将 HTTP 加密,使得运营商无法获取明文,就无法劫持响应内容
中间人攻击
中间人(Man-in-the-middle attack,MITM 攻击)是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们在通过一个私密的连接与对方直接对话,但事实上整个对话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。
一般过程:
- 客户端发送请求到服务端,请求被中间人截获
- 服务器向客户端发送公钥
- 中间人截获公钥,保留在自己手上。然后自己生成一个伪造的公钥,发送给客户端
- 客户端受到伪造的公钥后,生成加密 hash 值发送给服务端
- 中间人获得加密 hash 值,用自己的私钥解密获得真密钥,同时生成假的加密 hash 值,发送给服务器
- 服务器用私钥解密获得假密钥,然后加密数据传输给客户端
HTTPS
HTTPS 一定安全吗
非全站 HTTPS 并不安全。