14

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

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI1NDc5MzIxMw%3D%3D&%3Bmid=2247487849&%3Bidx=1&%3Bsn=bbcc53a9bbc01ed269ca3120db9cc10f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

导语

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

背景

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

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

  • 服务端鉴别:服务端根据请求特征、行为等判断请求是否是爬虫或者机器。

  • 动态签名:针对每次的请求,根据参数不同,按照一定算法规则动态生成相应的签名,服务端验证签名合法性,主要防篡改。

  • 身份验证:需要用户登录,后端在处理接口之前先校验用户信息合法性,然后再处理接口。

  • 数据加密:针对安全性要求较高的请求做数据加密,比如游戏密令登录,支付转账等。

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

故事开始

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

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

牛刀小试

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

3EFbIzn.png!web

爬虫找到数据接口,通过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 参数中,随请求一起发送。

auUnu2f.png!web

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

崭露头角

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

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

3EBreeU.png!web

大致步骤如下:

  • 用一个数组将每个接口完整地址(含签名)收集并存储起来。

  • 循环将数组中的接口挨个请求。

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

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

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

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

je6F7fA.png!web

大致步骤如下:

  • 计算签名时,将当前时间一起算入签名中。

  • 发送请求时候,将时间与签名放入参数中,一起随请求发送。

  • 后端可根据签名,验证参数的合法性,根据时间,判断签名有没有过期。

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

针锋相对

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

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

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

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

e2U7feE.png!web

B、借助工具格式化

niAFj2I.png!web

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

yU77jei.png!web

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

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

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

  • 服务端收到请求,首先验证cookie 中的token 是否合法,然后验证签名是否合法。

  • 服务端在返回数据时,将重新生成token 随cookie下发至浏览器。

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

nIZ7Jre.png!web

整个流程大致如下:

  • 生成签名时,客户端从cookie 中获取 token 值。将token 值加入到签名规则中。

  • 服务端收到请求,首先验证cookie 中的token 是否合法,然后验证签名是否合法。

  • 服务端在返回结果时,在cookie 中重新设置新的token。

  • 客户端第一次请求,当cookie中没有token时候,服务端返回特殊的错误状态码,sdk 识别这个状态码,会进行第二次请求(第二次会拿到第一次返回的token)。

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

神仙打架

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

VBfuIbN.png!web

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

bIVnim3.png!web

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

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

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

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

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

YjuaM3F.png!web

大致步骤如下:

  • 第一次请求,必返回异常(没有token),将返回header 中的cookie 记录下来用于第二次请求。

  • 请求中随机构造浏览器ua 等参数,突破后端的反爬策略。

  • 从第二次请求开始,每次请求返回的token,用map 存储起来,便于下次使用。

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

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

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

2、【防】爬虫利用token -- 行为统计
  • token 的设计,一千个人就有一千个设计。token 的生成和校验是一个高频行为,所以token的生成要保证高效 、加密,随机、体积小。

  • 爬虫与正常用户最大的差别是行为上访问频次不同,如果token 能记录下一个用户在一段时间内的访问频次,那从行为上可以区分出爬虫和正常用户,所以我们token 的设计除了用来校验合法性以外,我们还要记录用户一段时间内的访问频次。

EBzEJbf.png!web

  • 接口采用9位数组存放token, token中包含随机数,时间,请求量,以及加密校后的校验位置。

  • 服务端不对token 做存储,只校验token 的合法性校验。

  • t在一定允许时间内,token中的时间不更新,用做漏斗计数,记录用户在这段时间内的访问频次。

NzIZNjJ.png!web

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

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

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

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

接口其实还有一层处理,

异常token IP计数

NnMruaE.png!web

上面代码中:
没有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、【攻】高频被拒 -- 限流访问

qMFr6vf.png!web

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

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

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

2、【防】反编译 -- 混淆加密
  • sdk 代码不宜全部混淆,混淆核心代码就行了。

原始代码:

eeMzYvM.png!web

将核心代码加密

iq2Uv2m.png!web

加密的目是为了提高破解的难度,并不是从理论层面去达到代码的不可逆。所以我们加密的选型大致从两个维度考虑。
A、采用市面上已有的不可逆加密(至少不容易)
已有的将代码混淆的加密方案 Eval,
Array,_Number,JSfuck,JJencode,AAencode,URLencode,Packer,JS Obfuscator,My Obfuscate。均可被解密,且都能找到相应的开源解密工具,(不过可以使用两种以上的混淆方式,达到混合混淆,破解也有一定难度)。
上图代码采用的国内的jshaman 加密,不能保证完全不可逆,但至少还没有一个开源的将代码一粘就可以逆回来工具。
B、自己实现简单的可逆加密,但是逆向工程需要自己实现。
例如自己修改jjencode加密算法,然后混淆代码如下图:

fQvAFjV.png!web

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

  • 无法用肉眼识别是采用的哪种混淆方案。

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

3、收!让我们回到主题

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

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

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

秋色平分

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

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

EJzAJzQ.png!web

结尾

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

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

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

本文 完


作者简介

龚虹宇,2017年8月加入房产事业部。主要负责58商业地产业务与前端基础服务的建设工作。

阅读推荐


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK