网站:https://user.qunar.com/passport/login.jsp
用爬虫模拟完成滑块验证+获取验证码并登录
1)抓包可得在发送验证码前需要先完成滑动验证并获取slideToken
2)分析滑块验证码的请求包
可见请求体中的data需要进行某种算法加密,而其他三个参数为定值,接下来我们就需要找到这个算法并用python代码模拟加密过程。
3)查看调用程序并获取算法
在调用程序中可以看到传递的data的值为a,而a = t.encryption()。
搜索encryption可得该加密算法先对sliderInfo进行序列化,后采用AES算法进行加密
在此处打个断点,重新完成滑块验证即可得到sliderInfo的信息:
acc: []
deviceMotion: [DeviceOrientationEvent, DeviceOrientationEvent, DeviceOrientationEvent, DeviceOrientationEvent, DeviceOrientationEvent, DeviceOrientationEvent, DeviceOrientationEvent, DeviceOrientationEvent]
endTime: 1707832186251
openTime: 1707832184833
ori: []
startTime: 1707832186072
track: ['86143;143.00;334.00;1.00', '86162;151.00;338.00;9.00', '86182;173.00;343.00;31.00', '86202;228.00;347.00;86.00', '86223;336.00;349.00;194.00', '86243;508.00;349.00;366.00']
uid: "0001028025405c0d0a609b28"
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
uid似乎需要动态获取
4)获取uid
搜索t.sliderInfo查找uid的来源,可见uid = h(“QN1”)
跳转到函数h可得,uid似乎在cookie中
查看cookie可得uid就是cookie中的QN1,因此,我们需要先模拟访问登录界面并获取到QN1的值。
def aes_encrypt(data_string):
# key = "227V2xYeHTARSh1R".encode('utf-8')
key_string = "32323756327859654854415253683152"
key = binascii.a2b_hex(key_string)
aes = AES.new(
key=key,
mode=AES.MODE_ECB
)
raw = pad(data_string.encode('utf-8'), 16)
aes_bytes = aes.encrypt(raw)
res_string = base64.b64encode(aes_bytes).decode('utf-8')
return res_string
res = requests.get("https://user.qunar.com/passport/login.jsp")
cookie_dict = res.cookies.get_dict()
cookie_qn1 = cookie_dict['QN1']
sliderInfo = {
"openTime": int((time.time() - random.randint(500, 3000)) * 1000),
"startTime": int((time.time() - random.uniform(2, 4)) * 1000),
"endTime": int((time.time() - random.uniform(0, 1)) * 1000),
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"uid": cookie_qn1,
"track": ["95339;72.00;241.00;1.00", "95359;104.00;241.00;33.00", "95380;195.00;241.00;124.00",
"95400;358.00;249.00;287.00"],
"acc": [],
"ori": [],
"deviceMotion": [{"isTrusted": True}, {"isTrusted": True}, {"isTrusted": True}]
}
data_string = json.dumps(sliderInfo, separators=(',', ':'))
data = aes_encrypt(data_string)
res1 = requests.post(
url="https://vercode.qunar.com/inner/captcha/snapshot",
json={
"appCode": "register_pc",
"cs": "pc",
"data": data,
"orca": 2
},
cookies=cookie_dict
)
res1 = json.loads(res1.text)
print(res1)
在滑块验证通过后的都的”cst“的值即为sildeToken,但还有bella的值需要获取
1)寻找bella
点开调用程序,无法确定bella的来源,因此我们直接在代码中搜bella,查看bella在哪里被赋值
可见bella被赋值为f(若不确定,可以在此处打个断点,查看此处f的值是否与请求中的bella值相同),而f似乎为一个全局函数
2)模拟获取bella的值
跳转到Bella所在的js文件,将该文件全部copy到我们自己目录下的js文件(共17000多行,但不要慌)
创建一个html文件,先模板在网页中获取bella
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js">script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js">script>
head>
<body>
<script src="sdk.js">script> # 引入copy过来的js文件
<script>
console.log(window.Bella({slideToken: "654b163304abc177cb360da79562b1f7"}));
script>
body>
html>
打开浏览器,即可在控制台中看到bella的值与我们需要的似乎一致
3)用python代码获取bella
用python调用node执行js代码时需要先下载相关依赖并在js文件中添加浏览器环境
npm install -g jsdom
npm install -g node-gyp
npm install -g canvas --canvas_binary_host_mirror=https://npm.taobao.org/mirrors/canvas/
const jsdom = require("jsdom");
const {JSDOM} = jsdom;
XMLHttpRequest = function () {
return {
open: function () {
},
send: function () {
},
onreadystatechange: function () {
}
};
}; // 若报XMLHttpRequest相关的错误需要在此处继续添加相关函数
const html = `Hello world
`;
const dom = new JSDOM(html, {
url: "https://user.qunar.com/passport/login.jsp",
referrer: "https://www.qunar.com/",
contentType: "text/html"
});
document = dom.window.document;
window = global;
Object.assign(global, {
location: {
hash: "",
host: "user.qunar.com",
hostname: "user.qunar.com",
href: "https://user.qunar.com/passport/login.jsp",
origin: "https://user.qunar.com",
pathname: "/passport/login.jsp",
port: "",
protocol: "https:",
search: "",
},
navigator: {
appCodeName: "Mozilla",
appName: "Netscape",
appVersion: "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
cookieEnabled: true,
deviceMemory: 8,
doNotTrack: null,
hardwareConcurrency: 4,
language: "zh-CN",
languages: ["zh-CN", "zh"],
maxTouchPoints: 0,
onLine: true,
platform: "MacIntel",
product: "Gecko",
productSub: "20030107",
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
vendor: "Google Inc.",
vendorSub: "",
webdriver: false
}
});
在配置完环境后执行测试代码
import subprocess
res = subprocess.check_output('node sdk.js', shell=True)
data_string = res.decode('utf-8')
print(data_string)
会发现程序卡住无法获取结果,此时我们猜测可能是因为我们缺少js文件中的某些数据导致程序无法执行完,但缺少的数据与我们需要的Bella函数无关,我们阻止其执行即可
我们搜索bella可得,在9804行时bella就已经被赋值(_0x47bb39为一个函数),因此无需执行后面的代码。
再次执行测试代码,即可发现能够正常执行完成并拿到bella值
import subprocess
res = subprocess.check_output('node sdk.js eeb08c57bf8fc82f5c9fd5163293cf38', shell=True)
data_string = res.decode('utf-8')
print(data_string)
import subprocess
token = res1['data']['cst']
res = subprocess.check_output(f'node sdk.js {token}', shell=True)
data_string = res.decode('utf-8').strip()
res2 = requests.post(
url="https://user.qunar.com/weblogin/sendLoginCode",
data={
"mobile": "自己的手机号",
"prenum": "86",
"loginSource": '1',
"slideToken": token,
"smsType": "0",
"appcode": "register_pc",
"bella": data_string
},
cookies=cookie_dict
)
print(res2.text)
import base64
import binascii
import json
import random
import subprocess
import time
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
def aes_encrypt(data_string):
# key = "227V2xYeHTARSh1R".encode('utf-8')
key_string = "32323756327859654854415253683152"
key = binascii.a2b_hex(key_string)
aes = AES.new(
key=key,
mode=AES.MODE_ECB
)
raw = pad(data_string.encode('utf-8'), 16)
aes_bytes = aes.encrypt(raw)
res_string = base64.b64encode(aes_bytes).decode('utf-8')
return res_string
res = requests.get("https://user.qunar.com/passport/login.jsp")
cookie_dict = res.cookies.get_dict()
cookie_qn1 = cookie_dict['QN1']
sliderInfo = {
"openTime": int((time.time() - random.randint(500, 3000)) * 1000),
"startTime": int((time.time() - random.uniform(2, 4)) * 1000),
"endTime": int((time.time() - random.uniform(0, 1)) * 1000),
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"uid": cookie_qn1,
"track": ["95339;72.00;241.00;1.00", "95359;104.00;241.00;33.00", "95380;195.00;241.00;124.00",
"95400;358.00;249.00;287.00"],
"acc": [],
"ori": [],
"deviceMotion": [{"isTrusted": True}, {"isTrusted": True}, {"isTrusted": True}]
}
data_string = json.dumps(sliderInfo, separators=(',', ':'))
data = aes_encrypt(data_string)
res1 = requests.post(
url="https://vercode.qunar.com/inner/captcha/snapshot",
json={
"appCode": "register_pc",
"cs": "pc",
"data": data,
"orca": 2
},
cookies=cookie_dict
)
res1 = json.loads(res1.text)
token = res1['data']['cst']
res = subprocess.check_output(f'node sdk.js {token}', shell=True)
data_string = res.decode('utf-8').strip()
res2 = requests.post(
url="https://user.qunar.com/weblogin/sendLoginCode",
data={
"mobile": "13720824626",
"prenum": "86",
"loginSource": '1',
"slideToken": token,
"smsType": "0",
"appcode": "register_pc",
"bella": data_string
},
cookies=cookie_dict
)
print(res2.text)
此时我们的手机就能收到验证码了,之后将验证码携带到登录请求中即可完成登录
import base64
import binascii
import json
import random
import subprocess
import time
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
def aes_encrypt(data_string):
# key = "227V2xYeHTARSh1R".encode('utf-8')
key_string = "32323756327859654854415253683152"
key = binascii.a2b_hex(key_string)
aes = AES.new(
key=key,
mode=AES.MODE_ECB
)
raw = pad(data_string.encode('utf-8'), 16)
aes_bytes = aes.encrypt(raw)
res_string = base64.b64encode(aes_bytes).decode('utf-8')
return res_string
res = requests.get("https://user.qunar.com/passport/login.jsp")
cookie_dict = res.cookies.get_dict()
cookie_qn1 = cookie_dict['QN1']
sliderInfo = {
"openTime": int((time.time() - random.randint(500, 3000)) * 1000),
"startTime": int((time.time() - random.uniform(2, 4)) * 1000),
"endTime": int((time.time() - random.uniform(0, 1)) * 1000),
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"uid": cookie_qn1,
"track": ["95339;72.00;241.00;1.00", "95359;104.00;241.00;33.00", "95380;195.00;241.00;124.00",
"95400;358.00;249.00;287.00"],
"acc": [],
"ori": [],
"deviceMotion": [{"isTrusted": True}, {"isTrusted": True}, {"isTrusted": True}]
}
data_string = json.dumps(sliderInfo, separators=(',', ':'))
data = aes_encrypt(data_string)
res1 = requests.post(
url="https://vercode.qunar.com/inner/captcha/snapshot",
json={
"appCode": "register_pc",
"cs": "pc",
"data": data,
"orca": 2
},
cookies=cookie_dict
)
res1 = json.loads(res1.text)
token = res1['data']['cst']
res = subprocess.check_output(f'node sdk.js {token}', shell=True)
data_string = res.decode('utf-8').strip()
mobile = input("请输入手机号:")
res2 = requests.post(
url="https://user.qunar.com/weblogin/sendLoginCode",
data={
"mobile": mobile,
"prenum": "86",
"loginSource": '1',
"slideToken": token,
"smsType": "0",
"appcode": "register_pc",
"bella": data_string
},
cookies=cookie_dict
)
if (res2.json()['errcode'] == 200):
print("验证码发送成功")
sms_code = input("请输入短信验证码:")
res3 = requests.post(
url="https://user.qunar.com/weblogin/verifyMobileVcode",
json={
"piccoloT": "login_register_pc",
"mobile": mobile,
"prenum": "86",
"vcode": sms_code,
"type": "3",
"slideToken": token,
"appcode": "register_pc",
"loginSource": 1,
},
cookies=cookie_dict
)
print(res3.text)
else:
print("验证码发送失败")
登录结果: