2023-07-03
浏览器
00
请注意,本文编写于 235 天前,最后修改于 129 天前,其中某些信息可能已经过时。

目录

常见面试题
安全沙箱
持久存储
网络访问
用户交互
站点隔离
同源策略
主要内容
受安全策略影响的一些API
安全与便利行的权衡
Cookie
HTTP响应头字段 Set-Cookie
Cookie的特性
JS操作Cookie
Cookie追踪
Session
跨域问题及解决方案
JSONP
CORS
简单请求
复杂请求
注意事项
其他跨域方式
postMessage
Websocket
Node中间件代理
nginx反向代理
window.name + iframe
localtion.hash + iframe
document.domain + iframe
XSS攻击
XSS攻击注入点
XSS攻击类型
存储型XSS攻击
反射型XSS攻击
基于DOM的XSS攻击
XSS攻击的预防
前端对富文本内容进行过滤或转码
服务端对输入脚本进行过滤或转码
浏览器自带防御
充分利用 CSP
内容安全策略
CSP使用方式
CSP指令
CSP报告
例子
CSRF
CSRF 示例
CSRF 如何防御
Cookie与CSRF及XSS的关系
点击劫持
点击劫持 案例
点击劫持的预防
网络安全
HTTP传输窃听
其它安全问题
密码安全
拒绝服务攻击DOS
重放攻击
上传问题
信息泄漏
SQL注入
SQL注入 入门示例
一些神奇的SQL语法
SQL注入演示
SQL注入防御
NoSql注入和防御
参考资料

本文将详细阐述浏览器中的安全策略(同源策略、内容安全策略),web漏洞预防(XSS、CSRF、点击劫持),跨域问题 及 浏览器架构中的安全策略等

常见面试题

  • 浏览器有哪些安全策略?简述一下同源策略
  • Cookie有哪些特性?
  • 什么是XSS攻击?如何预防
  • 什么事CSRF攻击?如何预防
  • 有了解浏览器的内容安全策略吗?

安全沙箱

前面介绍《浏览器架构》时提起过,由于JS引擎及渲染引擎容易出现异常,所有浏览器有多个渲染进程(通常一个tab对应一个渲染进程),且渲染进程是是个沙箱环境。

浏览器本身的漏洞要远比 网页漏洞如XSS危害要大得,堪比“核弹”

  • xss攻击知识将恶意的JavaScript脚本注入到页面中,虽然能窃取一些Cookie相关数据,但是XSS无法对操作系统进行攻击。
  • 对浏览器漏洞的攻击时可以入侵到浏览器进程内部的,可以读取和修改浏览器进程内部的任意内容,还可以穿透浏览器,如安装恶意软件、监听用户键盘输入以及读取用户磁盘上的文件内容。

image.png

思考:

  1. 为什么要通过网络进程进行资源的请求和下载而不是直接使用渲染进程?
    • web资源存在各种可能,没有人能保证浏览器没有漏洞,但只要不执行就不会有问题,
    • 所以安全的事情(资源请求和下载)交给网络进程去做,不安全的事情(资源解析)交给渲染进程去做
  2. 为什么渲染进程只负责生成图片,显示还要叫个浏览器主控进程去做?
    • 原因同上, 显示是安全的事情,解析渲染是不安全的事情
  3. 渲染引擎的主要工作
    • DOM解析,CSS解析、JS执行渲染流水线、网络图片解码等都是有风险的事情
  • 安全沙箱的最小单位是进程,所以现代浏览器是多进程架构
  • 把涉和系统交互的功能都转移到非渲染进程中去实现。

持久存储

  • 首先存储涉及系统操作,肯定不能让渲染进程去做
  • 但渲染进程有一些需求 如 文件上传 Cookie 读写等
  • 解决方案是通过IPC通信交给其他进程去做

网络访问

浏览器在处理URL请求之前,会检查渲染进程是否有权限请求该URL,比如

  • 检查XMLHTTPRequest或Fetch是否是跨站点请求
  • 检查HTTPS站点是否包含了HTTP请求

用户交互

通常情况下,如果你要实现一个UI程序,操作系统会提供一个界面(窗口句柄)给你,该界面允许应用程序与用户交互,允许应用程序在该界面上进行绘制,比如。

同样,渲染进程不能直接访问窗口句柄。所以渲染进程需要完成以下两点大的改变。

  1. 渲染出位图,发送给浏览器进程,浏览器进程将位图复制到屏幕上
  2. 操作系统没有将输入事件直接传递给渲染进程,要经过浏览器主控进程处理
    • 限制渲染进程监控到用户输入事件的能力

站点隔离

所谓的站点隔离是指Chrome将同一站点(包含了相同根域名和相同协议的地址)中相互关联的页面放到同一个渲染进程中执行。

按标签页划分渲染进程的问题

  • 最开始 Chrome 划分渲染进程是以标签页为单位,也就是说整个标签页会被划分给某个渲染进程。但是,按照标签页划分渲染进程存在一些问题,原因就是一个标签页中可能包含了多个 iframe,而这些 iframe 又有可能来自于不同的站点,这就导致了多个不同站点中的内容通过 iframe 同时运行在同一个渲染进程中
  • 目前所有操作系统都面临着两个 A 级漏洞——幽灵(Spectre)和熔毁(Meltdown),这两个漏洞是由处理器架构导致的,很难修补,黑客通过这两个漏洞可以直接入侵到进程的内部,如果入侵的进程没有安全沙箱的保护,那么黑客还可以发起对操作系统的攻击。 所以如果一个银行站点包含了一个恶意 iframe,然后这个恶意的 iframe 利用这两个 A 级漏洞去入侵渲染进程,那么恶意程序就能读取银行站点渲染进程内的所有内容了,这对于用户来说就存在很大的风险了。
  • 因此 Chrome 几年前就开始重构代码,将标签级的渲染进程重构为 iframe 级的渲染进程,然后严格按照同一站点的策略来分配渲染进程,这就是 Chrome 中的站点隔离。
  • 实现了站点隔离,就可以将恶意的 iframe 隔离在恶意进程内部,使得它无法继续访问其他iframe 进程的内容,因此也就无法攻击其他站点了。

同源策略

主要内容

什么是同源策略?

  • 如果两个URL的协议、域名和端口都相同,我们称这两个URL同源。
  • 同源策略限定了同一个源加载的文档或脚本与来自另外一个源的资源进行交互

具体来讲 同源策略主要表现在DOM、Web数据和网络这三个层面

  1. DOM层面。
    • 属于同源的两个窗口(后者通过前者打开),后者可以通过 opener.document 操作前者DOM 比如 .document.body.style.display="none"隐藏前一个页面内容
    • 不如两个页面不同源, 则会报跨域问题
  2. 数据层面
    • 同源策略限制了不同源的站点读取当前站点的Cookie IndexDB LocalStorage、SessionStorage等数据。
    • 补充:Cookie并非严格的遵循同源策略(后面详细讲)
  3. 网络层面 跨域请求

受安全策略影响的一些API

安全与便利行的权衡

  1. 页面可以潜入第三方资源 但是遵守CSP
  2. 跨域资源共享 CORS
  3. 跨文档消息机制 postMessage

nodejs演示同源策略问题

Cookie

背景

  • HTTP是一种无状态的协议,不会保存客户端和服务端的通信状态。
  • 但随着Web的发展,HTTP的无状态也出现问题,如登录一家网站,即时跳转了其他网站,依然需要保存登录状态。于是运用了Cookie就可以管理网络状态了。

定义:

  • 保存在客户端、由浏览器维护、表示应用状态的HTTP头部存放到内存或磁盘中。
  • 服务器通过生成Cookie在响应中通过setCookie头部告知客户端(setCookie可以传递多个值,用“;”分割)
  • 客户端得到Cookie后后续请求都会自动将Cookie头部携带至请求中

Set-Cookie: <cookie-name>=<cookie-value>[;<attribute-name>=<attribute-value>]...

  • <cookie-name><cookie-value>是必须的,表示cookie的名称和值,可以包含多个属性。

常见的Cookie属性包括:

  • Expires 过期时间, 如果未指定,则当前窗口关闭Cookie失效
  • Max-Age 最大存活时间(单位秒)这个是相对时间,从接收响应开始计算
    • 有3种值,分别为正数、负数和 0
    • 0 表示立即删除Cookie
    • 负数 表示是一个临时Cookie 不会被持久化,仅在本浏览器窗口或本窗口打开的子窗口中有效
  • Domain 指定Cookie的域名,子域名可以携带父域名的cookie,反之不行
  • Path 指定Cookie的路径, 子路径可以携带上级路径的cookie,反之不行
  • Secure 如果设置了该属性 仅https写一下才能发送cookie
  • HttpOnly 指定Cookie只能服务端(Set-Cookie)修改 不能通过JS(document.cookieXMLTTPRequestRequest APIs)修改
  • Priority
    • 因为浏览器对同一域名下cookie数目大小有限制(一些浏览器限制的则是一个域名下所有 Cookie 的总大小等),当增加新 Cookie 将会导致 Cookie 超出存储上限的时候,浏览器会剔除旧的 Cookie 以容纳新 Cookie。于是google有了一个提案 给cookie设置Priority属性 可取的值为:LowMedium(默认值) 、High
  • SameSite 指定Cookie的同站策略
    • 值: StrictLaxNone
    • SameSite=Strict 严格模式 完全禁止第三方Cookie,跨站点时任何情况下都不发送Cookie。这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
    • SameSite=Lax 大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
    • SameSite=None Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
    • 参考资料 《Cookie的SameSite属性》

Cookie的特性

  • Cookie的设置domain和path只能往上层设,不能往下设置;
  • 请求时浏览器会自动携带cookie, 携带Cookie规则与设置cookie的规则一致
    • 这无形中增加了流量
    • 将静态资源放到cdn也是一种降低传输流量的方案
  • Cookie的作用域与协议和端口无关
  • 单个域名下的Cookie数量和大小限制
    • 单个域名下Cookie的数量限制在几十个活几百个之间,较早的cookie将被丢失或被新的cookie取代
    • 单个域名下的每个cookie一般限制在4KB,超出的内容将被阶段或拒绝接受。

RFC规范对Cookie的要求

  • 每个Cookie的总长度,浏览器至少要支持到4KB
  • 浏览器对每个域名下的cookie至少要支持50个
  • 浏览器总共至少要支持3000个Cookie

优缺点

  • 单个Cookie大小不要超过4KB
  • 每个域名不要超过50个Cookie
  • 虽然解决了http无状态问题,但也引入了问题

JS操作Cookie

ts
function setCookie ({ name = throwError('name'), value = throwError('value'), // expires = 24 * 60 * 60 * 1000, MaxAge = 6, // 秒 path = '/', domain = location.host } = throwError('参数')) { // const exp = new Date(); // exp.setTime(exp.getTime() + expires); // 写入Cookie, toGMTString将时间转换成字符串 document.cookie = `${name}=${encodeURIComponent(value)};max-age=${MaxAge};path=${path};domain=${domain}`; } /** * cookie中取值 * */ function getCookie (name = throwError('参数')) { const reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); // 匹配字段 const arr = document.cookie.match(reg) if (arr) { return decodeURIComponent(arr[2]); } else { return null; } }; /** * 清除指定cookie值 * 要删除一个cookie,domain和路径必须完全相同 * */ function delCookie ({ name = throwError('参数'), path = '/', domain = location.host }) { const cval = getCookie(name); if (cval && cval != null) { document.cookie = `${name}=${cval};max-age=0;path=${path};domain=${domain}` } }; const throwError = (name) => { throw new Error(`${name}必填!`) }

Cookie追踪

使用第三方cookie 跟踪用户的原理

你曾经在A网站浏览信息时,当访问B网站信息时发现它也知道你曾经购买了什么

  • A网站嵌入了B网站的资源,B网站响应头有setCookie
  • 浏览器允许对于不安全域下的资源(如广告图片)响应的set-cookie保存,并在后续访问改域时自动使用Cookie

Session

直接使用Cookie,等于明文存储用户信息,为了避免用户信息被别人看到,服务端引入了Session机制。

Session存储在服务端:通过cookie存储一个session_id,然后具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该session_id携带上来,服务器根据session_id在session库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做server side session。

跨域问题及解决方案

跨域问题是浏览器出于安全问题而设计的同源策略限定的

提示

  1. 如果你在开发中遇到,抓包正常,但页面不正常,查看代码逻辑也没有什么问题, 很可能是接口跨域问题
  2. 所有的跨域问题的解决都需要第三方的配合!

JSONP

利用 <script> 标签没有默认没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做配合才可以。

举例:

js
// window.cb function localHandler(res) { // 这里res即是服务端返回的数据 // 格式如 {name: 'zs', age: 12} } // 注意这里 请求的接口是与页面非同源, a.json返回的其实是个js文件 jsonP('http://localhost:8081/a.json', localHandler); function jsonP(url, cb) { const script = document.createElement('script'); script.setAttribute('src', `${url}?jsonpCallback=${cb.name}`); document.body.appendChild(script); } /** * JSONP需要服务端配合 * 1. 响应头类型 content-type: "text/javascript" * 2. 模拟js函数调用 如 localHandler({}) */

优缺点:

  • JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。
  • 缺点是仅支持get方法具有局限性, 不安全可能会遭受XSS攻击。

CORS

Cross-Origin Resource Sharing 跨域资源共享, 这个也是比较推荐的做法。

Q:什么是CORS?
1)访问站点A的请求,浏览器应告知该请求来自站点B
2)站点A的响应中,应明确哪些跨域请求是被允许的(站点B是被允许的)

简单请求

如果A站点请求B站点的资源,A站点必须在HTTP响应中显示的告知浏览器,允许访问B站点的资源

简单请求

简单请求的必要条件

  • GET/HEAD/POST方法之一
  • 仅能使用CORS安全的头部:Accept Accept-language Content-Language Content-Type
  • Content-Type的值只能是:'text/plain' 'multipart/form-data' 'application/x-www-form-urlencoded' 三者之一 简单请求的执行过程
  1. 请求中携带Origin头部告知来自哪个域
  2. 响应中携带Access-Control-Allow-Origin头部表示允许那些域
  3. 浏览器放行

复杂请求

简单请求以外的其他请求
访问资源前,需要先发起preflight预检请求(方法为OPTIONS)访问和中请求是被允许的

具体执行过程如下

image.png

请求头部

  • Origin: 一个页面的资源可能来自多个域名,在AJAX等请求中表明来自某个域名下的脚本,以通过服务器的安全校验
  • Access-Control-Request-Method 在preflight请求中告知服务器接下来的请求会使用哪些方法
  • Access-Control-Request-Headers 在preflight请求中告知服务器接下来的请求会携带哪些头部

响应头部

  • Access-Control-Allow-Methods
    • 告知客户端请求允许的方法
  • Access-Control-Allow-Headers
    • 告知客户端请求允许携带的头部
  • Access-Control-Max-age
    • 响应的信息可以缓存多久
  • Access-Control-Expose-Headers
    • 哪些响应头可以供客户端使用,默认情况下只有Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma可控使用,其他的JS脚本无法读取
  • Access-Controll-Allow-Origin
    • 允许那些域访问该资源,*表示允许所有域。为避免缓存错乱,响应中需要携带Vary
  • Access-Control-Allow-Credentials
    • 是否可以将Credentials暴露给客户端使用, Credentials包含cookie、authorization类头部、TLS整数等

注意事项

无论简单请求还是复杂请求都需要服务端配合

  • 简单请求, 服务端要配合设置响应头 Access-Control-Allow-Origin
  • 复杂请求 还要设置Access-Control-Allow-Headers 和 Access-Control-Allow-Methods

Access-Control-Allow-Origin使用注意

  1. 根据CORS的实施标准,当Access-Control-Allow-Origin是被动态产生的话,就要用Vary: Origin去指定。
  2. 设置了Access-Control-Allow-Origin: *(允许所有的跨域访问)后,就不能在设置Access-Control-Allow-Credentials: true 也就是cookie会丢失
  3. 浏览器不支持允许多个域名的跨域请求 如
    • Access-Control-Allow-Origin: https://example1.com https://example2.com
    • Access-Control-Allow-Origin: *.example1.com

其他跨域方式

postMessage

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递
  • 上面三个场景的跨域数据传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

otherWindow.postMessage(message, targetOrigin, [transfer]);

  • message: 将要发送到其他 window的数据。
  • targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,目标窗口的协议、主机地址或端口与targetOrigin完全匹配,消息才会被发送。
  • transfer(可选) 是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

Websocket

Websocket它实现了浏览器与服务器的全双工通信,它也可以作为跨域问题的一种解决方案, 需要服务器作为消息的中转

Node中间件代理

跨域问题是浏览器的安全策略, 服务端并没有这个问题,我们可以新建一个符合同源策略的代理服务器,作为处理消息的中转。

image.png

nginx反向代理

类似Node中间件代理,需要待见一个符合同源策略的中转nginx服务器, 用于转发请求和响应

nginx配置示例如下

nginx
server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; add_header Access-Control-Allow-Credentials true; } }

window.name + iframe

  • 这是一种比较hack(奇葩)的玩法,利用iframe切换后window.name内容不变,将外域数据传递到本域。

  • window.name可以支持非常长的name值(2MB)

示例代码如下

html
<!-- a,b两个页面同源 localhost:3000 c页面为跨域页面 localhost:4000 --> <!-- a.html --> <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe> <script> let first = true function load() { if(first){ // 第1次onload(跨域页)成功后,切换到同域代理页面 let iframe = document.getElementById('iframe'); iframe.src = 'http://localhost:3000/b.html'; first = false; } else { // 第2次onload(同域b.html页)成功后,读取同域window.name中数据 console.log(iframe.contentWindow.name); } } <script> <!-- b.html为中间代理页,与a.html同域,内容为空。--> <!-- c.html http://localhost:4000/c.html --> <script> window.name = '要传输的数据' </script>

总结: 它却是一种跨域问题的解决方案,同也需要第三方资源配合,但从性能角度考虑比较鸡肋

localtion.hash + iframe

与 window.name + iframe 方式类似

  • 这里a 内嵌跨域页面c, c再内嵌b页面, 通过location.hash通信
  • 因为a b同源 所以b可以修改a页面的iframe中的hash, a能监听hash变更
  • c可以接受a的hash, c通过设置iframe b的方式通知a

document.domain + iframe

该方式与 window.name + iframe 方式类似,优于后者, 但缺点是只能用于二级域名相同的情况

  • a b有两个页面非同源, a通过iframe加载b,两个页面都设置 document.domain = '相同的一级域名如warmplace.cn'

XSS攻击

XSS 全称是 Cross Site Scripting,为了与“CSS”区分开来,故简称 XSS ,翻译过来就是“跨站脚本”。XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。

image.png

  • XSS 攻击的本质是 数据当成了程序运行
  • 当页面被注入了恶意脚本后,黑客能做很多他想做的事情, 比如窃取Cookie监听用户行为生成浮窗广告等。

XSS攻击注入点

  1. HTML节点 image.png
  2. HTML属性 image.png
  3. JS代码 image.png
  4. 富文本 (富文本得保留HTML,html中有xss攻击的风险)

XSS攻击类型

  1. 存储型XSS攻击
  2. 反射型XSS攻击
  3. 基于DOM的XSS攻击

存储型XSS攻击

常见于留言板、评论等功能中

image.png

  1. 首先黑客利用漏洞将JS代码提交到网站的数据库中
  2. 然后用户想网站请求包含了恶意JavaScript脚本的页面
  3. 当用户浏览该页面的时候,恶意脚本就会将用户的Cookie信息等数据上传到服务器

反射型XSS攻击

与存储型XSS攻击类似, 但是Web服务器不会存储反射性XSS攻击的恶意脚本。

如下nodejs案例:

js
// xss.js const http = require('http'); const createHtml = function (q) { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> ${q} </body> </html> ` } const server = http.createServer((req, res) => { const search = req.url.split('#')[0].split('?')[1] || '' const querys = search.split('&').reduce((sum, it) => { const [k, v=''] = it.split('='); sum[k] = decodeURIComponent(v); return sum; }, {}); console.log(req.url); res.writeHead(200, {'Content-Type': 'text/html'}); res.end(createHtml(querys.search)) }) server.listen(3000, () => { console.log('Server is running on http://localhost:3000') })

image.png

基于DOM的XSS攻击

基于 DOM 的 XSS 攻击是不牵涉到页面 Web 服务器的。具体来讲,黑客通过各种手段将恶意脚本注入用户的页面中,比如通过网络劫持在页面传输过程中修改 HTML 页面的内容,这种劫持类型很多,有通过 WiFi 路由器劫持的,有通过本地恶意软件来劫持的,它们的共同点是在 Web 资源传输过程或者在用户使用页面的过程中修改 Web 页面的数据。

XSS攻击的预防

  • 存储型XSS攻击和反射型XSS攻击都是经过Web服务器来处理的,因此可以认为这两种漏洞时服务端的安全漏洞。
  • 而基于DOM的XSS攻击全部都是在浏览器完成的,因此基于DOM的XSS攻击属于前端的安全漏洞
  • 共同点: 往浏览器中注入恶意脚本

前端对富文本内容进行过滤或转码

XSS常见于富文本编辑器中,前端需要对富文本中的内容进行转义处理。 转义有多种方式

  • loadsh _.escape
  • 使用 cheeriojs 解析富文本
  • 第三方库 js-xss
  • 自定义转义规则

cheeriojs转义示例代码

js
function xssFilter (html) { if(!html) return '' const cheerio = require('cheerio') var $ = cheerio.load(html) const whiteList = { 'img': ['src'], // 只允许src属性 'font': ['color', 'size'], 'a': ['href'] } $('*').each((index, elem) => { if(!whiteList[elem.name]) { $(elem).remove(); } for(let attr in elem.attribs) { if(whiteList[elem.name].indexOf(attr) === -1) { $(elem).attr(attr, null) } } }) return $.html() }

自定义规则转义示例代码

js
const htmlEscapes = { '&': '&amp', '<': '&lt', '>': '&gt', '"': '&quot', "'": '&#39' } const reUnescapedHtml = /[&<>"']/g const reHasUnescapedHtml = RegExp(reUnescapedHtml.source) function escape(string) { return (string && reHasUnescapedHtml.test(string)) ? string.replace(reUnescapedHtml, (chr) => htmlEscapes[chr]) : string }

服务端对输入脚本进行过滤或转码

  • 比如 <script>alert('你被 xss 攻击了')</script>
  • 可以直接把整段内容过滤掉
  • 也可以 对 '<' '>' 进行转义 &lt;script&gt;alert(&#39; 你被 xss 攻击了 &#39;)&lt;/script&gt;

浏览器自带防御

  • Cookie的httpOnly属性, 防止黑客盗用cookie信息
  • X-XSS-Protection: 1 默认值是1,它只防御 参数出现在html内容或属性
    • 我试了在chrome没有看到效果,但在safari看到控制台看到报错
    • image.png
  • X-Content-Type-Options: nosniff 早期的IE浏览器会根据响应内容推测资源格式,有可能将数据当成程序运行,这个就是不让IE胡乱推测的

充分利用 CSP

CSP 有如下几个功能

  • 限制加载其他域下的资源文件,这样即使黑客插入了一个 JavaScript 文件,这个JavaScript 文件也是无法被加载的;

  • 禁止向第三方域提交数据,这样用户数据也不会外泄;

  • 禁止执行内联脚本和未授权的脚本;

  • 还提供了上报机制,这样可以帮助我们尽快发现有哪些 XSS 攻击,以便尽快修复问题。

内容安全策略

  • 它是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括XSS和数据注入等。
  • 主要目标: 减少和报告XSS攻击
  • 方案: 指定有有效域
  • CSP 被设计成完全向后兼容
    • 不支持CSP的浏览器会忽略该特性

CSP使用方式

以禁止加载其他域下的资源为例

  1. 通过meta标签配置
    • <meta http-equiv="Content-Security-Policy" content="default-src 'self';">
  2. 设置HTTP响应头
    • nodejs res.writeHead('Content-Security-Policy', 'default-src \'self\'')

image.png

也可以不执行限制选项, 只记录违反限定的行为

html
<meta http-equiv="content-security-policy" content="策略"> <meta http-equiv="content-security-policy-report-only" content="策略">

同样支持meta设置和HTTP响应头两种方式

CSP指令

  • 常见的CSP指令 image.png

image.png

  • 其他CSP指令 image.png

CSP报告

假设example.org网站的CSP策略如下

http
Content-Security-Policy: default-src 'none'; style-src cdn.example.com; report-uri /_/csp-reports

则会往http://example.com/_/csp-reports上报POST请求, 上报内容如下

json
{ "csp-report": { "blocked-uri": "http://example.com/css/style.css", "disposition": "report", "document-uri": "http://example.com/signup.html", "effective-directive": "style-src-elem", "original-policy": "default-src 'none'; style-src cdn.example.com; report-to /_/csp-reports", "referrer": "", "status-code": 200, "violated-directive": "style-src-elem" } }
  • 违规报告的语法 可以参考 MDN

例子

http
// 限制所有的外部资源,只能从当前域名加载 Content-Security-Policy: default-src 'self' // 限制所有的外部资源,只能从同源域名或 信任的子域名加载 Content-Security-Policy: default-src 'self' *.trusted.com // 错误写法,第二个指令将会被忽略 Content-Security-Policy: script-src https://host1.com; script-src https://host2.com // 多个指令用;分割, 细分指令优先, 'none' 表示限制所有来源 Content-Security-Policy: default-src https://host1.com https://host2.com; frame-src 'none'; object-src 'none' // 发送报告 POST请求 Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

参考资料:

CSRF

Cross-Site request forgery) 跨站请求伪造

image.png

经过上述6个步骤后,攻击者(WebB)就拿到了用户C的cookie,从而访问WebB时冒充用户C的身份,进行一些恶意操作,如下单转账等。

必要条件

从上图可以看出,要完成一次CSRF攻击,受害者必须满足两个必要的条件

  1. 登录受信任网站A,并在本地生成Cookie。(如果用户没有登录网站A,那么网站B在诱导的时候,请求网站A的api接口时,会提示你登录)
  2. 在不登出A的情况下,访问危险网站B(其实是利用了网站A的漏洞)。

CSRF 示例

image

html
<!doctype html> <html> <head> <meta charset="utf-8"/> <title>csrf demo</title> </head> <body> hello,欢迎来到钓鱼网站 http://127.0.0.1:5500 。 <script> // 这里是恶意脚本 document.write(` <form name="commentForm" target="csrf" method="post" action="http://localhost/post/addComment"> <input name="postId" type="hidden" value="1"> <textarea name="content">来自CSRF!</textarea> </form>` ); /** * 这里加了iframe, form表单请求时就不会跳转 * 当然你也可以手写ajax或fetch请求 */ var iframe = document.createElement('iframe'); iframe.name = 'csrf'; iframe.style.display = 'none'; document.body.appendChild(iframe); setTimeout(function(){ // 127.0.0.1:5500 网站是无法拿到 localhost网站的cookie (站点隔离机制) // 但是可以往 localhost 网站发出请求,会携带localhost的token // 利用该机制就可以冒充用户做一些事情 document.querySelector('[name=commentForm]').submit(); },1000); </script> </body> </html>

示例代码

上例演示了攻击者冒用你的身份发表了一次评论

但如果localhost网站 提交请求时get请求,那情况会更坏,攻击者冒用你的身份发表的评论内容是富文本,里面有链接假设链接为 <a href='http://诈骗网站'>点击这里有钱哦!</a> 这个链接还做了一些事情如别人一点又发表一次评论,这时候就会变成一个蠕虫病毒!

当然这种攻击方式还需要用户点击,有没有不需要用户点击的方式呢?有! 假设富文本的内容包含 <img src="http://localhost/ajax/addComment?postId=1&content=浏览就中招,自动传播"> 这种情况更更恐怖!!

CSRF 盗用用户身份, 除了发帖外, 还可能盗用用户资金(转账、消费), 最终会损外网站声誉。

CSRF 如何防御

  • 设置cookie 的samesite属性,禁用第三方网站请求时携带cookie
  • 验证referer
    • cookie的samesite属性还是略有兼容性问题, 服务端验证referer更安全一些
    • 有些浏览器不带referer,如IE10及以下,还有可能ajax请求是服务端发起的,referer也不是一种完美的方案
  • 验证码
    • 分析CSRF攻击,它有个特点,不访问A网站前端,那么我们就可以在前端页面加入验证信息
    • nodejs有个开源的验证码库 ccap
    • 验证码不能杜绝CSRF攻击,但可以很大程度预防
    • 当然正规企业网站大都是接入极验
  • token
    • 与验证码方式类似 利用 不访问A网站前端 这一特性
    • 在访问该页面表单时,后端设置一个cookie值为csrfTokenValue
    • 前端在表单中隐藏一个input,<input name="csrftoken" value="csrfTokenValue" type="hidden">
    • 提交表单,后端校验,对比cookie中csrftoken与表单中的是否一致

Cookie与CSRF及XSS的关系

用户凭证如何设计?

  1. cookie里面直接存放 用户ID 高危
  2. cookie里存放sessionId服务端根据sessionId从seession中取出用户信息 服务端重启session会丢失,这就需要 session持久化

Cookies和XSS的关系

  • XSS可能偷取Cookies
  • 支持http-only属性的浏览器Coookie不会被窃取

Cookies和CSRF的关系

  • CSRF利用了用户Cookies
  • 攻击站点无法读写Cookies
  • 最好能阻,止第三方使用Cookies samesite

Cookies安全策略

  • 签名防篡改
  • 私有变换(加密)
  • httpOnly (防止XSS)
  • secure (防止网络劫持)
  • samesite (防止CSRF)

点击劫持

  • 操作是用户完成的,但不是用户的意愿
  • 比如 你打开A网站(昨日头条localhost),但看到的内容是

点击劫持 案例

image.png

  • 出于雄性动物本能的好奇心,你点击了视频。
  • 哎呦,发现没应,重复点击也没有,被骗了,哎,不管了
  • 。。。。。。
  • 但是攻击者攻击者已经冒用你的身份干了一些坏事,,,
  • 我们打开 开发者工具

image.png

  • 发现网站放了一张背景图,内嵌一个iframe,ifame opacity:0 视觉隐藏, 点击视屏按钮的时候你已经发表了一个评论 “我是SB”。

示例代码

html
<!doctype html> <html> <head> <meta charset="utf-8"/> <title>csrf demo</title> </head> <body style="background:url(clickhijack.png) no-repeat"> <iframe style="opacity:.1" src="http://localhost:1521/post/1" width="800" height="600"></iframe> </body> </html>

点击劫持的特点

  • 用户亲手操作
  • 用户不知情

危害

  • 盗取用户资金(转账、消费)
  • 获取用户敏感信息

点击劫持的预防

  • JavaScript中禁止目标网站被其他网站内嵌
js
// 在 iframe 中 top.location 和 window.location不相等 if(top.location !== window.location){ top.location = window.location } // 这种方式有缺陷,攻击者通过设置iframe属性 sandbox="allow-forms" 禁用脚本
  • HTTTP响应头 X-FRAME-OPTIONS 禁止内嵌
    • nodejs res.writeHead('X-Frame-Options', 'DENY')
    • koa ctx.set('X-Frame-Options', 'DENY')
    • 效果如下图
    • image.png
    • 该方案兼容性非常好,支持到IE8
  • 验证码

网络安全

HTTP传输窃听

由于HTTP采用明文传输,在网络传输环境其内容很容易被撰改,进而发生

  • 插入广告
  • 重定向网站
  • 无法防御的XSS和CSRF攻击等

哪些环节会发生内容撰改?

  • 运行商劫持
  • 局域网劫持
  • 公共wifi

解决方案 HTTPS(TLS/SSL加密), 具体将在我的网络协议篇章讲解

其它安全问题

密码安全

  • 密码的作用 证明你是你
  • 密码的存储
    • 严谨明文存储 (防泄漏)
    • 单向变化(防泄漏) 哈希算法
    • 变换复杂度要求(防猜解)
    • 密码复杂度要求 (防猜解)
    • 加盐(防猜解)
  • 密码的传输
    • https传输
    • 频率限制
    • 前端加密意义有限 (js-md5)

密码的替代方案

  • 生物特征密码的问题
    • 传统密码不容易记忆
    • 指纹 (唇纹)
    • 声纹
    • 虹膜
    • 人脸

密码-泄露渠道

  • 数据库被偷
  • 服务器被入侵
  • 通讯被窃听
  • 内部人员泄露数据
  • 其他网站(撞库)

密码泄露案例(2016)

image.png

hash算法特征

  • 明文密文 一一对应 (碰撞概率可忽略)
  • 雪崩效应 明文一点点变动,密文的每一位都可能变动
  • 密文-明文无法反推
  • 密文固定长度
  • 常见的hash算法 md5 sha1 sha256

密码-单向变换彩虹表 (md5在线解密) 如何对抗彩虹表? 用复杂密码、增加运算、多次加密

密码-变换次数越多越安全

  • 加密成本几乎不变(生成密码时速度慢一些)
  • 彩虹表失效(数量太大,无法建立通用性)
  • 解密成本增大N倍

生物特征密码隐患 下图是美国国防部长的一次发言,媒体拍摄到,提取了美国国防部长的指纹

image.png

密码-生物特征密码

  • 私密性-容易泄露
  • 安全性-碰撞
  • 唯一性-终身唯一无法修改

拒绝服务攻击DOS

定义:

  • 模拟正常用户
  • 大量占用服务资源
  • 无法服务正常用户

大规模分布式拒绝服务攻击DDOS

  • 流量可达几十到上百G
  • 分布式(肉鸡、代理)
  • 极难防御

DOS攻击方式

  1. TCP半连接
  2. HTTP连接
  3. DNS

DOS攻击案例(2009)

  1. 游戏私服互相DDOS
  2. 换目标,攻击DNS服务器
  3. DNS服务器机器下线
  4. 数十万网站DNS解析瘫痪
  5. 暴风影音后台疯狂请求解析
  6. 各地local DNS瘫痪无法上网

DOS攻击防御

  • 防火墙(过滤一些流量)
  • 交换机、路由器(防御规模更小的一些攻击)
  • 流量清洗
  • 高防IP (原理也是流量清洗,此外云服务厂商提供大流量带宽)

DOS攻击预防

  • 避免重逻辑业务
  • 快速失败快速返回
  • 防雪崩机制
  • 有损服务(一个服务挂了不影响其他服务)
  • CDN (静态资源,减轻服务器压力)

重放攻击

  • 用户被多次消费
  • 用户登录态被盗取
  • 多次抽奖

如何防御

  • 加密(HTTPS)
  • 时间戳
  • token(session)
  • nonce(一次性的数字,服务端记录是否用过)
  • 签名(nonce和时间戳可能被篡改,签名就是防止篡改)

上传问题

  1. 网站有 上传文件 的需求 (有不听话的用户上传了一个php文件)
  2. 一般上传完了,需要再次访问上传的文件
  3. 这时候上传的文件(php)被当成程序解析

上传问题防御

  1. 限制上传后缀
  2. 文件类型检查
  3. 文件内容检查
  4. 程序输出
  5. 权限控制-可写与可执行互斥

部署应用时一般要使用权限低的满足网站需要的用户

信息泄漏

信息泄露的途径

  • 错误信息失控.
  • SQL注入,
  • 水平权限控制不当
  • XSS/ CSRF

SQL注入

SQL注入 入门示例

比如登录时用到的sql select * from table where id = ${id}; 假设用户不听话,传了 1 or 1 = 1 这时sql变成 select * from table id = 1 or 1 = 1; 这句话的含义是什么?举例场景例子 红色是用户输入的 image.png

手动执行一下

image.png

通过错误在页面上回显,这个不听话的用户就盗取了你网站的所有用户信息

一些神奇的SQL语法

sql
select * from table where id="10" and 1=0; select * from table where id="10" or 1=1; select * from table where id="10" and mid(version(),1,1)=5; select 1,2,3 from table; select id,1 ,2,3 from table; select * from table union select 1,2,3 from table2; select * from table where mid(username,1,1)="t";

SQL注入演示

在昨日头条点开一篇文章

image.png

推测 10 就是文章 ID ,我们探测一下sql能不能注入

控制台输入

js
location.href = `http://localhost/post/10 and 1=1` // 或 location.href = `http://localhost/post/10 and 1=0`

发现页面正常显示

加上引号再探测一下

js
location.href = `http://localhost/post/10" and '1'='1' and ''="` // 页面正常 location.href = `http://localhost/post/10" and '1'='0' and ''="` // 页面不正常

这时候一般就是有sql可以注入的问题

顺藤摸瓜,我们来推一下mysql版本

js
location.href = `http://localhost/post/10" and mid(version(),1,1)=5 and ''="`

location.href = http://localhost/post/10" and mid(version(),1,1)=5 and ''="`` 看到页面正常,所以得出数据库版本第一位是5同理可以推出第二位 mysql版本是5.7

攻击者就可以往上查询这个版本的数据库有什么漏洞

再通过union语句推测表里面有多少字段及每个字段名称 略

再看下面的脚本

js
location.href = `http://localhost/post/100" union select 1,username,2,password,5,6 from user where ""="`

image.png

此时用户名密码就暴露出来了!

SQL注入的危害

  • 猜解密码
  • 获取数据
  • 删库删表
  • 拖库

SQL注入防御

  • 关闭错误

image.png

  • 检查数据类型

  • 对数据进行转义

image.png

参数化查询

  • 使用mysql2
  • cnpm i mysql2 -S

image.png

  • 使用ORM(对象关系映射) nodejs sequelize

NoSql注入和防御

有一个博客系统用mongoole数据库

image.png

通过开发者工具找到登录请求

image.png

通过修改curl发起请求

image.png

如何防御?

  • 检查数据类型
  • 类型转换
  • 写完整条件

参考资料

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!