之前介绍了一些前后端结合的中间人攻击方案。由于 Web 程序的特殊性,前端脚本的参与能大幅弥补后端的不足,从而达到传统难以实现的效果。
攻防本为一体,既然能用于攻击,类似的思路同样也可用于防御。如果将前端技术结合到传统的 WAF 中,又能有如何的改进?
简单易用,是 Web 服务最大的优势。然而,这也是个致命的弱点。
这种格式简单、标准一致的特征,使得攻击者能利用现有的安全工具,进行大规模、通用化的探测和入侵。甚至无需了解其中的原理。
试想一下,如果某个网站使用私有的二进制协议,那么即使存在漏洞,也得先考虑通信问题。若是寄托于现成的安全工具,那就更举步维艰了。然而现实中是不存在的。通用性和低成本,始终是首要因素。
要模仿这种简单的协议易如反掌。于是,各种需要重复劳动的地方,都能见到假人的身影。对于需要反复测试的安全领域,更是必不可缺。
传统 WAF 大多关注于信息监控,记录拦截各种异常的输入输出。对于用户的真假鉴别,并非是其重点。
然而现实中,大多异常的请求,都不是正常用户发起的。有谁会那么闲,把各种帐号一次又一次输入测试撞库?或者反反复复的在浏览器里尝试内网探测?没有工具的协助,安全检测将是无比的折磨。
遗憾的是,WAF 很难从表面上识别用户的真假,只能对其一视同仁。通过之后更详细的规则进行综合分析,才能做出判定。因此,这样的决策似乎有些『有理无据』。
有理。例如正常用户每秒只有几个请求,但攻击者开了漏洞扫描工具,短时间内产生了上百请求,这显然不符合常理。
无据。虽然判定一个用户并不难,但要拿出确凿的证据,却不容易。
这种模糊的规则难免会有一些的误差。若是内网里有人对网站进行入侵探测,很可能导致正常用户也被屏蔽;或者攻击者放慢扫描速度,兴许又能躲过监视。
当然,一套好的规则和模型能让拦截更精准,不过这需要大量的分析和积累。对于 Web 这类特殊群体,我们能否另辟蹊径,寻找一种既简单又靠谱的方案?
在之前讲解的流量劫持系列中也曾提到,后端分析是十分被动的。好在它掌控着流量大权,而 Web 这种特殊流量,同时具备可执行能力。因此可化守为攻,开辟一条全新的作战方案。
所以,我们得借助前端技术,来实现最终目标 —— 做一个有理有据的规则系统。
但一个令人信服的证据不会无中生有,必须人为约定和创造,并在适当的时候将其带上,做到真正的『理据服』。
在开始构思的我们的『前后端 WAF』之前,有必要提一下现有的解决方案。
每当遇到这种禁止改包重放的场合,安全工程师们总能不假思索的给出解决方案。例如让页面产生个唯一的随机数和时间戳,加密后让后端去验证。
如果仅仅是为了解决个例,这样倒也无可厚非。然而现实中,这样的需求并不少见。如果要让每个业务都去现实这样的方案,将会极大增加前后端开发维护成本。
所以,把一些具体的方案抛给开发者,是很不合理的。对于开发者来说,理应投入全部精力在产品业务的开发上;与其不相关的事,都应交给适配层,让开发者无需了解任何细节,自动帮其实现。
于是,我们需要一个前后端相辅相成的切面系统,在其中透明解决这些琐碎的问题。这样才可大规模部署,以及后期统一更新和维护。
提到切面,Web『中间件』自然是我们的切入点。和过去的『中间人』劫持类似,我们在页面中注入一段脚本,以开启前端功能。
派出了位于前线的哨兵,就能提供更详细的情报,这在过去是难以实现的。如今前端技术日新月异,利用这些优势,我们开始构思一个全新的系统。
前面提到,如果能在发起请求时,提供一个证据来证明自己不是外人,那样后端就会好办的多 —— 不懂规矩的假人,自然会立即暴露破绽。
因此我们给页面 IO 做一层切面:在请求发出前一刻,带上一个蕴含各种私密信息的暗号,供后端验证。
借助之前《SSLStrip 的未来 —— HTTPS 前端劫持》中使用的技术,稍作修改即可实现前端层面的请求拦截。
如今我们目的更简单,只是携带一个额外参数而已,因此对于同站的请求,甚至无需修改目标 URL,将参数储存在 cookie 即可自动带入请求。
于是,开发者无需任何修改,就能获得更安全的防御。
在秘钥中,我们可储存各种环境的上下文,例如:
只能用一次的随机数,防止请求重放。
表单数据的校验值,防止中途被改包。
当前时间戳,让后端更精准的掌握发包间隔。
浏览器 BOM 特征,校验是否和 UserAgent 描述的浏览器相符合。
......
最终通过私有算法,将其编码成一个暗号秘钥。
当然,这个秘钥并不要求每次都严格验证。事实上首次访问,就是没有秘钥的;或者在钩子之外的网络请求,例如图片等资源文件,无法保证每次都有唯一的秘钥。
在 Web 富应用时代,『AJAX』和『JSONP』承载了绝大多数的接口请求,因此我们需严格防御。而普通的静态资源风险则小得多,可以更宽松一些。
不过,类似的系统曾经也有过尝试,但都是备受争议的。原因很简单,秘钥是在前端生成的,其中的秘密查看页面源码即可获得。一旦算法被解开,假人也能冒充真实用户,整个系统就失去了意义。
这也是为什么把『前端中间件』标注成黑色背景 —— 我们需要将前端脚本高度混淆,让攻击者难以在短期内破解其中的算法。
于是,我们可以把网络上的对抗,转换成逆向技术的比拼了。让攻击者需要具备更多的技能,从而提高入侵门槛。
即使无法破解,攻击者也能想尽办法,将其当做黑盒来使用。我们举几个能预测的情况,进行攻防模拟。
攻击者可完全无视加密细节,直接把假人的请求转到页面进行代理,因此看起来就像是正常业务发起的。
对于这样的情况,很难有绝对的防御措施。但可以用简单的策略:限制请求频率,从而降低攻击速度。
我们设定一个相对宽松的请求数阈值,如果一定时间里达到上限了,就让前端钩子 Pending 住请求,稍做休息再发出。并且堆积的越多,就让它推延更久。
如果正常用户短时间里操作太快,导致请求超标,那么惩罚几秒也情有可原(点的太快本来就会卡);但请求数持续居高不下的,那就很可疑了,是不是该好好休息下呢?
我们把请求数记录到全局存储里,在多个页面间共享,防止多开慢刷;并且页面关了仍然保留,下次回来继续惩罚,避免反复刷新页面清零。
即便如此,攻击者仍能想出一些规避方法。例如开上几个不同的浏览器、甚至虚拟机,作为完全隔离的环境,单独慢慢刷。
对付这样的假人,就得用一个靠谱的识别手段:用户行为分析。
正常的用户浏览页面,总是伴随着鼠标滚轮、移动、点击、触屏等事件。而且网络请求的发起,大多通过这些事件的驱动。若页面一动不动,却在不断的发请求,那很有可能就是开挂的。
甚至还可以考虑把采集到的行为数据,通过秘钥提交到后端进行分析,建立更详细的行为模型。
当然秘密总是会被发现的。行为采集这个门槛也难不倒攻击者,如今能模拟用户行为的机器人也不在少数,它们能逼真的模仿出各种事件,而我们也只能初略的分析。
不过能把攻击者的门槛提高到这一步,我们的目的也达到了 —— 我们并非要 100% 阻止假人,而是通过对抗减少假人。
在黑盒对抗下,由于攻击者 不了解实情,只能见招『猜』招,很是被动。我们可以不时更新下脚本,调整策略,或者添加一些巧妙的思路,不断折磨攻击者。
对于攻击者,显然不甘长久在黑盒中对抗,会想方设法破解脚本。
好在相比传统语言,JavaScript 流行起来的时间还很短,成熟的逆向工具少之又少。而且运行于浏览器,又会牵扯到各种 DOM 与 BOM,因此还得了解不少的前端知识。
做一个好的混淆器,需要不少理论知识。不过不必搞的那样先进 —— 我们只需比攻击者想的更远就可以了。
在实际对抗中,无需太过纠结『技术』层面,更多的是需要『计谋』。有些东西其实原理很简单,但就是想不到。这里就不详细讨论混淆技术了,分享几个非技术层面对抗的案例。
真正的脚本混淆器,应该是打乱原先的代码结构,并且加入各种多余语句,以增加调试复杂度。
不过目前有相当多的混淆器,只是加个壳而已 —— 把原先的代码进行加密,运行时解密再 eval 执行。要解开这种『混淆』毫不费力,把 eval 替换成 alert 就能原形毕露,相信大家都尝试过。
当然,我们也可以利用人们一些天真的想法,进行真假迷惑。
我们显然不会『加壳』原始代码,但可以准备一套伪代码,假装先解密再 eval。这套假代码看上去和真的一样,但里面的功能并不会触发,仅仅用以迷惑而已,把攻击者引到错误的方向上,从而浪费其时间。
而真正的代码,则夹杂在解密的区域,在脱壳之前已经开始运行了。
在解密伪代码的时候,还可以往其中插入大量无用的内容。例如眼花缭乱的特殊符号、成千上万的续行符,阻碍正常阅读。
尽管这不能解决根本问题,但能消耗攻击者的精力,这就是非技术对抗。
既然想逆向,那总得先大致看一下脚本。在眼花缭乱的代码里,一段可读文本,就像是万木丛中一点红。利用它吸引攻击者的眼球,从而上钩。
例如,我们在代码开头的某个字符串里,写一段『... compressed by xxxtool』。攻击者看到这段文字,必然会好奇 xxxtool 是什么工具,于是就去网上搜索。
我们预先制作一个简单的网页,提供在线 JavaScript 的加密和解密。名字就叫 xxxtool,一个非常特殊的名字。我们从不推广这个页面,正常情况下根本不会有人来访问。
当攻击者搜到这个网站,自然会进来看看。发现还提供在线解密,以为找到了解药,立即将代码粘进来试试。
落入了我们的蜜罐,就免不了被我们忽悠了。如果是一般的脚本,就显示普通工具的结果;一旦发现是在解密自己的代码,赶紧拿出预先准备的那套伪代码,让攻击者误以为成功解开了。
同时,只要攻击者一进入我们的网页,就立即上报给云端,能及时了解有谁在研究我们的脚本。甚至还可以记录下访客的 IP,通知给 WAF 封杀一段时间。
当然,也未必要那么复杂。我们可以在一个永远到不了的条件分支里,请求一个特殊页面;如果某天发现这个页面有访问量了,显然是有好奇的人在尝试破解。
类似的对抗思路还有很多,以后有时间再分享。只有『技术』与『计谋』结合,才能在对抗中更胜一筹。
回到正题,对于这个系统来说,即使被破解也不会有太大的损失。只要换一套秘钥算法和混淆方案,又可以继续我们的防御。自动化的部署,能让我们的更新维护更简单,为持久对抗提供强有力的保障。
我们把传统的网络攻防,转换成逆向技术的对抗,让系统涉及更多的领域。对于攻击者来说,就需要掌握更多的技能点,从而提高入侵的门槛。
退一步,即使攻击者能轻易破解我们的系统,那也无法立即应用于现有的安全工具,必须改造才能使用,同样能大幅增加攻击成本。