Author: 0pening
1. Introduction
Testing a certain WeChat mini program, without further ado, let’s start using BurpSuite to capture packets. However, upon capturing the packets, I noticed something was off 🤨.
This application encrypts the parameters into bizContent and uses a signature parameter sign to prevent man-in-the-middle attacks on the data packets… not very honorable.
2. Analyzing the Data Packet
Let’s bravely analyze it and see if the signature parameter sign is validated. By reducing the parameters, it turns out to be a paper tiger. The parameters were simplified as follows, and the data packet could be sent normally.
It turns out that sign is just a paper tiger, as there is no validation on the backend. So, is user identity verification done through openId? 🤔️
By modifying the openId parameter, I discovered a logical flaw.
Incorrect openId, verification failed.
openId is empty, successfully returned data.
Deleted openId parameter, successfully returned data.
Since this is an interface requesting personal information, deleting or leaving openId empty still retrieves personal information. Therefore, there exists a parameter in bizContent that determines the user identity. If this identifier is predictable, it is highly likely that one can modify this identifier to gain unauthorized access to others’ identity information.
3. Cracking the Signature Algorithm
After the above analysis, the next step is to find a way to obtain the plaintext data of the bizContent parameter.
By checking the history records of BurpSuite’s data packets
Searching the context yielded no relevant JS requests, so the encryption algorithm’s source code should be in the WeChat mini program’s source code.
-
• The source files of WeChat mini programs are stored on WeChat’s servers after developers publish them. When users first load the mini program, it downloads the mini program to the user’s local device (****in *.wxapkg format as a binary file).
Extracting the .wxapkg file: (Android will place it in the following directory)
/data/data/com.tencent.mm/MicroMsg/{number_string}/appbrand/pkg/
Decompiling the .wxapkg file to obtain the mini program’s source code (https://github.com/Cherrison/CrackMinApp)
This tool is used mainly because it does not require an additional node.js environment and can be used directly.
By globally searching for the keyword bizContent, I found it in the app-service.js file. Beautifying the app-service.js makes it easier to read. I found the encryption and decryption algorithms.
Testing the AES decryption, it was successful, with two parameters being passed: one idcard and one desc for the request interface.
Then, by replacing the idcard parameter with someone else’s ID card, I called the interface to query and returned someone else’s information, proving unauthorized access.
4. Using the Burpy Plugin
After figuring out the encryption algorithm, testing can commence, but a problem arises: do I have to copy and paste the bizContent parameter and returned data every time to decrypt for testing? Is there a more elegant testing method?
I turned my attention to m0nst3r’s https://github.com/mr-m0nst3r/Burpy. It supports Python 3.
The usage tutorial is available in the blog:
https://m0nst3r.me/burpsuite/打通BurpSuite与Python之间的任督二脉.html
After loading the plugin, configure it as follows: (the plugin requires Pyro4 to be installed -> pip3 install Pyro4)
The collected AES-related encryption configurations are as follows:
783a2274472d5928
iv = 0102030405060708
Mode = ECB
padding = Pkcs7
Write the AES encryption and decryption script. Just implement the encrypt and decrypt functions as required by the project.
import json
import base64
from Crypto.Cipher import AES
class Burpy:
def __init__(self):
self.key = "783a2274472d5928"
self.iv = "0102030405060708"
self.apicode = ""
def encrypt(self, header, body):
# Get the encryption parameter bizContent
body_json = json.loads(body)
bizContent = json.dumps(body_json["bizContent"]).replace(' ', '')
# AES encryption
while len(bizContent) % 16 != 0: # Pad the string length to a multiple of 16
bizContent += (16 - len(bizContent) % 16) * chr(16 - len(bizContent) % 16)
bizContent = str.encode(bizContent)
aes = AES.new(self.key, AES.MODE_ECB) # Initialize the encryptor
bizContent = str(base64.encodebytes(aes.encrypt(bizContent)), encoding='utf8').replace('\n', '')
# Replace parameters, set openId to empty to bypass
body_json["bizContent"] = bizContent
body_json["openId"] = ''
body = json.dumps(body_json)
return header, body
def decrypt(self, header, body):
cipher = AES.new(self.key, AES.MODE_ECB, self.iv)
# Get the encryption parameter bizContent
body_json = json.loads(body)
bizContent = body_json.get("bizContent")
# AES decryption
content = cipher.decrypt(base64.decodebytes(bytes(bizContent, encoding='utf8'))).decode("utf8")
content = content[:-ord(content[-1])]
body_json["bizContent"] = json.loads(content)
body = json.dumps(body_json)
return header, body
After loading the plugin, configure it as follows, checking the automatic encryption and decryption option.
Testing as follows, perfectly retrieving data, testing is much more elegant now!
In the Repeater, modify the data packet and send it in plaintext, the returned packet is automatically decrypted.