反爬(九)

反爬九

目标网站,本次破解的网站是一个信用地方网站,主要获取的是行政处罚数据,该网站的混用了DES3、RSA、SHA256和MD5加密,比较具有代表性,但是对于有经验的爬虫工程师来说本身难度不高,如果缺乏经验可能会多走一些弯路。

查看网络请求可以发现其post请求的参数被加密了

通过查看网络请求的调用栈(过滤掉jquery等类似请求),可以定位到这个地方

通过分析,可以知道post参数是这样的

1
{"q": "", "xydm": "", "time": "", "pageStart": 1, "pageSize": 10, "sign": "", "salt": ""}

而其整体加密逻辑是:

  1. 先通过加密算法生成sign;
  2. 再通过加密算法生成salt;
  3. 最后通过加密算法把整个post参数加密。

现在需要解决的就是上面三步的加密逻辑,抠出其加密的代码或者知道其使用的加密算法,找到其密钥自行加解密。

解决sign参数

sign参数是其上图的中这行代码生成的:
var _0x299f7d = sort['getSign'](_0x58338f, _0x5a36f2)

_0x58338f的值为:”pageSize=10&pageStart=2&q=&time=&xydm=”

_0x5a36f2的值为:”1670250539180”
不难推想,sort[‘getSign’]是一个加密函数,其两个参数根据我们的实际请求而定,例如第一个参数中pageStart需要每次改变,第二个参数为发送请求时的时间戳。

可以定位到加密函数

这里就是把”pageSize=10&pageStart=2&q=&time=&xydm=”和”&key=”和”1670250539180”拼接在一起传入SHA256(),如果没有听说过SHA256的同学,可能就会进入这个方法体进一步深究(那样就会多绕很多弯路),而实际上SHA256是一种哈希算法(与MD5类似),在python中调用方法也和MD5相似。

1
2
3
import hashlib

hashlib.sha256(text.encode('utf-8')).hexdigest()

这里可以自己实验下,使用这一方法哈希得到的值是否与该网站得到的值一致。

解决salt参数

salt参数是这行代码生成的
_0x493352[_0x22d55e['QbcoC']] = sort[_0x6227('0xb', 'Etzi')](_0x5a36f2);

_0x22d55e[‘QbcoC’]的值为salt,_0x5a36f2上面也说了是一个时间戳,主要是通过sort[_0x6227(‘0xb’, ‘Etzi’)]方法加密,进入到方法体内部:

这里面的逻辑也不难,稍微看下都能看懂,但是缺乏经验的话,也会继续深入到更深层次的方法体中,而如果对RSA加密算法比较熟悉的同学,通过一定的细节即可推断出这里使用的RSA加密算法。

这里把_0x70805e变量设为公钥(setPublicKey),而这个变量的值为:
“MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMdek/OvlEBhsSfIoDEbnwUPEwn3WbI+WY4O/0nrDnL8DHkNdeZytcfhjX4nV713FYKe7EeEDQVmRlU2e//h/eECAwEAAQ==”
很容易让人想到RSA加密算法的公钥,因为RSA算法是非对称加密,所以其公钥和秘钥长度比较长,虽然这里没有”—–BEGIN PUBLIC KEY—–\n”这个典型标志。

如果能往RSA算法方面想,就可以尝试下嘛,说不定就成功了。
另外要注意,这里秘钥不完整,需要在首先加上RSA秘钥的典型标志,否则加密时会提醒秘钥格式不正确。

整体post参数加密

上面两步完成之后,可以很清晰的看到是将post参数传入了DES3加密:
var _0x50c5be = DES3['encrypt'](JSON[_0x6227('0xf', 'hp9#')](_0x493352));
写的这么清晰,想必同学都不会再绕啥弯路了,只要拿到秘钥,mode和偏移即可。


可以看到是CBC模式,iv=b’12345678’,秘钥这里是动态生成的,主要在getkey()方法中实现。


我们仔细分析这段代码,并转化为可读性更高的代码

1
2
3
4
5
6
7
8
9
10
11
hex_md5(
CryptoJS['enc']["Base64"]["stringify"](
CryptoJS["enc"]["Utf8"]['parse'](
_0x21915d[_0x6227('0x67', ')BHH')](
"20221205", "0B7E7DEE87B1C3B98E72131173DFBBBF"
)
)
)
)["toUpperCase"]();

_0x21915d[_0x6227('0x67', ')BHH')] 把传入来的两个参数拼接在一起

根据此,我们可以写出这样的python代码来获取key

1
2
3
4
5
6
7
8
9
from hashlib import md5

def get_des_key():
# 获取当天日期
date = Tools.days_before(0).replace('-', '')
secret_key = "0B7E7DEE87B1C3B98E72131173DFBBBF"
b64_bytes = base64.b64encode((date+secret_key).encode('utf-8'))
key = md5(b64_bytes).hexdigest()
return bytes(key.upper().encode('utf-8'))

最后需要注意的是,进行DES3加密之前需要把post的参数转为字符串,可以使用json.dumps,但是要注意python中会有多余空格的问题,可以这样处理

1
json.dumps(message, ensure_ascii=False).replace(' ', '')

message为post参数,为dict类型。

关于DES3、RSA加密算法的实现,在其他文章中都有介绍。