一、确定爬取目标
今日受害者网站:https://jzsc.mohurd.gov.cn/data/company
我们的目标是爬取该网站列表页当中的企业信息
二、数据抓包分析
首先刷新网页进行抓包,观察一下数据在哪个数据包当中被返回(获取返回数据)
结果发现返回的都是一串看不懂的密文
搜索数据包中的文字也没有找到任何信息
这说明我们收到的响应数据是加密后的结果,返回客户端之后由客户端解密拿到明文字符串展示在前端当中,所以我们才能在网页看到有效的信息
因此现在就需要对返回的响应数据进行解密(decrypt)
三、js逆向分析
我们直接搜索decrypt,在所有可疑的位置打上断点
注意:像css/axios//jquery/react这种可以直接pass;但是app/chunk不能pass
另外一个js文件也不能放过
然后我们重新刷新网页,来再次发送请求
这样就定位到了解密的关键位置,不难发现是使用AES-CBC模式进行的加密解密
此外还有2处断点是具体的加密细节,可以直接忽略(取消断点)
既然是AEC-CBC进行加密解密
那么我们就重点关注3个参数:密文(n),密钥(f),iv(m)
a = d.a.AES.decrypt(n, f, {
iv: m,
mode: d.a.mode.CBC,
padding: d.a.pad.Pkcs7
}
往上观察发现密钥和iv都是直接静态给出了
但是我们在使用之前需要验证一下值是否正确
结果发现密钥的值竟然和js代码当中给的不一致!
我们往下翻,原来是在执行解密之前key的值被重新修改了.......
看来还是得小心点留个心眼啊😂😂😂
这样数据解密的步骤我们已经分析完了,现在还有一个问题就是我怎么知道返回的7个XHR数据包哪一个是列表页展示的、我们希望得到的目标数据呢?
其实很简单.....
法一:你可以一个个爬取尝试(排除法)
而且几个过小/过大的文件肯定不是,剩下几个慢慢排除即可
法二:
在调试窗口观察到目标数据之后,点击网络面板,观察哪个数据接口返回了数据
其实根据名字也能猜个大概,因为我们想要要获取列表页数据,那么list?pg=0,pgsz=15就显得很可疑了😂😂
现在我们还需要确认一件事,就是这个网站使用的AES-CBC是不是标准的AES算法,这将决定我们需不需要扣代码
不妨在在线加密解密网站测试一下
能够正确解密,说明使用的是标准的AES算法
而且通过观察,js代码没有特别需要扣的地方,都是使用的库当中自带函数
function b(t) {
var e = d.a.enc.Hex.parse(t)
, n = d.a.enc.Base64.stringify(e)
, a = d.a.AES.decrypt(n, f, {
iv: m,
mode: d.a.mode.CBC,
padding: d.a.pad.Pkcs7
})
, r = a.toString(d.a.enc.Utf8);
return r.toString()
}
四、代码编写
下面就是愉快的编码环节了
第一步是正确拿到返回的加密数据
import execjs
from loguru import logger
import requests
headers = {
"Connection": "keep-alive",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"accessToken": "",
"timeout": "30000",
"sec-ch-ua-mobile": "?0",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Core/1.94.253.400 QQBrowser/12.6.5678.400",
"v": "231012",
"Accept": "application/json, text/plain, */*",
"sec-ch-ua-platform": "^\\^Windows^^",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "https://jzsc.mohurd.gov.cn/data/company",
"Accept-Language": "zh-CN,zh;q=0.9"
}
url = "https://jzsc.mohurd.gov.cn/APi/webApi/dataservice/query/comp/list"
params = {
"pg": "0",
"pgsz": "15",
"total": "0"
}
response = requests.get(url, headers=headers, params=params)
print(response.text)
print(response)
然后在vscode当中脱离浏览器环境编写解密的js代码,并完成测试
var CryptoJS = require('crypto-js');
function decrypt_aes_cbc(arg_key,arg_iv,arg_text){
// utf-8串转word数组
var key = CryptoJS.enc.Utf8.parse(arg_key);
// 16进制数转word数组
var e = CryptoJS.enc.Hex.parse(arg_text);
// 将e转为base64字符串
var n = CryptoJS.enc.Base64.stringify(e);
var a = CryptoJS.AES.decrypt(n, key, {
iv: CryptoJS.enc.Utf8.parse(arg_iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return a.toString(CryptoJS.enc.Utf8).toString()
}
//测试
//console.log(decrypt_aes_cbc('Dt8j9wGw%6HbxfFn','0123456789ABCDEF',"5588a9e126c91a28cc2f6813e37933690edae8a89a2a40f9ee87399a0fa435be5ec9333788e282e9b9cf6c5b7b8d92dcf0f09279c62908f4d6ce72a06dd3e9f2"))
最后在pycharm当中调用js代码即可
with open('aes_cbc.js','r',encoding='utf-8') as f:
ctx = execjs.compile(f.read())
decry_data = ctx.call('decrypt_aes_cbc','Dt8j9wGw%6HbxfFn','0123456789ABCDEF',response_string)
print("解密后的数据为:",decry_data)
结果报错:
1)UnicodeDecodeError: 'gbk' codec can't decode byte 0xac in position 62: illegal multibyte sequence
2)AttributeError: 'NoneType' object has no attribute 'replace'
为此只需要在execjs库的上面添加如下代码,确保js代码执行时编码环境为utf-8:
import subprocess
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
再次运行,顺利拿到解密后的列表数据
完整代码:
import subprocess
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
import execjs
import requests
headers = {
"Connection": "keep-alive",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"accessToken": "",
"timeout": "30000",
"sec-ch-ua-mobile": "?0",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Core/1.94.253.400 QQBrowser/12.6.5678.400",
"v": "231012",
"Accept": "application/json, text/plain, */*",
"sec-ch-ua-platform": "^\\^Windows^^",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "https://jzsc.mohurd.gov.cn/data/company",
"Accept-Language": "zh-CN,zh;q=0.9"
}
url = "https://jzsc.mohurd.gov.cn/APi/webApi/dataservice/query/comp/list"
params = {
"pg": "0",
"pgsz": "15",
"total": "0"
}
response = requests.get(url, headers=headers, params=params)
response_string = response.text
# print(response.text)
# print(response)
with open('aes_cbc.js','r',encoding='utf-8') as f:
ctx = execjs.compile(f.read())
decry_data = ctx.call('decrypt_aes_cbc','Dt8j9wGw%6HbxfFn','0123456789ABCDEF',response_string)
print("解密后的数据为:",decry_data)
ok,今天的你就到此为止吧,我们下期再见~