Websocket与js加密函数调用

1 minute read

背景

目前JS加密参数的使用越来越广泛且复杂,同时针对主流web自动化测试工具selenium和puppeteer的检测也已成熟且代码隐蔽。

以某条为例,14日更新的_signature参数较以往有很大的升级。加密参数的位置通过断点调试不难发现是window.byted_acrawler.sign,参数是{url: "https://www.xxxxxxxx.com/xxxxxxxx/c/user/article/?pa…45&count=20&as=A1C5BE7508C0710&cp=5E58C0F791B0AE1"}。但是想将该函数抽取出来需要做很多额外的工作。

但是如果利用浏览器环境,可以直接调用该函数。最直接的方法是使用selenium开启Chrome执行driver.execute_script('return window.byted_acrawler.sign({url: "https://www.xxxxxxxx.com/xxxxxxxx/c/user/article/?pa…45&count=20&as=A1C5BE7508C0710&cp=5E58C0F791B0AE1"})')。但是很神奇的发现,只要使用selenium,Chrome打开网站并不会加载feed流,生成的_signature也无效。

很明显,某条对selenium等自动化测试工具做了检测。隐去部分selenium特征后,feed流能正常加载,但是在执行JS后,又不能加载feed流,_signature无效。

这里不做JS的分析,提出另一种解决方式,websocket。应对JS检测selenium,主流的做法是通过中间人攻击的方式,修改目标网站的JS,但是定位代码耗时耗力。其实换一种角度,我们也可以通过加入JS,通过websocket实现浏览器和服务器的通信,服务器将加密函数参数传到浏览器,浏览器加载js后返回给服务器,实现完全不用selenium也可以操作浏览器。

下边给出实战代码,首先来看mitmproxy,中间人攻击的相关代码,使用mitmdump -p 8080 -s tt.py开启代理服务器。

# tt.py
import mitmproxy.http

inject = """var ws = new WebSocket('ws://localhost:8000/');
ws.onopen= function() {
    ws.send('browser started')
};
ws.onmessage= function(evt) {
    ws.send(window.byted_acrawler.sign({url: evt.data}));
};
"""

class TT(object):
    def response(self, flow: mitmproxy.http.HTTPFlow):
        if 'acrawler.js' in flow.request.url:
                flow.response.text = inject + flow.response.text
                print('inject success')


addons = [
    TT()
]

另外在服务端,起个websocket服务

import asyncio
import websockets

async def send(websocket, path):
    while True:
        await websocket.send("https://www.xxxxxxxx.com/xxxxxxxx/c/user/article/?page_type=1&user_id=5954781019&max_behot_time=1582778326&count=20&as=A1E58E3517D5422&cp=5E5745D432429E1")
        await asyncio.sleep(3)
        print("url sent")

asyncio.get_event_loop().run_until_complete(
    websockets.serve(send, 'localhost', 8000))
asyncio.get_event_loop().run_forever()

最后启动chrome浏览器即可

./Chromium http://www.xxxxxxxx.com --proxy-server=127.0.0.1:8080 --ignore-certificate-errors

浏览器启动后会执行注入JS,与服务器建立连接。接收到server传入的参数,计算出加密参数后,返回给server。

另外,有些情况下加密函数不是全局函数,如某瓜视频。应对方式也很简单:

  1. 在加密函数出现的位置注入websocket连接,即可获取到加密函数和执行环境。
  2. 新建一个全局变量,将加密函数赋值给该全局变量。

其实还有一个改进点,我们可以将页面全部下载到本地,直接修改本地的JS文件,然后用浏览器打开本地的html即可,省去了使用mitmproxy。

全文完~

Updated: