Appearance
前言
聊聊前端安全SDK的设计和实战,
需求设计
当我们设计一个前端安全SDK时,我们需要这个SDK能干嘛,畅想一下场景。
- 能够做一些基本的安全工作,比如csrf、xss攻击
- 判断当前宿主环境,判断是否是机器人访问
- 能够对消息进行拦截,做出一些自定义行为,比如全局弹窗要求校验
- 采集宿主信息,生成指纹
还需要满足一下功能
- 对于接入方来说是无感知的,还不能侵扰业务的正常业务。
- 加载速度一定要快,
- 在宿主环境不满足时,快速降级,不能影响正常业务
- 效率要高,尽可能复用
- 防止别人调试
- 防止安全代码被破解
拦截系统和风险系统
拦截系统是能够对风险操作直接拦截,风险系统是在某个危险操作触发时及时上报通知相关人员,触发后续操作
技术准备工作
脚本注入
如何将安全SDK代码注入,首先安全SDK的代码必须运行在所有的脚本代码前面,包括内联代码,最好的方式无感知注入,通过openresty直接在返回的文档中插入代码。
脚本是由一个个插件的构成的,有些插件并不需要立刻执行,比如用户行为采集,浏览器静态指纹(前置运算)、验证码功能等,可以后置异步加载形式注入
不同业务可能配置的功能不同,比如有些业务方可能不需要某些插件的功能(包括同步插件),需要一种在编译时注入插件的功能,减少SDK的代码量。
浏览器指纹
浏览器指纹是设备固有的属性,和当前页面业务无关,在用户未登录的情况下能对用户进行唯一化。目前浏览器技术分为3代 第一代是基于cookie和every cookies的,是有状态的协议 第二代是采集浏览器的固有信息, 第三代是采集用户行为信息和同系统的跨浏览器的识别
目前指纹采集技术进入到2.5代技术,通过采集浏览器固有信息和部分用户行为
- 字体 利用的原理是如果某个某个字体不支持,那么会使用后面的字体。比如设置字体为font-family: 'somefont defaultfont'。如果字体大小等于defaultfont大小,说明somefont不支持,反之就是支持。需要注意的是,最好把字体的判断放置到ifram执行,避免宿主网页的影响
- 字体性能
- cpu cpu内核数
- 颜色深度(能够检测是否在虚拟桌面) window.screen.colorPath
- 屏幕分辨率
- 可用最小内存
- 语言 获取用户的系统所有语言和当前应用的语言 navigator
- navigator.hardwareConcurrency 虚拟线程既4c8t 中的T
- 时区 时区有两种获取方式第一个是window.Intl.DateTimeFormat.timezone, 第二个直接获取当前时间的getTimeZoneOffset
- 是否支持sessionStorage 或者 localStorage
- 是否支持indexDB
- 是否支持openDataBase
- 平台 platform.platform
- 插件列表,现在插件只能获取内置的列表,不能获用户所有的插件
- vendor navigator.vendor
- 某些特定的全局变量, ucweb
- 是否支持cookies,直接本地写一个然后再读
- 是否支持某些css 媒体查询
- 是否hdr屏 媒体查询 dynamic-range
- 支持的数学方法
- canvas指纹
- 检查是否支持touch事件,ontouchstart是否存在,检查createEvent('TouchEvent')、navigator.touchPoints
上面指纹又区分了基础指纹、高级指纹和硬件指纹。基础指纹的重复过高,高级指纹也可能会有部分重复项的,硬件指纹不太容易获取。
上面指纹因为可变会的量过多,怎么样设计权重是一个问题,解决方案是针对不可变的数据进行分组摘要,对于可变的数据进行端摘要, 摘要的算法简单采用md5即可。
xss
xss有三种,反射形、存储形和dom形,前面两者的区别是有没有经过后端存储,dom形是指渲染了某些的不合规的dom属性导致注入。 xss的危害是最大,相当于将整个页面的控制权完全交给了第三方,第三方脚本可以轻易将用户的cookie,界面信息,用户信息传入到第三方中。除了传统的后端的防范,前端也需要规避xss。
作为安全SDK需要具备主动防御功能,因为xss总是脚本执行,所以需要总是将安全SDK放置到所有脚本的前面
- 首先来解决dom形的注入。dom形注入一般是如下的形式。
html
<!-- 理论上应该是这样的 -->
<img src="xxx.png" />
<!-- 实际变成了这样 -->
<img src="xxx.png" onload="alert('xxx')" />
<!-- 注入的内容是 xxx.png" onload="alert('xxx') -->一种常见的办法是开定时器对全局的dom进行扫描。检查是否存在可以事件,但是现在是ajax的时代,这种方式效率极低。 首先对内联的事件进行监督,在document上面对所有的事件在捕获的极端判断e.target上面的事件定义,是否会触发关键字。触发后停止后续的事件,并及时上报。对于已经判断过的的dom添加flag,后续直接跳过。比如我们的内联事件代码总是不会太长。如果某个内联事件的代码过长,黑客又不知道,可能会立马触发扫描。
- 如果是dom形注入的是外部的脚本,上面那种定时扫描就会生效。外部的脚本总是需要创建一个script脚本或者引入一个脚本,引入一个脚本总是需要添加src属性,从这个下手,对createElement进行重写,获取到script元素后,对script元素的src进行拦截,如果发现是外部脚本直接拦截并报警
js
const originFn = Document.prototype.createElement;
Document.prototype.createElement = function(...args) {
const element = originFn.call(this, ...args);
if(element.tagName === 'SCRIPT') {
Object.defineProperty(element, 'src', {
set(url) {
if(checkUrl(url)) {
return url;
}
}
})
}
}上文中还有一个万一别人把apply重写一把,那不就失效了。为此还是需要使用自己的apply,再把apply设置为不可配置即可。
有些人说不让createElement, 那还可以cloneElement,也给干掉即可。
当然别人也能通过document.write 把你所有的代码禁用掉。我们也可以在DomContentLoaded后直接把document.write重写掉,添加监控事件。
- 有些页面是反射性注入,大致是通过构造一个同域iframe来实现,这时候iframe是能正常携带cookie,而且不会被主域名的事件监控,iframe可以拿到contentWindow,直接contentWindow也添加上保护程序即可,唯一麻烦是怎么知道iframe什么时候被创建,监听mutationObject 会触发domnodeinserted事件,监听这个即可 浏览器中还有直接禁掉eval和new Function的
现在的浏览器通过csp字段已经实现了部分上面的功能,能够限制网页内嵌代码等功能
csrf拦截
crsf跨站请求伪造,如同名字主要是通过伪造达到目的,目前防御的方式有两种,第一是总是验证请求来源,对于无来源或者来源不是白名单的直接忽略掉,第二个是双token,除了默认的token外总是添加自定义的toke在请求中(url、head或者body)。
安全SDK需要完成的内容是对于关键请求总是添加双token认证,将token保存在document下或者sessionStorage下,在form请求时总是携带上,对于动态表单总是在action后面添加,如果是ajax请求直接全局拦截
全局请求拦截
对于请求的拦截,跳转性都交给后端进行拦截,前端SDK需要解决的是ajax请求和fetch请求
ajax请求
ajax通过open构建请求信息,通过send构建body信息,通过response 添加响应信息,我们只需要对这3个方法进行重写即可。之前有通过Proxy实现对ajax重写,性能大概比Object.defineProperty 慢4倍左右,直接修改原型是最好的办法
fetch
fetch拦截稍微简单一些,fetch本质是一个函数,只用给函数添加一层,并且重写res.json方法即可。
验证码功能
对于一些高危的请求,在拦截后需要验证码校验当前用户的,对于跳转的页面,直接跳转到相应的页面即可,对于ajax或者fetch,就需要全局拦截后通过iframe的形式来添加验证码,并且根据登记渲染不同验证码,比如手机验证码或者图形验证码等。
验证码校验成功后如何重放请求,建议是不重放请求。让页面直接报错即可。
错误监控
前端中有两种错误需要监控,通过error(不用onError,onError不能监控资源加载失败的问题)和unhandleRejection 抓取异步的错误,抓取的内容
上报功能
数据上报,通过sendBeacon和gif上报,设计了优先级和上报队列的方式,gif上报有字节数限制,一般设置为2047个。
插件系统设计
插件的基本功能是注册,然后在core各个阶段被调用。在什么阶段被调用完全看插件本身做了哪些订阅操作。静态插件还好,如果是动态插件则还需要加载器。动态插件加载时机在core流程的最后, 首先对全部模块进行梳理,有环境检查、工具类()、加载器(加载第三方插件)、日志上传、ajax拦截器、fetch拦截器、xss拦截器、xss预警器、csrf拦截器、验证码模块、指纹模块、反调试模块、错误加载器、行为模块 每个模块都要求是可以配置控制的。
core执行流程如下
- 初始化事件监听器
- 初始化上下文,工具类
- 内置插件注册,包括工具类插件
- 执行静态插件注册
- 初始化加载器
- 加载动态插件并执行
