前端爬虫攻防之接口签名方案「转」

.NET
523
0
0
2022-03-27
标签   .NET爬虫
内容简介:背景本文主要探讨接口在使用动态签名的机制下,爬虫与接口的相互攻防策略。故事开始
本文转载自:http://mp.weixin.qq.com/s?__biz=MzI1NDc5MzIxMw==&mid=2247487849&idx=1&sn=
bbcc53a9bbc01ed269ca3120db9cc10f,转载出于传递更多信息之目的,版权归原作者或者来源机构所有。

导语

本文通过爬虫与接口服务的攻防回合制,简单介绍如何使用签名来提高接口安全性。道高一尺,魔高一丈。道不服,再高1.5丈。

背景

如何提高api 接口的安全性?

服务端将接口提供给客户端,在公网开放访问,非常危险。我们既要确保客户端与服务端之间的安全通信,又要考虑防爬防篡改等恶意攻击。没有绝对安全的接口,我们只能做的让接口相对安全一些。提高接口安全性的方法一般分为以下几个方面:

  • 服务端鉴别:服务端根据请求特征、行为等判断请求是否是爬虫或者机器。
  • 动态签名:针对每次的请求,根据参数不同,按照一定算法规则动态生成相应的签名,服务端验证签名合法性,主要防篡改。
  • 身份验证:需要用户登录,后端在处理接口之前先校验用户信息合法性,然后再处理接口。
  • 数据加密:针对安全性要求较高的请求做数据加密,比如游戏密令登录,支付转账等。

本文主要探讨接口在使用动态签名的机制下,爬虫与接口的相互攻防策略。

故事开始

一个很普通的晚上,一段代码悄悄运行,代号 -爬虫。

接口的访问量突然升高报警不断。就这样一段爬虫与接口的博弈,拉开序幕。

牛刀小试

1、 【攻】数据采集 -- 脚本抓取

前端爬虫攻防之接口签名方案「转」

爬虫找到数据接口,通过for 循环更改page 页码参数,很轻松的采集到了该接口下的所有数据。但并不满足,还写了定时脚本,每天晚上了2点准时开跑。

突然有一天,接口失效了。接口由原来的

https://xxx.58.com/zufang/list?page=1变为

https://xxx.58.com/zufang/list?page=1&sign=

de2e67afcc554c1840886a96e2211589 。

是的,爬虫的行为被接口察觉到了,接口反手做了签名处理。

2、 【防】参数篡改 -- 尾随签名

首先,业务代码中需要安装一个请求接口的sdk,sdk中封装了签名逻辑。

SDK中签名步骤大致如下:

  • 将所有业务请求body参数序列化成一个json。
  • body参数和url中的请求参数通过Sign函数md5加密得到签名sign。
  • 将签名追加在url 参数中,随请求一起发送。

前端爬虫攻防之接口签名方案「转」

经过签名处理后,爬虫将参数篡改后发送的请求,接口能够识别出来,并直接拒掉返回。

崭露头角

然而并没有难倒爬虫,url 虽然经过了签名处理,但参数相同的情况下,签名出来的url是固定的。

1、【攻】参数签名-- url 收集

前端爬虫攻防之接口签名方案「转」

大致步骤如下:

  • 用一个数组将每个接口完整地址(含签名)收集并存储起来。
  • 循环将数组中的接口挨个请求。

收集url过程虽然有点麻烦,但却是一劳永逸的事。

接口侧很快发现之前的策略并没有有效的制止住爬虫,于是做了升级

2、【防】url重复利用 -- 签名引入时间

客户端计算签名时候,请求参数中添加t=当前时间,然后计算签名,当前时间随参数发送到后端,后端校验签名的时间是否在一段时间范围内,如果超过一定时间,则判断签名非法。

前端爬虫攻防之接口签名方案「转」

大致步骤如下:

  • 计算签名时,将当前时间一起算入签名中。
  • 发送请求时候,将时间与签名放入参数中,一起随请求发送。
  • 后端可根据签名,验证参数的合法性,根据时间,判断签名有没有过期。

引入时间因子后,签名出来的url在一定时间后会失效。爬虫无法将一个签名长期使用。

针锋相对

爬虫发现请求参数、时间均可在客户端生成,既然不能重复的利用现有的签名,那就自己去生成签名。

1、【攻】动态签名 -- 签名伪造

自己生成签名,其实不易,但阻止不了一个爱钻牛角尖的爬虫。

A、逆向代码,肉眼找到相关逻辑

前端爬虫攻防之接口签名方案「转」

B、借助 工具 格式化

前端爬虫攻防之接口签名方案「转」

C、抽取签名函数,爬虫主程序修改参数重新计算签名,然后抓取。

前端爬虫攻防之接口签名方案「转」

提取出签名函数后,重新计算签名,与正常用户请求的数据一模一样。并且签名所需要的各种因素均可自给自足,简单方便有效。

接口不得不再次升级,之前只对签名正确性做校验,没有对签名合法性做校验。所以决定在签名因子中加入一个token字符串。这个字符串,只能后端生成,后端校验。

2、【防】客户端伪造签名 -- 引入token

  • 服务端收到请求,首先验证cookie 中的token 是否合法,然后验证签名是否合法。
  • 服务端在返回数据时,将重新生成token 随cookie下发至浏览器。

SDK中根据token生成签名代码如图:

前端爬虫攻防之接口签名方案「转」

整个流程大致如下:

  • 生成签名时,客户端从cookie 中获取 token 值。将token 值加入到签名规则中。
  • 服务端收到请求,首先验证cookie 中的token 是否合法,然后验证签名是否合法。
  • 服务端在返回结果时,在cookie 中重新设置新的token。
  • 客户端第一次请求,当cookie中没有token时候,服务端返回特殊的错误状态码,sdk 识别这个状态码,会进行第二次请求(第二次会拿到第一次返回的token)。

token 由后端生成,随cookie 下发,客户端无法伪造,也不知道生成规则,无法自给自足伪造签名。

神仙打架

一段时间后,爬虫发现接口失效,排查发现接口并没有增加新的参数,而且发现一个奇怪的现象:

前端爬虫攻防之接口签名方案「转」

在控制台中抓到一个链接,能正常返回数据。然后将这个链接拷贝出来,在浏览器访问,则提示请求非法。

前端爬虫攻防之接口签名方案「转」

爬虫百思不得其解。同样的浏览器,同样的环境,上一次请求,第二次就不行了。两次请求区别在哪儿?

此时一道的闪电劈中爬虫的天灵盖,脑中闪过一道灵感 “cookie 中有诈”

经过爬虫分析,发现每次签名都会将cookie 中的一个字符串带入进去,并且,每次请求完成后,这个cookie 被响应头给重置了。所以一个链接在浏览器里面第二次使用的时候,cookie 里面的token 已经发生变化,导致后端校验不通过。

爬虫很快对程序进行修改。

1、【攻】动态签名 -- 浏览器行为模拟

前端爬虫攻防之接口签名方案「转」

大致步骤如下:

  • 第一次请求,必返回异常(没有token),将返回header 中的cookie 记录下来用于第二次请求。
  • 请求中随机构造浏览器ua 等参数,突破后端的反爬策略。
  • 从第二次请求开始,每次请求返回的token,用map 存储起来,便于下次使用。

v4.0的爬虫犹若开启了神智,签名不管什么策略,复杂程度,都能模拟。而且市面上大部分的签名程序都能通过相似的步骤破解 。

接口方脑袋疼。爬虫请求来的数据和正常用户的一模一样,还有没有什么办法辨别?

同样的,那道劈中爬虫的闪电又劈中了接口的天灵盖。

2、【防】爬虫利用token -- 行为统计

  • token 的设计,一千个人就有一千个设计。token 的生成和校验是一个高频行为,所以token的生成要保证高效 、加密,随机、体积小。
  • 爬虫与正常用户最大的差别是行为上访问频次不同,如果token 能记录下一个用户在一段时间内的访问频次,那从行为上可以区分出爬虫和正常用户,所以我们token 的设计除了用来校验合法性以外,我们还要记录用户一段时间内的访问频次。

前端爬虫攻防之接口签名方案「转」

  • 接口采用9位数组存放token, token中包含随机数,时间,请求量,以及加密校后的校验位置。
  • 服务端不对token 做存储,只校验token 的合法性校验。
  • t在一定允许时间内,token中的时间不更新,用做漏斗计数,记录用户在这段时间内的访问频次。

前端爬虫攻防之接口签名方案「转」

token 中采用漏斗计数策略后,从访问频次大致可以区分爬虫和正常访问。从而拒绝掉一些高频流量访问。

3、还有一个问题,如果接口每次都不利用上一次token,token传空怎么处理?

这儿在sdk 侧有个简单处理,当token 为空时,后端返回一个token 为空的错误状态,但返回错误状态时,cookie中会下发token。sdk此时会进行第二次请求,第二次则能够拿到正确数据。

爬虫也可以利用这个原理,绕开token行为统计。

接口其实还有一层处理,

异常token IP计数

前端爬虫攻防之接口签名方案「转」

上面代码中:

没有token的请求,我们拿到ip ,将ip转换为32位int,在一个int 的map中加一,并判断map中这个ip是否超过一定阈值,如果超过则拒绝返回。

4、代码中有几个细节简单说明一下:

A、为什么这个规则只应用于没有token的请求,而不应用于所有的请求。

应用于所有请求,容易大面积误伤。学校,机场等公共wifi,大部分人的出口ip是同一个,很容易命中策略。而对没有token的请求应用此策略,既能做到ip限制也可以降低误伤可能性

B、为什么不直接用ip 字符串作为key,需要转换为int作为key?

32位int 占4字节,在node 中是一个number 最多也就8字节,而字符串的话所占字节位7-15字节,int 存储可节省内存。

map 中number 作为key 效率相当于数组下标寻址,而用字符串做key,map 中还需要转一遍hash。number key 在存储和查询效率上会高出一大截。

5、说明完毕,回到正文

爬虫发现接口抓取在一定数量后,接口返回就异常了。一定是通过啥手段识别出来短时间内访问太多,爬虫进行了更进一步的调整。

点到为止

爬虫对着忽有忽无的接口,大致也猜到了接口的一些限流处理。然而并没有难住他。

1、【攻】高频被拒 -- 限流访问

前端爬虫攻防之接口签名方案「转」

显示的规则破解sdk可以拿到签名代码,隐示规则可以自己写爬取策略,换ip、限频次、模拟行为、伪造内容,防不胜防。

策略全开的爬虫,有了那么一点灵性。这点灵性,接口如何来应对?

接口祭上了一份小小礼物。

2、【防】反编译 -- 混淆加密

  • sdk 代码不宜全部混淆,混淆核心代码就行了。

原始代码:

前端爬虫攻防之接口签名方案「转」

将核心代码加密

前端爬虫攻防之接口签名方案「转」

加密的目是为了提高破解的难度,并不是从理论层面去达到代码的不可逆。所以我们加密的选型大致从两个维度考虑。

A、采用市面上已有的不可逆加密(至少不容易)

已有的将代码混淆的加密方案 Eval,

Array,_Number,JSfuck,JJencode,AAencode,URLencode,Packer,JS Obfuscator,My Obfuscate。均可被解密,且都能找到相应的开源解密工具,(不过可以使用两种以上的混淆方式,达到混合混淆,破解也有一定难度)。

上图代码采用的国内的jshaman 加密,不能保证完全不可逆,但至少还没有一个开源的将代码一粘就可以逆回来工具。

B、自己实现简单的可逆加密,但是逆向工程需要自己实现。

例如自己修改jjencode加密算法,然后混淆代码如下图:

前端爬虫攻防之接口签名方案「转」

首先说明一下,以上代码是可逆的,混淆逻辑就是将jjencode混淆函数中的混淆变量做了一些替换,逻辑上做了一些调整。但替换后有两个好处:

  • 无法用肉眼识别是采用的哪种混淆方案。
  • 无法用市面上现有的开源解密工具解密出来,除非爬虫精通市面上的大部分混淆方案,然后自己实现一个解密函数。

3、收!让我们回到主题

代码加密有什么用呢?加密代码和不加密代码,放在浏览器控制台不都能运行出相同的结果?

是的,在浏览器端都能运行出相同的结果,但是加密后的代码在node端则无法正常运行出正确的结果(黑人问号.jpg)。

例如代码中判断 window.document 对象是否存在,如果存在,则走正常的签名,如果不存在则返回错误的签名。由于混淆后的代码,不易反编译,所以爬虫无法知道里面的判断逻辑,无法伪造浏览器环境。进一步提高破解门槛。

秋色平分

1、【攻】混淆加密 -- 完全浏览器模拟

逆向加密后的代码比较困难,但有个思路,本地装一个浏览器内核,调用浏览器api 执行js 就可以抓取数据了。

前端爬虫攻防之接口签名方案「转」

结尾

爬虫与接口没有绝对的胜负,两者的攻防较量都了献上了自己的智慧,当爬虫具备一些灵智,开始模拟用户环境和行为后,接口辨别爬虫与正常用户变得更为困难。接口签名只是提高提高伪造门槛,属于防爬的一个环节,拦住一些低级的爬虫。

当爬虫真的能完全模拟浏览器后,接口可开启接入反爬服务。反爬服务中,对ip,浏览器ua等有全方面的分析,爬虫虽然能完全模拟浏览器行为,但爬虫并没有那么多机器用来来部署,ip是有限的,最终也会被反爬

服务识别出来。但反爬服务会对接口性能造成一定影响,所以可作为最后的防线,选择性的开启。