When Encryption Isn’t the Problem: Breaking Client-Side Encrypted Architectures the Right Way
~Basavaraj B Banakar, February 12, 2026
For a long time, encrypted applications have had a strange psychological effect on security testers.
Once traffic looks encrypted, many people subconsciously slow down.
Some stop entirely.
“This is end-to-end encrypted.”
“There’s nothing meaningful we can test.”
“It’s all handled by an SDK.”
This blog is about why that mindset is dangerous — and how an encrypted client + proxy architecture led us to uncover two high-impact security issues: full request/response visibility at runtime and a server-side request forgery vulnerability.
Not by breaking encryption — but by understanding where trust actually lives.
The Setup: An Embedded SDK and a Proxy
The architecture looked clean on paper:
- Applications create request objects in plaintext
- The SDK encrypts request data on the client
- Encrypted data is sent to a proxy server
- The proxy decrypts, forwards the request, encrypts the response
- The SDK decrypts the response and hands it to the app
From a network perspective:
- All traffic was encrypted
- Payloads were unreadable
- TLS + application-level encryption were both in place
At first glance, this looked like a hard stop.
It wasn’t.
The First Shift in Thinking: Stop Attacking Crypto
One of the most important lessons here was realizing what not to attack.
Encryption algorithms were strong.
Key management looked reasonable.
Nothing about the crypto itself suggested weakness.
So instead of asking “How do we break the encryption?”, we asked:
- Where does plaintext exist in memory?
- Where does encryption start?
- Where does decryption end?
- Which component makes security decisions?
These questions matter far more than packet captures.
Finding the Real Attack Surface: Runtime Plaintext
No matter how strong encryption is, plaintext must exist at some point:
- Before a request is encrypted
- After a response is decrypted
That boundary is unavoidable.
Instead of focusing network traffic(Because the requests and responses were encrypted), we focused on:
- Runtime request construction
- Serialization and deserialization logic
- SDK interceptors
- Encryption and decryption entry points
Once we instrument those points, encryption stopped being a blocker.
We could:
- Observe request objects before encryption
- Observe response objects after decryption
- Modify request logic at runtime
- Let the SDK encrypt attacker-controlled data itself
At that point, the SDK was no longer a black box.
The First Finding - Breaking the SDK’s Core Business Promise
The SDK’s value proposition was simple:
“We encrypt everything, so communication is safe.”
From a transport perspective, that was true.
From a security perspective, it was incomplete.
Once we gained visibility into plaintext at runtime, we were able to:
- Read decrypted responses
- Modify request fields before encryption
- Craft new requests using the same SDK logic
- Re-encrypt those requests and send them through the proxy
Step 1: Attacking the Plaintext Boundary (Before Encryption)
What the script hooks
|
Gson.toJson.overload(‘java.lang.Object’) |
The SDK uses Gson to serialize request objects before encryption. This is the most critical mistake in the design.
At this point:
- The request is fully plaintext
- All business-critical fields exist in memory
- Encryption has not yet started
By intercepting this method, we gain access to:
- HTTP method
- Target URL
- UID
- Complete request structure
This is not a cryptographic break — it is a logic break.
Step 2: Modifying Business Logic at Runtime
if (
json.includes(“\”method\””) &&
json.includes(“\”url\””) &&
json.includes(“\”uid\””)
)
This condition ensures we are intercepting actual business requests, not encrypted blobs.
The script then modifies the request before the SDK encrypts it:
return modified;
From the SDK’s perspective:
- The request is legitimate
- The data was encrypted “securely”
- No tampering is detected
But in reality:
- The attacker fully controls request intent
- Encryption merely wraps attacker-controlled data
This completely breaks the SDK’s core promise:
“Requests cannot be modified because they are encrypted.”
They absolutely can.
Step 3: Attacking the Decryption Boundary (After Encryption)
What the script hooks
|
Gson.fromJson.overload(‘java.lang.String’, ‘java.lang.Class’) |
This method is called after the SDK decrypts server responses.
At this point:
- Responses are already decrypted
- Response data is in plaintext
- The SDK is about to map it into application objects
The script logs the raw decrypted response before the app sees it.
Again, this is not breaking encryption — this is exploiting where plaintext exists.
Step 4: Forcing Decrypted Data Into the UI (UI used to show only the responses which are in JSON Format)
if (clazz.getName().includes(“ServerResponse”)) {
fake.message.value = json;
return fake;
}
This step is critical.
Even if:
- The response was not meant to be user-visible
- The app expects a strict schema
The script:
- Replaces the legitimate response object
- Injects arbitrary decrypted data into UI-bound fields
As a result:
- Any decrypted response can be rendered
- Response confidentiality is lost
- The SDK cannot enforce how decrypted data is used
Effectively, we regained full control over the application’s behavior — without breaking crypto and without bypassing the SDK.
In this scenario, an attacker changes their strategy to target the architecture rather than the math. Here is how an attacker would execute this process:
- Bypassing the Crypto: Instead of trying to crack the complex math of the encryption, which was found to be strong , an attacker uses a tool called Frida to look at the data while it is still in the phone’s memory.
- Visibility: By hooking into the app’s internal logic—specifically the Gson library used for serialization —an attacker can see the “secret” requests in plain text before the app encrypts and sends them.
- Manipulation: The attacker modifies these requests in memory while they are still in a fully plaintext state. Since they do this before encryption starts , the app’s own SDK then “securely” encrypts the attacker-controlled data and sends it to the server.
- The Result: The server receives a perfectly encrypted message that appears legitimate. Because it was encrypted by the trusted SDK, the proxy server assumes it is safe, even though it actually contains malicious, attacker-controlled commands.
This method proves that encryption only protects the privacy of the data during transit; it does not stop a compromised client from sending a validly encrypted but malicious request.
That alone is an important lesson:
Encryption does not stop a malicious or compromised client from sending malicious but valid requests.
The Second Finding: When Encryption Hides Trust Flaws(Server Side Request Forgery)
With request construction under control, the next question was simple:
What does the server actually trust?
In this architecture, the proxy decrypted requests and then acted on client-supplied values — including request destinations.
That was the critical mistake.
Because the proxy trusted encrypted input, it assumed that:
- If the request was encrypted correctly, it must be legitimate
- If it came from the SDK, it must be safe
By modifying plaintext request data before encryption, we were able to influence where the proxy sent requests.
The result was a classic server-side request forgery — but through an encrypted channel and a trusted proxy.
SSRF To Access AWS Metadata
This wasn’t a client-side issue.
The server made the request.
The proxy executed it.
Encryption didn’t prevent it.
The Core Lesson: Encryption Is Not a Trust Boundary
This case reinforced a fundamental rule:
Encryption protects confidentiality, not intent.
If a system:
- Trusts client input
- Uses encryption as a substitute for validation
- Assumes SDKs cannot be abused
Then encryption becomes a false sense of security, not a control.
The real vulnerabilities live in:
- Trust assumptions
- Server-side enforcement gaps
- Proxy behavior
- Architectural decisions
How to Test Encrypted Applications Properly
If you encounter an application that uses client-side encryption, don’t stop. Change approach.
Here’s what worked consistently:
- Map the architecture
- Client, SDK, proxy, backend
- Who encrypts? Who decrypts? Who decides?
- Identify crypto boundaries
- Before encryption
- After decryption
- Inspect runtime objects, not packets
- Requests before encryption
- Responses after decryption
- Test trust, not algorithms
- URLs
- Methods
- Identifiers
- Destinations
- Authorization assumptions
- Ask one critical question
- “What happens if the client is malicious?”
If the answer is “the proxy will still trust it,” there’s work to do.
What a Secure Design Actually Looks Like
In a SaaS SDK + proxy model, this means:
- The proxy must enforce server-side policies
- Destinations must be validated after decryption
- Internal and metadata endpoints must be blocked
- Encryption must never replace validation or authorization
- Need to enforce/ give guidelines for the clients to have strict Root Detection/SSL Pinning/Frida Detection/Play Integrity checks in Place
Encryption is still valuable — just not sufficient.