web人机验证的探索与实践
目录
- Web页面的安全挑战
- 风控基本流程
- Google人机验证
- 人机识别验证
- 问题1:人机识别验证
- 问题2:确保行为数据上报安全
- 问题3:前端代码加密混淆 - 化解纸老虎的尴尬
- 技术方案设计
- 为什么前端代码一定要加密混淆
- 场景
- 小结
在移动互联网时代,我们为了功能的快速上线、bug快速修复、及时更新,很多业务场景还是更依赖Web页面来实现。而由于 前端Web天生暴露在外,成了“环境透明”,相对于pc客户端和App,web页面在安全性上存在更大的挑战。
Web页面的安全挑战
移动互联网赚钱的三大模式:广告、电商、游戏。 这些都会涉及到营销,而营销很大部分会根据人类喜欢凑热闹而组织线上活动,比如抢券、领红包、抽奖、秒杀、投票、点赞等营销行为,这些行为很容易被"小人"以作弊的方式直接模拟刷接口的方式来提高自己的收益,这样对普通小白用户就不公平;
这种行为也会使得原本积极的品牌营销行为,变成负面消极的品牌营销行为,不仅让活动主办方带来经济等方面的损失。比如说有个活动是用户注册后在某个页面领兑换码抵用购买金额。第一想法一般是我们会限制每个用户只能领取一次。然而对于“撸羊毛党”甚至形成产业链的工作室说,简直是天下掉下的馅饼。
只限制每个用户只领取一次,远远不过的,这个用户到底是人是机器,请求是否真实用户发起,是否存在安全漏洞,是否很容易被“撸羊毛党”恶意利用等等,这些都是活动担心的问题。
风控基本流程
为了提高Web页面的安全性,大公司通常会引入专业的风控服务。引入风控服务后,安全防护的流程大致如图所示。
用户 ->> web 前端 ->> 风控服务(前后端)->> 后端业务服务
-
web前端:用户通过 Web 页面来参与活动,同时 Web 前端也会收集用于人机识别验证的用户交互行为数据。由于不同终端(移动端 H5 页面和 PC 端页面)交互形式不同,收集用户交互行为数据的侧重点也会有所不同。有时需要根据针对业务逻辑收集特殊的用户行为数据。
-
风控服务:一般大公司都会有专业的风控团队来提供风控服务。比如根据地理位置范围、ip地址、业务用户id或其他敏感数据实现频率访问/黑白名单限制简单的重放攻击行为,还可以依托于第三方比如阿里云等风控服务,也可以内部通过海量的全业务场景的用户大数据,使用贝叶斯模型、神经网络等来构建专业度较深的风控服务。风控服务可以为 Web 前端提供通用的独立验证SDK:验证码、滑块验证等区分人机的“图灵验证”,也可以为服务端提供 Web API 接口的验证等。
-
后端业务服务:负责处理活动主要业务逻辑,如抢券、领红包,投票、抽奖等。请求需要经过风控服务异步验证,确保其安全性,然后再进行处理实际业务逻辑。
不论是验证还是限制,最终归根结底的问题如何区别用户是人还是机器,实现人机识别验证。在人机识别验证的专业领域上,可以先看看业界巨头 Google 是怎么做的。
Google人机验证
Google 使用的人机验证服务是著名的 reCAPTCHA(Completely Automated Public Turing Test To Tell Computers and Humans Apart,区分人机的全自动图灵测试系统),也是应用最广的验证码系统。早年的 reCAPTCHA 验证码是这样的:
如今的 reCAPTCHA 已经不再需要人工输入难以识别的字符,它会检测用户的终端环境,追踪用户的鼠标轨迹,只需用户点击“我不是机器人”就能进行人机验证(reCAPTCHA骗用户进行数据标注而进行AI训练的验证另说)。
reCAPTCHA 的验证方式从早先的输入字符到现在的轻点按钮,在用户体验上,有了较大的提升。
而在活动场景中引入人机识别验证,如果只是简单粗暴地增加验证码,或者只是像 reCAPTCHA 那样增加点击“我不是机器人”的验证,用户体验与业务功能匹配度不成比例时,将会降低用户体验,降低用户参加活动的积极性。
Google 的普通 Web 页面的浏览和有强交互的活动 Web 页面虽是不同的业务场景,但对于活动 Web 页面来说,强交互恰好为人机识别验证提供了用户交互行为数据收集的契机。
人机识别验证
理想的方案是在用户无感知的情况下做人机识别验证,这样既确保了安全又对用户体验无损伤。
从实际的业务场景出发再结合 Web 本身的环境,如果想实现理想的方案,会面临以下的几个问题:
- 1、根据用户的使用场景制定人机识别验证算法:前端负责收集并上报用户行为数据,风控服务校验用户行为数据是否符合正常的用户行为逻辑。
- 2、保证前端用户行为数据不被模拟及前端与风控服务之间的数据传输安全。
- 3、以上两点中提到的逻辑算法不被破解。
以上三个问题中,1 完成人机识别验证的功能,2 和 3 都是为了确保识别验证数据与算法不被破解而做的安全措施。
问题1:人机识别验证
真实用户进入web页面参与活动的步骤是用户进入页面后,会有短暂的停留,然后点击按钮触发参与事件。参与实践归根结底都是一个接口请求。如果是非正常用户或非真实用户,会直接跳过以上的场景手动实际动作,直接请求参与事件的接口,又或者可能模拟页面加载再请求参与事件接口,但还是会缺失真人的实际进入页面触发的停留、滚动、点击等人为操作。(当然随着技术与认识的发展,这些行为事件很快都是做得到模拟的,这个后面再说)
那么区别于真实正常用户和非正常用户/机器的,就是那些被机器跳过的人为动作,我们对人为动作进一步整理如下:
- 进入web页面
- 短暂的停留
- 滚动操作(可能)
- 点击参与
这些人为工作有些是必须的操作,有些是可能的操作。前端将这些一连串的人为动作产生的日志数据进行收集上报服务端,可异步上报风控,也可以在参与事件接口同步风控人机验证。
在验证用户行为数据合法后,需要考虑到这些用户行为数据是不是可以被轻易的模拟。另外,参与事件行为在实际业务逻辑中,可能存在一些前后顺序的操作或动作行为,也就是说每个动作都有一个发生时间点,可以把这些有序的行为的时间点加入验证逻辑。
一些用户行为的日志数据也会有一个合理的区间,比如进入页面以document ready为基准,那么加载时间可能大于100毫秒,小于5秒。比如参与事件按钮点击,触发点坐标也有对应的合理区间;这些一系列的合理区间根据实际的终端环境等来进行设置。
除了有用的用户行为数据外,上面提到需要根据终端实际情况进行设置,所以设备环境的数据也可以进行收集,包括终端类型、浏览器BOM信息、是否客户端容器等信息。
为了加大破解难度,还可以收集一些暂时无用的数据,用于混淆阻碍破解,验证时将其忽略。
问题2:确保行为数据上报安全
前端收集的用户行为敏感数据要上报到风控服务端,需要确保数据上报过程的安全。
- 上报接口不能被中途拦截和篡改,使用https是基本要求;服务端生成本次唯一有效的Token,在通信过程中携带该Token。
- 接口携带的敏感数据尽量不是明文的,敏感数据要进行加密,这样攻击者无法通过网络抓包来详细了解敏感数据的内容。
Token设计与用途
Token 是一个简短的字符串,主要为了确保通信的安全。用户进入页面时,服务端下发本次Token,触发参与事件时,带上该Token,服务端验证Token的合法性。也就是说,Token 由服务端生成,传给前端,前端再原封不动的回传给服务端。一旦加入了 Token 的步骤,攻击者就不能直接去请求参与活动的接口了。当然Token都可以通过模拟请求加载页面后去Token或直接请求Token获取,再模拟用户参与事件,这个非"君子"行为了,后续再说。
关于Token的存储和时效性,Token可以后端缓存给定生命周期。当然对于技术大牛来说,不论怎么下发或存储都能轻易取到Token。
当然Token还可以参与到数字签名保证数据不被篡改,可以用于后续的对称加解密,结合前端代码加密混淆提高破解难度。
保护敏感数据
由于前端源码是完全暴露的,所以对于对称与不对称加解密的选择,只要保证算法不会太简单,有加密因子或向量就可以。上报数据时,可以将敏感数据进行加密。
为了提升加密的安全等级,加密时的密钥可以动态生成,前端和服务端约定好动态密钥的生成规则。加密的算法和密钥也要确保不被暴露。(第三个问题里会重点考虑这个问题)
对敏感数据加密后,攻击者在不了解数据内容结构的前提下,短时内很难模拟请求。
问题3:前端代码加密混淆 - 化解纸老虎的尴尬
前面说了虽然验证数据合法性不是透明的,攻击者无法知道,但前端收集数据、敏感数据加密算法是透明的,很容易被有能力的个人或工作室破解模拟数据收集与加密,模拟上报数据。
对的,这就是web经验丰富开发者说的“纸老虎”,从哲学上说,事物是发展的,认识也是发展的,真理是有条件的,在一定条件和时代背景下,通过这个时代一些安全机制的加强,还是可以让“纸老虎”尽量逼真。
大部分脚本语言,包括后端php脚本使用官方zendguard的加密方案,还是可以被反解出来,虽然代码可读性很差,但努力一下还是可以理解代码逻辑。完全暴露的前端代码在压缩后,通过使用浏览器格式化工具和断点工具,仍然具备一定的可读性,一样努力点也依然可以理解其中代码的逻辑。
让“纸老虎”更加逼真,就是把前端当做服务端看待,要求前端源码及时暴露也不能暴露加密或签名算法与秘钥,这就要对前端的代码进行加密混淆。
前端代码加密混淆
前端的 JS 代码压缩工具基本都是对变量、函数名称等进行缩短,压缩对于混淆的作用是比较弱。除了对代码进行压缩,还需要进行专门的混淆。
对代码进行混淆可以降低可读性,混淆工具有条件的话最好自研,开源的工具要慎用。或者基于 Uglify.js 来自定义混淆的规则,混淆程度越高可读性就越低。
代码混淆也需要把握一个度,太复杂的混淆可能会让代码无法运行,也有可能会影响本身的执行效率。同时还需要兼顾混淆后的代码体积,混淆前后的体积不能有太大的差距,合理的混淆程度很重要。
;eval(function(p,a,c,k,e,r){e=function(c){return c.toString(a)};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('5 o(b,c,d){0 e=5(a){k(a.l<m){a=a+\'\\8\'}3 a};0 f;0 g=c||4.v||\'\';g=e(g);0 h=d||4.9||\'\';g=1.2.6.7(g);h=1.2.n.7(h);0 i=1.p.q(b,g,{r:h,s:1.t.u});f=1.2.6.j(i);3 f}',32,32,'var|CryptoJS|enc|return|data|function|Utf8|parse|u0000|aesiv||||||||||stringify|while|length|16|Base64|decrpytion|AES|decrypt|iv|padding|pad|ZeroPadding|aeskey'.split('|'),0,{}));
断点工具的防范会更麻烦些。在使用断点工具时通常都会导致代码延迟执行,而正常的业务逻辑都会立即执行,这是一个可以利用的点,可以考虑在代码执行间隔上来防范断点工具。
通过代码混淆和对代码进行特殊的处理,可以让格式化工具和断点工具变得没有用武之地。
有了代码混淆,反编译的成本会更高,这样“纸老虎”已经变得很逼真了。
技术方案设计
了解整个流程及问题与解决方案后,把相应方案串联起来,设计成一个可实施的技术方案,参考美团风控服务技术方案架构图:
1、基础拦截
基础风控拦截是对访问频率、黑名单等的拦截限制,在web服务器层如Nginx层就能直接实施拦截。如果发现是恶意请求,直接将请求过滤返回 403。这是初步的拦截,用户在请求 Web 页面的时候就开始起作用了。
2、服务端生成Token下发到前端
步骤1可能还没进入到Web页面,进入Web页面后才真正开始风控流程,前端事先获取Token。
3、前端收集数据
前端开始收集数据,数据包含用户交互行为数据、设备环境数据、业务逻辑数据以及无效数据。
4、前端上报数据
Token可以作为Authorization的值添加到 Header 中,数据接口的签名可以有效防止 CSRF 的攻击。
另外要求使用https协议,避免数据被篡改,如果没有采用https,也可以自己实现验签或非对称加解密方式(前提是前端代码保护不泄露)。
5、风控服务数据校验
风控服务端收到请求后,会先验证数据接口签名中的 Token 是否有效。验证完 Token,才会对敏感数据进行解密。数据解密成功,再进一步对用户行为数据数据合法性进行校验。
6、业务逻辑的处理
前面的步骤完成人机校验,在校验合法后开始处理实际的业务逻辑。
为什么前端代码一定要加密混淆
传统的图片验证码
一般我们在pc网站上注册或登录时,经常会被要求填写验证码,有一种是短信验证码,有一种是图片验证码,短信验证码与语音验证码,以目前的技术和环境对人机验证效果会比较明显,而传统的图片验证码基本难以阻挡强大的刷单刷票刷券工作室组织,这里我们先不谈图片验证码是否还有效果,我们只是以图片验证作为范例讲解技术方案流程。
- 前端加载web页面
- 点击参与事件
- 前端请求图片验证码
- 服务端生成验证码字符串,并计算生成图片
- 下发图片给前端
- 前端渲染验证码图片
- 用户输入验证码字符串
- 前端提交参与事件请求
- 后端校验验证码及处理参与业务逻辑
- 下发业务结果到前端
- 结束流程
这个流程中,除了存在图片验证码本身已经不符合时代技术意识外,还存在服务器计算生成图片单点问题,当存在1w个模拟用户短时间内轮询请求生成验证图片时,服务器cpu带动负载上升,服务器出口流量大增,可能导致服务器雪崩。
新的图片验证码
新的技术方案,本质是解决这个单点计算生成图片问题,将计算转移到其他服务器或转移个终端,这里我们选择转移到终端,方案终端计算生成图片,需要事先从服务端拿到验证码字符串,服务端临时存储用于后续提交参与事件进行校验,由于前端透明,这种方案很容易被模拟请求验证码字符串,再模拟请求参与事件,考虑以下方案,可以一定程度上增加被模拟的难度:
- 前端加载web页面
- 点击参与事件
- 前端请求验证码字符串(服务端对字符串加密)
- 前端解密字符串、计算生成验证码图片
- 前端渲染图片
- 用户输入验证码字符串
- 前端提交参与事件请求
- 后端校验验证码及处理参与业务逻辑
- 下发业务结果到前端
- 结束流程
这个流程方案,解决了cpu导致负载问题,但引入了上面说的容易被模拟请求的问题,问题的解决方案归根结底还是前端字符串解密算法的保护,前端代码的加密混淆。
场景
- 投票
- 抽奖营销
- 用户注册
小结
由于前端透明这个特殊性,为提高Web页面的安全性,需要考虑每个环节及各种技术方案,并将这些技术方案组合,环环相扣让其发挥安全防范作用,如果其中一个环节处理不好,整个流程链就会出现漏洞,导致整个验证流程方案被轻易破解。
加密这个事就是防君子,小人难防。没有绝对的安全,运动是绝对,静止是相对的,安全攻防是一项长期的“拉锯升级战”,安全防范措施也需要持续地优化升级。
参考资料
- https://www.google.com/recaptcha/intro/v3.html
- https://segmentfault.com/a/1190000006226236
- https://www.freebuf.com/articles/web/102269.html
本文在美团技术文章的基础上结合自身经验,在文章结构基础上进行改动。
本文收藏来自互联网,用于学习研究,著作权归原作者所有,如有侵权请联系删除
部分引用格式为收藏注解,比如本句就是注解,非作者原文。