一、数据接口分析
主页地址:某税网
1、抓包
通过抓包可以发现登录接口是factorAccountLogin
2、判断是否有加密参数
- 请求参数是否加密?
通过查看载荷模块可以发现有一个datagram
和 一个signature
加密参数
- 请求头是否加密?
通过查看“标头”模块可以发现,请求头中有Deviceidentyno
、X-App-Clientid
、X-Temp-Info
三个加密参数
- 响应是否加密?
登录失败是无加密,登录成功时datagram
是加密参数 - cookie是否加密?
无
二、加密位置定位
1、加密参数以及部分请求头
(1)看启动器
查看启动器发现里面包含异步,所以无法正确找到加密位置
(2)搜索关键字
通过搜索关键字datagram=
可以发现在请求拦截器中会对datagram
进行赋值,并且此处还有signature
参数以及部分请求头,所以此处大概率是加密位置。
在此处下断点,发现可以断住,并且生成位置就在上方,所以此处就是加密位置
2、请求头Deviceidentyno
搜索关键字
通过搜索关键字Deviceidentyno
可以找到设置请求头的位置。
3、响应解密
因为定位到的加密位置是在请求拦截器中,所以解密位置大概率会在响应拦截器中,我们可以在请求拦截器的js文件中搜索interceptors.response
就可以找到响应拦截器的位置。并且可以发现,在响应拦截器中确实有解密操作。
三、扣js代码
1、本地存储
我们在扣js代码时,可以发现网站会从本地缓存中取出一些数据,但是其中只有new_key16
、clientId
和natureuuid
是有值的,其他都是空。同时我们可以看到,网站将new_key16
取出的值赋值给了a
,然后在下方代码中,又判断a
是否为空,如果为空就使用Object(A["b"])()
方法生成,所以我们就可以直接将这个生成方法扣出,使用这个方法生成。clientId
仔细观察可以发现,这个值是固定不变的,可以写死。所以我们只需要关心natureuuid
即可。
进入控制台的“应用”标签页中的“本地存储空间”,将本地存储全部清除
然后对localStorage.setItem
进行hook
hook代码:
var my_setItem = localStorage.setItem;
localStorage.setItem = function (key, value) {
if (key == 'natureuuid'){
debugger
}
return my_setItem.call(localStorage, key, value);
};
运行hook代码,再次点击登录,发现可以断住
接着调试执行,可以发现设置natureuuid
的位置,同时可以看出这段代码是在回调中执行的。
再次观察发包可以发现,网站是请求了一个getPublicKey
接口,返回了uuid
和publicKey
,虽然目前来看我们没有用到publicKey
,但是肯定是有用到的地方,所以我们也先保存一下。
2、请求流程
当我们将本地存储清空之后,再次登录可以发现,网站是发送了三个请求,先请求了getPublicKey和sendSm4这两个接口之后才请求了登录接口。
所以我们在请求登录之前也需要先请求这两个接口。
观察这两个接口的“载荷”,可以发现这两个接口携带的参数与登录接口是一样的,只不过其中的数据不同,同时,网站在请求前两个接口时,也会经过我们上面定位出来的加密位置,只不过datagram
的生成不太一样,前两个接口只是转成了json字符串,只有登录接口才加密。
在观察前两个接口的发包过程时,可以发现第一个接口getPublicKey的datagram就只是一个空字典,然后转成json字符串。但是,网站在给sendSm4
接口发包时,是有数据的,而且还有一个我们不知道的参数secret
。
我们通过搜索关键字的方式,搜索secret:
就可以找到生成位置了,同时这个参数生成的时候还用到了之前通过请求getPublicKey接口获取到的publicKey
。
3、坑
在对这三个请求发包时,使用的new_key16
以及deviceIdentyNo
的值都要使用同一个。
4、加密算法
通过扣js代码可以发现,网站使用了sm2、sm4以及HmacSHA256三种加密算法,解密时使用的是sm4解密,而且这些算法都是标准算法,所以我们可以直接使用标准模块进行加解密。
四、源代码
JavaScript源码:
const {sm2, sm4} = require("sm-crypto");
const CryptoJS = require("crypto-js");
function func_i(e, t) {
var n, a, r = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""), i = [];
if (t = t || r.length,
e)
for (n = 0; n < e; n++)
i[n] = r[0 | Math.random() * t];
else
for (i[8] = i[13] = i[18] = i[23] = "-",
i[14] = "4",
n = 0; n < 36; n++)
i[n] || (a = 0 | 16 * Math.random(),
i[n] = r[19 == n ? 3 & a | 8 : a]);
return i.join("")
}
function object_A_d(e, t) {
r = sm4.encrypt(e, t);
return r
}
function object_A_i(e) {
for (var t = "", n = 0; n < e.length; n++)
"" === t ? t = e.charCodeAt(n).toString(16) : t += e.charCodeAt(n).toString(16);
return t
}
function object_A_a(e, t) {
var n = CryptoJS.HmacSHA256(e, t).toString();
return n
}
function object_A_e(e, t) {
r = sm4.decrypt(e, t);
return r
}
function padZero(num) {
return num < 10 ? "0" + num : num;
}
var clientId = 's44fftt3bc634tcab4teasbaasba7ft4'
function get_headers(natureuuid, ded) {
if (!ded) {
ded = func_i(32)
}
var t = {
headers: {},
}
t.headers["deviceIdentyNo"] = ded;
t.headers["X-APP-CLIENTID"] = clientId
if (natureuuid) {
t.headers["X-TEMP-INFO"] = natureuuid
}
return [t.headers, ded]
}
var v = 'GwdK^R4q'
function get_params(t_data, is_get_key, g) {
if (!g) {
g = func_i(16, 61)
}
var currentDate = new Date();
var formattedDate = currentDate.getFullYear() +
padZero(currentDate.getMonth() + 1) +
padZero(currentDate.getDate()) +
padZero(currentDate.getHours()) +
padZero(currentDate.getMinutes()) +
padZero(currentDate.getSeconds());
c = {}
p = g.substring(0, 8) + v
u = ""
c["zipCode"] = "0"
if (is_get_key) {
c["encryptCode"] = "0";
c.datagram = JSON.stringify(t_data);
} else {
f = JSON.stringify(t_data);
u = object_A_d(f, object_A_i(p));
c.datagram = u;
c["encryptCode"] = "2";
}
c["timestamp"] = formattedDate
c["access_token"] = ""
c["signtype"] = "HMacSHA256"
c["signature"] = object_A_a(c["zipCode"] + c["encryptCode"] + u + c["timestamp"] + c["signtype"], g)
return [c, g]
}
function func_l(e, t, a) {
if (t) {
var i = sm2.doEncrypt(e, t, a);
return i
}
return ""
}
function get_secret(pubkey, g) {
new_key16 = g
secret = func_l(new_key16, pubkey, 1)
return secret
}
function get_data(g, datagram) {
var a = g.substring(0, 8) + v;
return object_A_e(datagram, object_A_i(a))
}
Python源码:
"""
Author:陈帅超
Email:912917367@qq.com
Date: 2023/8/29 16:25
"""
import json
import execjs
import requests
class Spider:
def __init__(self):
self.session = requests.session()
self.session.headers = {
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Authorization": "",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/json",
"Origin": "https://tpass.jiangsu.chinatax.gov.cn:8443",
"Pragma": "no-cache",
"Referer": "https://tpass.jiangsu.chinatax.gov.cn:8443/",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
"X-LANG-ID": "null",
"X-NATURE-IP": "",
"X-SM4-INFO": "0",
"X-TICKET-ID": "null",
"hUid": "",
"sec-ch-ua": "^\\^Chromium^^;v=^\\^116^^, ^\\^Not)A;Brand^^;v=^\\^24^^, ^\\^Google",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "^\\^Windows^^"
}
with open('reverse.js', 'r', encoding='utf-8') as f:
self.js_obj = execjs.compile(f.read())
self.uuid = ''
self.publicKey = ''
self.g = ''
self.ded = ''
def get_public_key(self):
url = "https://tpass.jiangsu.chinatax.gov.cn:8443/sys-api/v1.0/auth/oauth2/getPublicKey"
headers2 = self.js_obj.call('get_headers', self.uuid)
self.ded = headers2[0]
self.session.headers = headers2[0] | self.session.headers
data = self.js_obj.call('get_params', {}, True)
self.g = data[1]
response = self.session.post(url, json=data[0])
datagram = json.loads(response.json()['datagram'])
self.uuid = datagram['uuid']
self.publicKey = datagram['publicKey']
print('uuid:', self.uuid)
print('publicKey:', self.publicKey)
def send_sm4(self):
url = "https://tpass.jiangsu.chinatax.gov.cn:8443/sys-api/v1.0/auth/white/sendSm4"
headers2 = self.js_obj.call('get_headers', self.uuid, self.ded)
self.session.headers = headers2[0] | self.session.headers
secret = self.js_obj.call('get_secret', self.publicKey, self.g)
data = self.js_obj.call('get_params', {'uuid': self.uuid, 'secret': secret}, True, self.g)
response = self.session.post(url, json=data[0])
print(response.text)
print(response)
def login(self):
t_data = {
"client_id": 's44fftt3bc634tcab4teasbaasba7ft4',
"account": '登录用户名',
"password": '登录密码',
"redirect_uri": "https://etax.jiangsu.chinatax.gov.cn/sso/kxLogin/authorize",
"creditCode": '税号'
}
url = "https://tpass.jiangsu.chinatax.gov.cn:8443/sys-api/v1.0/auth/enterprise/quick/factorAccountLogin"
headers2 = self.js_obj.call('get_headers', self.uuid, self.ded)
self.session.headers = headers2[0] | self.session.headers
data = self.js_obj.call('get_params', t_data, False, self.g)
response = self.session.post(url, json=data[0])
response_data = self.js_obj.call('get_data', self.g, response.json()['datagram'])
print(response_data)
if __name__ == '__main__':
s = Spider()
s.get_public_key()
s.send_sm4()
s.login()