Recently, I encountered a requirement for automatic login. After clicking a few buttons, I found that besides the encrypted login password, there was also a sliding verification code to consider. This seemed like a good opportunity to write some introductory articles for sharing and documentation.
0x01 Basic Information Gathering and Workflow Analysis
First, let’s go through the process to take a look at the related requests. We know that the website is built on a front-end and back-end separation architecture, with all requests under the API directory. The request flow for login is as follows:

After examining each request and analyzing the business, I mapped out the following workflow:
a. /get
interface retrieves the verification code data
To make it easier for readers to follow along, I took a screenshot in Postman. This interface request returns four values: a secretKey
(currently unknown), a complete background image for the sliding verification code, a gap image, and a token.

The actual appearance of the images after encoding is as follows:

b. `/check` interface is used to verify the slide result
The verification interface uploads the verification code type (which is the same as the one from the get interface), the token (extracted from the return package of the get interface), and a point field.

From the results so far, the point field clearly represents the outcome or data of the sliding action (which some sliding verification codes use), but it’s not in plain text, so further analysis and decryption attempts are needed.
c. `/login` interface is used for logging in

In this interface, the username is in plain text, while the password and verification code fields are encrypted. After analysis, I found that the encrypted verification code field isn’t related to the /check
interface and shouldn’t be retrieved from its return values. Therefore, we only need to handle the /get
and `/login` interfaces.
Note: Proper analysis is very important. I initially tackled the verification code recognition interface based on my understanding, and it wasn’t until I worked on the login interface that I realized that the interface itself was useless… what a waste of time.
0x02 Handling the Sliding Verification Code
To understand how the server validates the verification code, we need to decipher what the encrypted verification code corresponds to. Here’s a direct screenshot showing the flow:
a. Identify the verification code recognition logic
Based on the parameters of the login interface, locate the corresponding JS file:

Once the file is located, I set breakpoints at suspicious code sections to see if I could intercept the execution:

Interpreting the code, the parameters for the verification code are passed from the above variable const le. It essentially performs a ternary evaluation that concatenates d.value with a constant when d.value evaluates to true, combines it with JSON x/y values, and then calls function H to produce our final string.

Further analysis shows that d.value refers to the /get
interface’s secretKey, and the variable r corresponds to the distance of the sliding verification code, or its horizontal position on the background image. The y variable is fixed and doesn’t require our attention. Next, we need to look at what the H function does:

The logic of the H function is straightforward: two variables are passed in, f being a constant and l a plain text. Using f as the key, AES encryption is applied in ECB mode with PKCS7 padding. To simplify our understanding, we can distill the logic:

The corresponding encryption logic is: the secretKey is concatenated with a constant and then the JSON text of the sliding verification code’s x/y values, which is then encrypted using AES-ECB based on that secretKey.
b. Attempt to automate verification code recognition
After grasping the logic, I could start automating the operation. For reference on verification code recognition, you can check out my previous article: https://blog.mrtblogs.net/burpsuite-ocr-captcha. The ddddocr library is a great open-source tool for automated verification code recognition, and according to the project documentation, it supports simple sliding verification code recognition.
Considering that our server-side doesn’t validate the sliding trajectory of the block, we only need to identify the correct position of the block gap. Since I wanted to use n8n to automate the login process, I launched a FastAPI service locally to handle the verification code recognition requests. Here’s a reference code:
import base64
import ddddocr
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
# Define the data model for the request body
class SlideMatchRequest(BaseModel):
origin: str # Base64 encoded original image
target: str # Base64 encoded jigsaw image
app = FastAPI()
# Initialize ddddocr, setting det and ocr to False, used only for block matching
# Note: ddddocr should be initialized at application startup to prevent reloading the model on each request.
# For simplicity, it's placed within the request handling function; however, for high concurrency,
# it’s better to move it outside.
# det_ocr = ddddocr.DdddOcr(det=False, ocr=False) # A better way to initialize once like this.
@app.get("/")
def read_root():
return {"message": "Slide match API is running"}
@app.post("/slide_match/")
async def perform_slide_match(request: SlideMatchRequest):
"""
Receives base64 encoded original image and sliding image, returning the slide match result.
"""
try:
# Decode base64 images
target_bytes = base64.b64decode(request.target)
full_bytes = base64.b64decode(request.origin)
# Initialize ddddocr (if it was initialized above, use det_ocr here)
det = ddddocr.DdddOcr(det=False, ocr=False)
# Perform sliding match
res = det.slide_match(target_bytes, full_bytes)
return {"result": res}
except base64.binascii.Error:
raise HTTPException(status_code=400, detail="Invalid base64 string provided.")
except Exception as e:
# Catch any other potential errors, such as ddddocr processing failures
raise HTTPException(status_code=500, detail=f"An error occurred during the slide match: {e}")
After ddddocr completes the recognition, it will provide the position of the gap in the complete verification code image. We can take the left edge of the rectangle and appropriately add 1-5px to meet the requirements.
Moving on to the specific n8n flow, we first need to get the corresponding verification code information, which is the base64 encoded verification code image:

Then we can send it to the locally running FastAPI service with ddddocr for recognition and get the x-axis information for the gap:

With the x-axis information in hand, we then use the reverse-engineered JavaScript encryption logic in the code node to encrypt our verification code information for further use:

0x03 Solving Password Encryption
Similar to the reverse engineering of the verification code, I pinpointed the suspected password encryption section in the same file for breakpoint testing and found the qe function:

By stepping through the code with F11, I eventually uncovered that the password undergoes RSA encryption and retrieves the public key:

Finally, I implemented the password encryption step within n8n:

Leave a Reply