0. 思路来源
此文章仅用于技术交流
1. 什么是XX乐
XX乐是一种web端的网页反爬虫技术,其主要逻辑为对一个url连续请求三次,第一次为错误(状态码501)的请求,并且生成一个cookie,第二次同样为错误的请求,并且会携带此cookie发起请求,得到一段混淆的js代码,并且此代码运行后会生成最终的cookie,第三次携带此cookie即可获得正确的结果
2. 解题思路
-
- 发起第一次请求,获得cookie
-
- 携带cookie发起第二次请求
-
- 根据第二次请求获得的js代码运行后得到的cookie发起第三次请求
3. 思路
0x01 检查与分析
浏览器无痕窗口(无cookie)访问网址,通过F12检查三次请求
在第二次请求的时候携带了一个新的cookie:__jsl_clearance_s,这个cookie即第一次请求返回的结果。
在第三次请求的时候也携带了此cookie,但是与第二次的不一样,是通过第二次返回的JS代码运行后获取的。
0x02 第一次请求与分析
因涉及到cookie的变换,这里使用session较为合适
import requests, re, execjs
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
}
url = "https://xx.xxxxxx.com/search/?kw=%E6%84%9F%E5%86%92"
session = requests.session()
response_1 = session.get(url, headers=headers).text
如图所示为第一次运行后的结果:
返回的内容并不是全都需要,需要对结果进行处理
js_1 = re.findall('document.cookie=(.*?);loca', response_1)[0]
再通过第三方库execjs进行js的运行并且对结果进行处理并写入到session里
__jsl_clearance_s = execjs.eval(js_1).split(';')[0].split('=')[1]
session.cookies.set('__jsl_clearance_s', __jsl_clearance_s)
0x03 第二次请求与分析
按照上面的方式进行第二次的请求
response_2 = session.get(url=url, headers=headers).text
js_2 = re.findall('<script>(.*?)</script>', response_2)[0]
得到了一段比较长的js代码,其中的ha
值代表了加密方式,总共有三种:sha1、sha256、md5。每次的加密方式总是随机生成。
对返回的代码进行运行和分析,发现缺少环境,需要补充环境。
首先是补充最基本的window和document以及location环境,其次运行发现userAgent报错
在浏览器中进行调试。在源代码
-事件侦听器断点
-脚本
进行勾选,然后清理浏览器cookie重新请求。找到一直放行断点直至符合我们第二次请求返回的代码处,找到报错的位置进行输出查看
在控制台输出混淆的代码即可发现是window[‘navigator’][‘userAgent’]
只需要对window下的navigator下的userAgent进行补环境即可。
当我们补完了环境,运行后发现既不报错也无任何数据输出。检查代码发现核心代码是使用了setTimeout计时器,原理是此方法设置了一个定时器,一旦定时器到期,就会执行一个函数或指定的代码片段。可以使用一个简单的箭头函数进行自定义。
至此,我们的环境几乎补充完全,还需要考虑到两点:1. 这个js是Python请求获取到的,可以定义一个变量,在Python代码中把得到的JS代码和补环境中的变量进行替换即可使用。2. 我们运行了这个JS代码的目的是获得cookie,所以我们需要使用箭头函数定义一个变量来输出cookie。
补环境代码如下:
jscode = """window = global; window.navigator = {userAgent : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'}; document = {}; location = {}; let fn = () => document.cookie; setTimeout = (arg1, arg2) => arg1(); code_"""
当我们运行整合后的Python代码时可能会报错:
UnicodeEncodeError: 'gbk' codec can't encode character '\xe5' in position 14296: illegal multibyte sequence
如果遇到这个错误,我们只需要在代码最上方添加如下三行代码即可解决:
import subprocess
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
PS:这个问题出现的原因是使用了pyexecjs,此库已经不维护了,有许多编码问题,想解决编码问题可以通过
pip install pyexecjs2
下载新的库使用
再使用execjs对代码进行处理并写入session
js_final = execjs.compile(jscode.replace('code_', js_2)).call('fn').split(';')[0].split('=')[1]
session.cookies.set('__jsl_clearance_s', js_final)
0x04 第三次请求
第三次可正常请求,即可得出结果:
response_3 = session.get(url=url, headers=headers).text
4. 总体代码
import subprocess
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
import requests, re, execjs
jscode = """window = global; window.navigator = {userAgent : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'}; document = {}; location = {}; let fn = () => document.cookie; setTimeout = (arg1, arg2) => arg1(); code_"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
}
url = "https://xx.xxxxxx.com/search/?kw=%E6%84%9F%E5%86%92"
session = requests.session()
response_1 = session.get(url, headers=headers).text
js_1 = re.findall('document.cookie=(.*?);loca', response_1)[0]
__jsl_clearance_s = execjs.eval(js_1).split(';')[0].split('=')[1]
session.cookies.set('__jsl_clearance_s', __jsl_clearance_s)
# 第二次请求
response_2 = session.get(url=url, headers=headers).text
js_2 = re.findall('<script>(.*?)</script>', response_2)[0]
encrypt_type = re.findall('"ha":"(.*?)","is"', js_2)[0]
js_final = execjs.compile(jscode.replace('code_', js_2)).call('fn').split(';')[0].split('=')[1]
session.cookies.set('__jsl_clearance_s', js_final)
# 第三次请求
response_3 = session.get(url=url, headers=headers).text
print(response_3)