0x01 前言 在滲透測試中,前端加密的請求數(shù)據(jù)常常增加分析難度,傳統(tǒng)的手動逆向加解密效率低下且耗時。JsRpc通過WebSocket與客戶端通信,結(jié)合Burp Suite的插件(如Galaxy)或代理工具(如mitmproxy),可實現(xiàn)請求與響應的自動加解密,極大提升測試效率。本文整合兩篇教程,詳細解析JsRpc與Burp Suite聯(lián)動的實現(xiàn)原理與步驟,涵蓋環(huán)境搭建、函數(shù)注冊、腳本配置及實戰(zhàn)效果展示,幫助安全從業(yè)者快速掌握自動化加解密技術(shù)。 練手地址: https://github.com/0ctDay/encrypt-decrypt-vuls JsRpc:https://github.com/jxhczhl/JsRpc 參考文章:https://xz.aliyun.com/news/16886 https://xz.aliyun.com/news/14689 現(xiàn)在只對常讀和星標的公眾號才展示大圖推送,建議大家把滲透安全HackTwo“設為星標”,否則可能就看不到了啦! 末尾可領(lǐng)取挖洞資料文件 #滲透安全HackTwo 0x02 漏洞詳情 JsRpc基本使用首先觀察一下原始的數(shù)據(jù)包![]() 可以看到除了請求體需要解密,還有請求頭中的 ![]() ![]() 可以看到就是圖中的幾個地方處理的,具體邏輯就不分析了,主要是學會使用jsrpc 首先把這幾個函數(shù)先提升到全局,需要先斷點讓其加載到作用域 ![]() 然后在控制臺執(zhí)行下面語句提升至全局作用域
![]() 然后注入jsrpc中的js文件 ![]() 然后啟動jsrpc服務端 ![]() 再客戶端連接 var demo = new Hlclient('ws://127.0.0.1:12080/ws?group=zzz'); ![]() 連接成功后服務端會提示新上線….然后就是將函數(shù)注冊進去,即我們要怎么處理值
上面是加密,下面是解密 demo.regAction('decode', function (resolve,param) { //這樣添加了一個param參數(shù),http接口帶上它,這里就能獲得 var response = d(param) resolve(response); }) ![]() 此時就可以測試一下jsrpc處理是否能成功 ![]()
可以看到加密是沒有任何問題的, ![]() 也是能夠正常處理的 JsRpc聯(lián)動burp聯(lián)動burp需要借助mitmproxy寫腳本,或者借助其它一些burp插件也行,這里就用mitmproxy了,腳本借助gpt弄幾下即可 import requests import json from mitmproxy import ctx
def get_rpc(param): url = 'http://127.0.0.1:12080/go' params = { 'group': 'zzz', 'action': 'encode', 'param': param.decode('utf-8', errors='ignore') } try: result = requests.get(url, params=params, timeout=5) # ctx.log.info(f'RPC Response: {result.text}') # 第一次解析 JSON,獲取外層結(jié)構(gòu) outer_data = result.json() # 第二次解析 data 字段(字符串 -> 字典) inner_data = json.loads(outer_data['data']) return inner_data # 直接返回解析后的內(nèi)層數(shù)據(jù) except Exception as e: ctx.log.error(f'Error in get_rpc: {str(e)}') return None
def get_responseRpc(param): url = 'http://127.0.0.1:12080/go' params = { 'group': 'zzz', 'action': 'decode', 'param': param.decode('utf-8', errors='ignore') } try: result = requests.get(url, params=params, timeout=5)
outer_data = result.json() # 第二次解析 data 字段(字符串 -> 字典) inner_data = json.loads(outer_data['data']) return inner_data # 直接返回解析后的內(nèi)層數(shù)據(jù) except Exception as e: ctx.log.error(f'Error in get_rpc: {str(e)}') return None
def request(flow): original_body = flow.request.content ctx.log.info(f'原始請求體: {original_body}')
result = get_rpc(original_body) if result is None: ctx.log.error('RPC failed, skipping request modification') return
try: # 直接使用內(nèi)層數(shù)據(jù) flow.request.headers['timestamp'] = result['time'] flow.request.headers['requestId'] = result['id'] flow.request.headers['sign'] = result['sign'] flow.request.content = result['request'].encode('utf-8') flow.request.headers['Content-Length'] = str(len(flow.request.content)) ctx.log.info(f'加密后的請求體: {flow.request.content}')
except Exception as e: ctx.log.error(f'Error in request: {str(e)}')
def response(flow): response_body = flow.response.content ctx.log.info(f'響應體: {response_body}')
result = get_responseRpc(response_body) if result is None: ctx.log.error('Response RPC failed, keeping original response') return
try: # 將解碼后的字典轉(zhuǎn)為 JSON 字符串并編碼為字節(jié) decoded_json = json.dumps(result, ensure_ascii=False).encode('utf-8') flow.response.content = decoded_json flow.response.headers['Content-Length'] = str(len(flow.response.content)) # 可選:設置 Content-Type 為 JSON flow.response.headers['Content-Type'] = 'application/json; charset=utf-8' ctx.log.info(f'解碼后的響應體: {flow.response.content}') except Exception as e: ctx.log.error(f'Error in response: {str(e)}') 然后設置burp的上游代理 ![]() 運行腳本
![]() 這樣即可實現(xiàn)自動化加解密 當密碼不正確時 ![]() 當密碼正確時,這里請求頭中的三個值在burp雖然沒變但實際上是已經(jīng)改變了的,因為是在腳本中做的處理 ![]() 還是非常方便滴,因為不需要去逆js了,接下來是 JsRpc+Galaxy工具實現(xiàn)網(wǎng)站HTTP報文自動加解密+自動更新簽名 Galaxy工具可以做到在請求/響應在客戶端/Burp/服務端流轉(zhuǎn)時加入自己的處理邏輯,這可以用來實現(xiàn)請求/響應自動解密。 詳情見https://github.com/outlaws-bai/Galaxy JsRpc工具在網(wǎng)站的控制臺新建一個WebScoket客戶端鏈接到服務器通信,調(diào)用服務器的接口 > 服務器會發(fā)送信息給客戶端 > 客戶端接收到要執(zhí)行的方法執(zhí)行完js代碼后把獲得想要的內(nèi)容發(fā)回給服務器 > 服務器接收到后再顯示出來。 詳情見https://github.com/jxhczhl/JsRpc 環(huán)境靶場:http://39.98.108.20:8085/ 項目地址:
JS逆向分析網(wǎng)站數(shù)據(jù)包如下,數(shù)據(jù)進行加密處理,請求包的請求頭內(nèi)三個參數(shù)requestId、timestamp、sign缺一不可,改動任何一個都失效,且無法使用repeater重放數(shù)據(jù) ![]() 瀏覽器控制臺定位相關(guān)js代碼,其中關(guān)于請求頭三個參數(shù)如下: ![]() 加密js代碼如下,采用AES CBC key和iv都寫在js內(nèi) ![]() JS內(nèi)打上斷點 ![]() 可以看到其中n為需要傳輸?shù)臄?shù)據(jù)、timestamp通過Date.parse(new Date)獲取、requestId通過函數(shù)p()獲取、sign為a.a.MD5函數(shù)對傳輸?shù)臄?shù)據(jù)n加上timestamp加上requestId做MD5處理。l(n)為加密函數(shù)對傳輸?shù)臄?shù)據(jù)n進行加密。 控制臺調(diào)用函數(shù)打印相關(guān)數(shù)據(jù) ![]() 注入JsRpc客戶端及獲取簽名方法在這里使用jsrpc調(diào)用接口執(zhí)行js獲取timestamp、requestId、sign三個請求頭數(shù)據(jù),而加密采用Galaxy自帶加密模板。 jsrpc連接成功后,在瀏覽器控制臺對相關(guān)函數(shù)進行注冊 window.requestId=p //requestId window.v1 = v //函數(shù)v window.sign=a.a.MD5 //簽名sign ![]() 連接通信
![]() 這里可以直接通過瀏覽器訪問查看對應結(jié)果 ![]() 在Galaxy的hook腳本中調(diào)用JsRpc服務端加解密此處加密采用模板加解密python文件AES_CBC來實現(xiàn) 腳本內(nèi)寫入key、iv和對應算法 ![]() 該網(wǎng)站內(nèi)數(shù)據(jù)包采用GET和POST兩種方式進行請求,且只有POST請求的數(shù)據(jù)進行加密處理,因此請求數(shù)據(jù)發(fā)送到burp解密和burp明文數(shù)據(jù)到服務器加密需要區(qū)分開 ![]() ![]() JsRpc引入引入jsrpc需要jython引入http客戶端 from org.m2sec.core.outer import HttpClient 按照格式構(gòu)建引入jsrpc和替換請求頭的代碼 ![]()
將構(gòu)建好的代碼寫入到burp發(fā)送到服務器的函數(shù)zhong ![]() 效果圖自動加解密實現(xiàn)效果 ![]() 重放數(shù)據(jù)實現(xiàn)效果 ![]() Galaxy完整的hook腳本import json import base64 from org.m2sec.core.utils import ( CodeUtil, CryptoUtil, HashUtil, JsonUtil, MacUtil, FactorUtil, ) from org.m2sec.core.models import Request, Response from org.m2sec.core.outer import HttpClient from java.lang import String ''' 內(nèi)置示例,需要自定義代碼文件時查看該文檔:https://github.com/outlaws-bai/Galaxy/blob/main/docs/Custom.md 按 Ctrl(control) + ` 可查看內(nèi)置函數(shù) ''' ALGORITHM = 'AES/CBC/PKCS5Padding' secret = b'1234567891234567' iv = b'1234567891234567' paramMap = {'iv': iv} jsonKey = 'data' log = None def hook_request_to_burp(request): '''HTTP請求從客戶端到達Burp時被調(diào)用。在此處完成請求解密的代碼就可以在Burp中看到明文的請求報文。 Args: request (Request): 請求對象 Returns: Request: 經(jīng)過處理后的request對象,返回null代表從當前節(jié)點開始流量不再需要處理 ''' if(request.getMethod()=='GET'): return request else: # 獲取需要解密的數(shù)據(jù) encryptedData = CodeUtil.b64decode(request.getBody()) # 調(diào)用內(nèi)置函數(shù)解密 data = decrypt(encryptedData) # 更新body為已加密的數(shù)據(jù) request.setContent(data) return request def hook_request_to_server(request): '''HTTP請求從Burp將要發(fā)送到Server時被調(diào)用。在此處完成請求加密的代碼就可以將加密后的請求報文發(fā)送到Server。 Args: request (Request): 請求對象 Returns: Request: 經(jīng)過處理后的request對象,返回null代表從當前節(jié)點開始流量不再需要處理 ''' if(request.getMethod()=='GET'): burpRequestBody='' url='http://127.0.0.1:12080/go?group=zzz&action=hello¶m=' jsrpcUrl=url+burpRequestBody jsrpcRequest=request.of(jsrpcUrl) jsrpcRespone=HttpClient.send(jsrpcRequest) jsrpcResponeJson=jsrpcRespone.getJson() headData=jsrpcResponeJson['data'] headData=json.loads(headData) time=headData['time'] requestId=headData['id'] sign=headData['sign'] head=request.getHeaders() head.put('sign',sign) head.put('requestId',requestId) head.put('timestamp',time) request.setHeaders(head) return request # 獲取被解密的數(shù)據(jù) else: data = request.getContent() burpRequestBody=request.getBody() url='http://127.0.0.1:12080/go?group=zzz&action=hello¶m=' jsrpcUrl=url+burpRequestBody jsrpcRequest=request.of(jsrpcUrl) jsrpcRespone=HttpClient.send(jsrpcRequest) jsrpcResponeJson=jsrpcRespone.getJson() headData=jsrpcResponeJson['data'] headData=json.loads(headData) time=headData['time'] requestId=headData['id'] sign=headData['sign'] head=request.getHeaders() head.put('sign',sign) head.put('requestId',requestId) head.put('timestamp',time) request.setHeaders(head) # 調(diào)用內(nèi)置函數(shù)加密回去 encryptedData = encrypt(data) # 將已加密的數(shù)據(jù)轉(zhuǎn)換為Server可識別的格式 body = CodeUtil.b64encode(encryptedData) # 更新body request.setContent(body) log.info('header2: {}',request) return request def hook_response_to_burp(response): '''HTTP請求從Server到達Burp時被調(diào)用。在此處完成響應解密的代碼就可以在Burp中看到明文的響應報文。 Args: response (Response): 響應對象 Returns: Response: 經(jīng)過處理后的response對象,返回null代表從當前節(jié)點開始流量不再需要處理 ''' # 獲取需要解密的數(shù)據(jù) encryptedData = CodeUtil.b64decode(response.getBody()) # 調(diào)用內(nèi)置函數(shù)解密 data = decrypt(encryptedData) # 更新body response.setContent(data) return response def hook_response_to_client(response): '''HTTP請求從Burp將要發(fā)送到Client時被調(diào)用。在此處完成響應加密的代碼就可以將加密后的響應報文返回給Client。 Args: response (Response): 響應對象 Returns: Response: 經(jīng)過處理后的response對象,返回null代表從當前節(jié)點開始流量不再需要處理 ''' # 獲取被解密的數(shù)據(jù) data = response.getContent() # 調(diào)用內(nèi)置函數(shù)加密回去 encryptedData = encrypt(data) # 更新body # 將已加密的數(shù)據(jù)轉(zhuǎn)換為Server識別的格式 body = CodeUtil.b64encode(encryptedData) # 更新body response.setContent(body) return response def decrypt(content): return CryptoUtil.aesDecrypt(ALGORITHM, content, secret, paramMap) def encrypt(content): return CryptoUtil.aesEncrypt(ALGORITHM, content, secret, paramMap) def set_log(log1): '''程序在最開始會自動調(diào)用該函數(shù),在上方函數(shù)可以放心使用log對象''' global log log = log1 0x03 總結(jié) |
|