CLIENT-SIDE CRYPTO // AES-CBC // CDN SECRETS
WEB APPLICATION SECURITY

Reversing Client-Side AES: When ‘Encrypted APIs’ Are Just Obscurity

CRYPTOJS // STATIC IV // API AUTOMATION

researcher
Naveen Jagadeesan
published
2023-02-15
platform
Web / Android / iOS (shared API)

Summary

A multi-app product family encrypted all REST bodies client-side with AES-CBC, advertising the design as a security control. Secrets lived in shared CDN JavaScript with a static IV. Once reversed, the “encrypted API” became a normal JSON API — and weak CAPTCHA token issuance enabled automated registration abuse.

Theory

Client-side encryption of HTTP JSON is almost never confidentiality against the user. It is, at best:

The research theory:

If encryption keys ship in the same trust domain as the attacker (browser/app), treat ciphertext as encoding, not security. Your job is to recover transform parameters and then test the real server-side controls underneath.

Shared CDN + many subdomains made that theory stronger: one broken crypto helper would unlock a portfolio of apps.

Recon observations

Keyword pass over bundles: encrypt, decrypt, CryptoJS, iv, key, AES.

Recovering the transform

Configuration fragments exposed key material and endpoints:

// simplified shape observed in bundles
{
  apiDomain: "https://api.example.com",
  bookingApiDomain: "https://bookings.example.com",
  cdnPath: "https://cdn.example.com",
  dataHasKey: "###################=",
  dataIVKey: "jm8lgqa3j1d0ajus",
  encryptionKeys: "[...]"
}

Encrypt helper (conceptual reconstruction):

encrypt: function (obj) {
  const key = this.encryptKEY;           // hardcoded
  const iv  = "jm8lgqa3j1d0ajus";       // static IV
  const plain = JSON.stringify(obj);
  return CryptoJS.AES.encrypt(
    plain,
    CryptoJS.enc.Utf8.parse(key),
    {
      iv: CryptoJS.enc.Utf8.parse(iv),
      padding: CryptoJS.pad.Pkcs7,
      mode: CryptoJS.mode.CBC
    }
  ).toString() + "---" + btoa(iv);
}

Cryptographic red flags

Property Observation Why it matters
Key location Browser/app bundle Attacker is a legitimate client
IV Static / reused Breaks semantic security; patterns leak
Mode AES-CBC with client decrypt Server trusts client-produced ciphertext structure
Framing cipher---b64(iv) Easy to reimplement offline

After decryption, bodies were ordinary JSON (PII fields, passwords, activity sources, etc.).

CAPTCHA workflow under the microscope

Registration used Google reCAPTCHA, but token minting / verification steps were insufficiently bound to:

Attack workflow:

obtain captcha challenge
  → verify / mint token
  → craft registration JSON
  → AES-CBC encrypt with recovered key/IV
  → POST to API
  → repeat

Impact class: large-scale fake account creation, spam, resource exhaustion, bypass of “encrypted + captcha protected” assumptions.

Why this is worth writing up

Not because “AES in JavaScript is bad” as a slogan — but because:

  1. Crypto was positioned as a control, not friction.
  2. A shared CDN secret expanded blast radius across apps.
  3. Static IVs and recoverable keys made offline tooling trivial.
  4. Real server controls (CAPTCHA, rate limit, abuse) were weaker than the crypto cosplay suggested.

Defender guidance

Takeaway

When you see encrypted JSON APIs, reverse the client transform first. The interesting bugs usually start after decrypt() — where business logic and anti-automation were never as strong as the Base64 looked.