我们是守方,我负责活动的前端页面和node服务,作为先锋,拦一些比较简单的爬虫,并留一些陷阱。

参加者攻破前端页面以后,就要靠我方神盾的规则来拦截可疑ip,限流以及拉黑。

活动简单来说就是通过爬取特定的网站上的信息,综合参与者的的请求被识别为爬虫的次数,来评分。

活动页介绍

共有两个主要页面,一个列表页,每页10个公司,带有分页功能,以及公司的详情页。
参加者需要爬取5000家公司的相应信息来得分。
活动的页面选择使用 Pug(html模板语言) + Nest.js(node服务) 通过后端直接渲染页面的方式来完成。

得分项及反爬措施

一共五个得分点:
活动页得分项

1. 公司名称

最简单的一项,只要请求页面就可以直接拿到,没有任何的反爬措施,白送的1分。
白给的公司名称

2. 热线电话

400热线电话使用雪碧图(精灵图)显示,略提升了一些难度。
准备了10张包含 0-9 10个数字且顺序不同的图片,node服务每次随机获取一张,并返回对应电话的位移量,用css在页面上显示完整的电话。
热线电话
随机的底图

【破解方法】

方法一:将所有可能性的底图全部爬下来,然后找到位置偏移量的对应关系 Mapping,正则或是程序定位到css的偏移量,从 Map 里找到对应数字。
方法二:OCR

3. 联系人手机号

页面初次加载时,手机号从服务渲染出来就是带有星号的,需要通过一个 Ajax 请求来获取详细的号码。
获取手机号的请求

请求返回的手机号经过了一些加密处理,原理简单来说就是把数字的 charCode 偏移几位,然后转成 base64。客户端拿到以后用相同的方法,逆向解就可以拿到真实的手机号。

node 层加密过程:

1
2
3
4
5
6
7
8
export const encode = (str: string, digit = 10) => {
let convert = '';
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);
convert += String.fromCharCode(charCode + digit);
}
return Buffer.from(convert).toString('base64');
};

客户端解密过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function es(_0x380035) {
const _0x4301eb = _0x46a74e;
let _0x165d09 = '';
const _0x35a10a = window['atob'](_0x380035);
for (
let _0x321545 = 0x0;
_0x321545 < _0x35a10a[_0x4301eb(0x79)];
_0x321545++
) {
const _0x49c453 = _0x35a10a[_0x4301eb(0x6d)](_0x321545);
_0x165d09 += window[_0x4301eb(0x6f)]['fromCharCode'](_0x49c453 - 0xa);
}
return _0x165d09;
}

虽然是混淆的,但是还是在客户端的源码里可以找到,只要有node或是浏览器的运行环境,就可以轻松运行 es() 来解(完全不需要管过程)。

手机号这里还留了一个陷阱,我们网页加了设备指纹,如果用浏览器正常打开,在请求的header里会带有相关的字段,如果是通过 Api 打过来的则没有。
对于没有相关header的请求,会返回一个假的加密过的手机号。

【破解方法】

方法一:从浏览器调试工具里不难找到 es 这个 Function,解混淆,然后用爬虫所用语言重写一遍,拿到手机号后调用重写的方法即可反解。
方法二:OCR

4. 联系人名字

这里自己把自己坑了,用的 WebSocket 获取联系人,结果没考虑到 socket 只有连的那下有个http请求,后面都是保持连接直接通讯,所以根本不会过Ingress,并且中反爬的规则,也无法跳验证码拦截。

点击获取详情按钮后:
ws获取联系人

最后决定加一个限流,如果获取太快服务层就主动断开 socket,需要重连。

【破解方法】

方法一:只要接通连上服务端的 WebSocket,模拟浏览器的 headers,以及握手方式,浏览器传什么,模拟就传什么,最后总能拿到,而且连上一次就不需要断开了。
方法二:OCR

5. 公司地址

这里用了字体反爬。
服务层渲染的是一个混淆后的地址(文字),需要通过相应的字体文件,用css font-family 来显示正确的内容。

公司地址混淆 公司地址真实 字体文件,英文及中文阿拉伯数字映射

【破解方法】

方法一:获取到所以有可能性的字体文件,然后使用相关工具解除文字和文字或文字和字符的对应关系,然后反解。
方法二:OCR

以上五项就是得分点,5000条数据,满分5万分,分高者取胜。

6. 页码处理

列表页的地址栏会带有页码,例如:?p=12
为了让「爬虫」们不要轻易就用p=1、2、3、4的循环去打开页面,将页码简单加了个密,原理和手机号显示是一样的,每个数字的 charCode 位移几位,然后 base64 转换,就成了下面的样子:

列表页页码处理

这样一来大家只能模拟去点击下一页来获取页码,而不是直接猜,当然这种加密是很简单的,看这个字符串就长得很像 base64,所以还是可以花时间猜出来。

7. 设备指纹

每个访问者第一次访问后都会留下指纹,放到 localStorage,再下一次访问的时候,同一个指纹的行为和 ip 进行分析。

获取指纹

【破解方法】

方法一:开代理并且频繁切 ip 的话基本可以无视这个。

神盾

当然前端的反爬再花里胡哨也是有局限的,如果要照顾SEO,那就更不能做什么太多的防爬措施了。并且在如今相当成熟的 OCR 面前,前端反爬都是浮云。

所以作为先锋,以上我做的只是拖慢大家的爬取的时间。真正的盾还是在神盾系统。我们会收集用户的真实IP,然后进行分析,识别为爬虫的则会返回验证码滑块,如果多次命中规则,则会限流。

而我在前端的工作本质是增加需要获取全部信息接口的数量,接入神盾的SDK,获取正确的IP给到神盾,并加入设备指纹。这次爬虫攻防活动也是为了收集爬虫的行为,为以后神盾规则优化做铺垫。

DONE.

第一名以3w分左右获胜。

颁奖