进击的反爬机制

爬 虫 & 反爬虫

爬 虫:
指通过一定的规则策略,自动抓取、下载互联网上的网页内容,再按照某些规则算法抽取数据,形成所需要的数据集。有了数据,就有可能进行一些非法活动。
反爬虫:
一般指网站管理员使用一定的技术手段,防止爬虫程序对网站的网页内容进行爬取,以及阻止通过爬虫获取数据后的非法活动。


反爬方与爬虫方相互博弈,不断制造爬取难度,或一定程度上阻止了爬虫行为。爬虫方也在不断更新技术,来对抗种种反爬限制。


对抗过程

初始状态下,网站提供网站服务,未部署任何反爬措施。

ROUND 1

爬虫方启动爬虫程序 (实验环境中使用 scrapy 爬取) 成功爬取网页内容。

反爬方发现有爬虫程序在对网站进行爬取,在分析日志中访问请求的 user-agent 后,加载 iWall3 Web应用防火墙模块,编写并加载防护规则 anti-crawlers-match-user-agents.json 如下:

{
  "info": {
    "title": "UA Crawlers list"
  },
  "rules": [
    {
        "meta": {
            "phase": 2,
            "desc": "https://github.com/coreruleset/coreruleset/blob/v3.3/dev/rules/crawlers-user-agents.data"
        },
        "if": {
            "variable": [
                "REQUEST_HEADERS", 
                "REQUEST_BODY", 
                "REQUEST_FILENAME"
            ],
            "transform": "lowercase",
            "operator": "contain",
            "pattern": [
                "80legs",
                "black widow",
                "blackwindow",
                "prowebwalker",
                "pymills-spider",
                "ahrefsBot",
                "piplbot",
                "grapeshotcrawler/2.0",
                "grapefx",
                "searchmetricsbot",
                "semrushbot",
                "rogerbot",
                "mj12bot",
                "owlin bot",
                "lingewoud-550-spyder",
                "wappalyzer",
                "scrapy"
            ]
        },
        "then": [
            "IP.bad_ua_count@20=IP.bad_ua_count+1",
            "if": "IP.bad_ua_count > 3",
            "then": {
                "action": "deny",
                "log": "Too frequnt access from crawlers."
            }
        ]
    }
  ]
}

爬虫方再次爬取网页,发现超过一定阈值后,后续爬虫请求被阻止:

pic02

反爬方防护成功。

ROUND 2

爬虫方在初次交锋中 user-agent 被识别,这一次则使用随机的 user-agent (scrapy 使用 random_user_agent 配置),成功爬取网页内容。如下图:

pic03

反爬方利用 user_agent 防护的措施失效后,可根据“爬虫 (同一个 IP 地址) 在短时间内会访问多个不同应用 (如 url)”的逻辑,编写并加载防护规则 anti-crawlers-limit-user-access-different-application.json 如下:

{
    "rules": [
        {
            "meta": {
                "phase": 2,
                "function": "当IP.urls字段中不包含当前访问的请求名,则进行设置操作加判断操作。如果是参数代表不同应用修改即可。",
                "function": "设置操作包括给原有的IP.urls字段append新的请求名,并给IP.urls_count加1;",
                "function": "判断操作是判断IP.urls_count是否大于5(代表10秒内访问了不同的url,不同的url代表不同的功能),",
                "function": "若是则deny,否则放行。"
            },
            "if": [
                {
                    "or": [
                        "REQUEST_FILENAME == '/shopxo-1.6.0/index.php' && @ARGS.s == '/index/user/reginfo.html'",
                        "REQUEST_FILENAME == '/shopxo-1.6.0/index.php' && @ARGS.s == '/index/user/logininfo.html'",
                        "REQUEST_FILENAME == '/shopxo-1.6.0/index.php' && @ARGS.s == '/index/category/index.html'",
                        "REQUEST_FILENAME == '/shopxo-1.6.0/index.php' && contain(@ARGS.s, '/index/search/index/category_id')",
                        "REQUEST_FILENAME == '/shopxo-1.6.0/index.php' && contain(@ARGS.s, '/index/article/index/id')",
                        "REQUEST_FILENAME == '/shopxo-1.6.0/index.php' && contain(@ARGS.s, '/index/goods/index/id')"
                    ]
                },
                {
                    "not": {
                        "variable": "IP.args_s",
                        "operator": "contain",
                        "pattern": "';' .. ${@ARGS.s} .. ';'"
                    }
                }
            ],
            "then": [ 
                "IP.args_s@10=IP.args_s .. ';' .. @ARGS.s .. ';'",
                "IP.args_s_count@10 = IP.args_s_count+1",
                "if": "IP.args_s_count > 5",
                "then": {
                    "action": "deny",
                    "log": true
                }
            ]
        }
    ]
}

超过一定阈值后,后续爬虫请求被阻止:

pic05

反爬方防护成功。

ROUND 3

爬虫方由于在上一回合中使用同一 IP 地址且访问速度过快,导致反爬方可以依此特性设置防护。故改为使用 IP 代理池 (scrapy 使用 IP 代理池),成功爬取网页内容。Proxy_ip 的设置如下所示:

pic06.png

反爬方更新防护措施,相应地在页面中增加“蜜罐页面链接”。一旦爬虫程序访问“蜜罐页面链接”就会被拦截。编写并加载防护规则 anti-crawlers-add-honeypot-page.json 如下:

{
    "rules": [
        {
            "meta": {
                "phase": 3,
                "function": "当访问/shopxo-1.6.0/idnex.php时,在<a href ...>标签下面增加一个标签(含有假的链接)"
            },
            "if": [
                "REQUEST_METHOD == 'GET'",
                "REQUEST_FILENAME == '/shopxo-1.6.0/'",
                "#ARGS == 0"
            ],
            "then": {
                "execution": [
                    {
                        "directive": "alterResponseBody",
                        "op": "string",
                        "target": "<div class=\"navigation-button am-show-sm-only\">",
                        "substitute": "<div class=\"navigation-button am-show-sm-only\"> <a href=\"http://10.10.11.67/shopxo-1.6.0/index.php?s=/index/user/fake_logininfo.html\"></a>",
                        "ignore_case": false
                    }
                ]
            }
        },
        {
            "meta": {
                "phase": 1,
                "function": "当爬虫进行页面爬取时,就会访问到假的链接时,进行阻止"
            },
            "if": [
                "REQUEST_FILENAME == '/shopxo-1.6.0/index.php'",
                "@ARGS.s == '/index/user/fake_logininfo.html'"
            ],
            "then": {
                "action": "deny"
            }
        }
    ]
}

网站加载此规则后,爬虫程序有些请求会被拦截,效果如图:

pic08

反爬方防护成功。

ROUND 4

爬虫方在前面的攻防对抗中,爬虫程序进行全局爬取会访问到“蜜罐页面链接”。为避开蜜罐,爬虫方使用 Selenium + WebDriver 对网站进行访问,成功爬取网页内容。

反爬方为应对新的爬虫手段,尝试在前端做“插桩”操作,并判断返回的 WebDriver 属性,编写并加载防护规则 anti-crawlers-check-is-selenium-chromedriver.json 如下:

{
    "rules": [
        {
            "function": "1.当访问GET /shopxo-1.6.0/index.php?s=/index/user/reginfo.html 时,修改</head>为",
            "function": "<script>const ret = window.navigator.webdriver;$.get(\"http://[这块写上host即可]/shopxo-1.6.0/index.php/send_result.js\"+$ret, function(data, status){});</script></head>",
            "meta": {
                "phase": 3
            },
            "if": [
                "REQUEST_FILENAME == '/shopxo-1.6.0/index.php'",
                "@ARGS.s == '/index/user/reginfo.html'"
            ],
            "then": {
                "execution": [
                    {
                        "directive": "alterResponseBody",
                        "op": "string",
                        "target": "var __user_id__ = 0;",
                        "substitute": "var __user_id__ = 0; var ret = window.navigator.webdriver;var url_ret=\"http://10.10.11.67/shopxo-1.6.0/index.php/send_result.js?ret=\"+ret; var httpRequest = new XMLHttpRequest(); httpRequest.open('GET', url_ret); httpRequest.send(); ",
                        "ignore_case": false
                    }
                ]
            }
        },
        {
            "meta": {
                "phase": 1,
                "function": "当访问send_result.js时,获取到ARGS.ret,查看是否是true,若是则deny"
            },
            "if": [
                "REQUEST_FILENAME == '/shopxo-1.6.0/index.php/send_result.js'",
                "@ARGS.ret != 'false'"
            ],
            "then": {
                "verdict": {
                    "action": "deny",
                    "log": "client is chromedriver!",
                    "continued": {
                        "subject": "REAL_IP",
                        "action": "deny",
                        "duration": 36000,
                        "log": "${REAL_IP.__id} in blocking"
                    }
                }
            }
        }
    ]
}

网站加载本规则后,爬虫请求被拦截,且被持续拦截。

反爬方防护成功。

ROUND 5

爬虫方在上一轮对抗中,因使用 Selenium + WebDriver 会将 WebDriver 属性设为真,对方可据此属性值对爬虫进行拦截。采用如下设置即可修改 WebDriver 的值:

Object.defineProperties(window.navigator,{webdriver:{get:()=>false}})

爬虫方每次使用 Selenium + WebDriver 进行爬取前,预先执行一下这段 js 代码,即可成功爬取网页内容。

反爬方对应的防护规则因 WebDriver 值被人为更改而失效,继而尝试增加字体反爬规则。

规则背景是,在爬虫与反爬示例中,字体文件会产生一个 *.woff 的请求:

pic10

以测试页面 niushop 项目首页为例,对价格进行字体反爬处理:

pic11

规则如下:

pic12

网站加载此规则后,爬虫方获取的价格显示为乱码。

反爬方防护成功 (价格信息)

ROUND 6

爬虫方受制于字体反爬规则,爬取价格信息时得到的是乱码。于是,在网站字体文件不变的情况下,直接解析固定的 woff 文件——

使用 Python 下 fontTool 库的 ttLib 包,破解反爬的代码文件与效果如下:

pic13
pic14

爬虫方成功获取网页信息 (准确的价格信息)

反爬方升级字体反爬规则,以应对爬虫方借字体文件对乱码所做的还原,升级思路是“字体信息不换,动态更换字符编码”——

1. 使用 fontCreator 软件对字体文件进行编码和位置的修改,并产生多个“编码各不相同”、“字体顺序各不相同”和“位置各不相同”的 woff 文件:

pic15
pic16

2. 在规则中随机调用这些字体文件。这里生成了 5 个随机 woff 文件 (实际字体坐标点信息不变),在生产环境甚至可以制作 1000 个或更多。规则如下:

pic17

3. 效果是,多次请求获取到不同的 woff,且价格数值显示正常:

pic18
pic19

这一措施是利用随机 woff 文件增加爬虫难度。对爬虫方来说,由于 woff 文件中的对应关系变为随机,原先的爬虫程序失效。

反爬方防护成功 (价格信息)

ROUND 7

爬虫方因防守方升级字体反爬规则,而原有脚本需要对每一个 woff 进行信息映射。如果运行原有脚本,会出现错误的价格数值,测试结果如下:

pic20

攻击方需加码升级爬虫脚本,根据“同一个字符其字体关键点的坐标是不变的”的逻辑爬取网页。爬取的代码文件如下:

pic21
多次访问验证效果,可以获取原有价格数值:

pic22

此时,爬虫方可以成功获取网页信息 (准确的价格信息)

反爬方由于升级后的字体反爬规则被破解,需要继续升级规则。思路:在已有的动态编码的基础上,追加一个动态字体坐标 (以不影响页面字体显示为前提,微调字体的坐标点,类似于验证码中字体的扭曲变形)。通过微调字体形状 (反映在坐标点上),网站可以做出 1000+ 种字体文件,本文测试中设置出 20 种 (0-19) 字体文件用于随机。

pic23
部署后访问页面,价格信息可以正常显示。而爬虫方由于字体本身信息被微调,脚本再次失效。

反爬方防护成功 (价格信息)

ROUND 8

爬虫方针对字体爬虫脚本的失效,可以发展出两种破解方案:一个是 OCR 技术 (Optical Character Recognition,光学字符识别),另一个是 KNN 算法 (K 近邻分类 K-Nearest Neighbor Classification)

此处使用 KNN 算法尝试破解。

1. 测试文件地址:

pic24
pic25

2. 首先,font.py 脚本从网站中下载 5 个随机 woff 文件,存储到 fonts 目录中,并修改 font.py 脚本中所代表的字体值 (根据下载的 woff 文件在 fontCreator 中的顺序修改),如下:

pic26

3. 而后,执行 python3 knn_test.py,看到预测率为 100%,如图:

pic27

4. 接下来,预测并替换原响应内容,得到正确内容:

pic28

可以看到,爬虫方成功获取了信息。

反爬方字体反爬规则对 KNN 算法失效。此时,在字体上的反爬措施也已经走到尽头。防守需转换阵地,对相关网页内容进行 js 混淆,使用 JavaScript-Obfuscator 开源项目进行加密测试。

在本文的测试环境下,通过 cli 命令对 handle_init_js.js 文件进行混淆:

javascript-obfuscator/bin/javascript-obfuscator handle_init_js.js -o a_out.js --compact false

pic29.png

由于网页信息做了 js 混淆处理,爬虫方需要先解密混淆的 js 代码。如果爬虫方无法解密混淆后的 js 代码,则网站反爬防护成功。

ROUND 9

爬虫方一旦其一步步吃透了混淆后的 js 代码,同样可以成功爬取网页信息。

反爬方需继续改变应对策略,增设图片反爬规则。

这里有两种方式:

方式一:将敏感文字信息以图片形式显示,并设置图片加扰等措施。效果和规则如下:

pic30
pic31

方式二:svg 图片与 css 样式偏移显示结合,进行反爬。效果和规则如下:

pic33
pic32

由于价格以图片显示,能爬取的只有图片链接和 html 标签信息,并不能直接获取价格信息。在爬虫方难以进一步做图片识别的情况下,可视为对网站进行了成功的反爬防护。

ROUND 10

爬虫方:上回合里,价格以图片方式呈现,爬虫方无法直接获取价格信息,故需要使用 OCR 技术来获取图片中的内容。

以 Tessarect(c++) 开源项目为例 (地址),安装命令为:

./autogen.sh && ./configure && make && make install && ldconfig
git clone https://github.com/tesseract-ocr/tessdata.git
export TESSDATA_PREFIX=/usr/local/pachong/ocr/tesseract/tessdata/

测试如图所示:

pic34

可以看到,图片内容被准确识别出来,爬虫成功获取到了价格信息。

反爬方:网页终究是提供信息供用户阅览的,当爬虫方使用 OCR 技术进行图片识别的时候,网站暂时是没有更好的办法进行反爬防护的。当然,还可以继续跟爬虫方在图片显示技术上进行对抗,譬如使用图片水印对原信息进行处理。网页终究是要呈现信息给用户的,当走到爬虫方使用 OCR 技术进行图片识别这一步时,网站暂时是没有更好的办法进一步反爬的。当然,还可以继续跟爬虫方在图片显示技术上进行对抗,譬如使用图片水印对原信息进行处理。

总结
正所谓,“道高一尺魔高一丈”,爬虫和反爬的对抗始终斗智斗勇,步步紧逼,相互间的博弈没有终点。

(张戈 | 天存信息)

posted @ 2021-08-24 14:48  天存信息  阅读(346)  评论(0编辑  收藏  举报