When Security by Obscurity Blinds the WAF: From Client-Side Encryption to Critical SQL Injection (Part 2)

Post Date

Published on
Authors
When Security by Obscurity Blinds the WAF: From Client-Side Encryption to Critical SQL Injection (Part 2)

TL;DR#1: In the first part of this post, we covered how Just Mobile Security's offensive and research team identified a recurring pattern of additional client-side encryption over TLS in a sample of banking and fintech applications from Latin America. We explained how they confirmed this by combining traffic capture, static and dynamic analysis, using Tungstenic and local storage review.

TL;DR#2: Now, we will continue reviewing the practical process of reverse engineering on a real application, how to extract cryptographic keys in real time using Frida, and how to replicate the logic in external scripts.

TL;DR#3: Finally, we will automate the entire flow by creating a custom extension for Burp Suite, which will allow us to completely bypass WAF inspection and exploit a critical Out-of-Band (OOB) SQL Injection vulnerability directly on the backend.


Index

  1. Reverse engineering of client-side encryption
  2. Reimplementation of the encryption and decryption flow
  3. From manual scripts to a Burp extension
  4. When the WAF went blind
  5. Critical Finding: SQL Injection OOB
  6. Defensive learning
  7. Conclusion

1. Reverse engineering of client-side encryption

With the pattern identified, the evaluation team selected a representative application and began to follow the complete flow within the client: where the request was constructed, at what point it was encrypted, what values the app needed to do so, how it received the response, and when it transformed the content back into readable JSON.

Reverse Engineering Client-Side Encryption - Demo

Each relevant hypothesis was validated manually, because in this type of analysis a false positive or an incorrect interpretation can completely change the technical conclusion.

The analysis identified the components responsible for constructing requests, encrypting bodies, encapsulating data, decrypting responses, and obtaining required values at runtime. In this case, some values were correctly protected using Android's own mechanisms, such as Keystore.

That did not imply direct exposure for the end user; the challenge for the team was to instrument the legitimate flow and obtain those values from their own test device.

To do this, the resources were analyzed as follows:

  1. Implementation of request and response generation.
  2. Classes, methods, helpers and interceptors responsible for encryption and decryption.
  3. Derivation of reproducible keys or values from data available to the application.
  4. Using Frida to observe the exact moment when certain values were available in memory or could be extracted during the legitimate flow of the application.
  5. Validation that the traffic could be decrypted and re-encrypted outside the original client, using only information available to our test instance.

1.1 Request encryption

The team observed that the request flow followed an architecture based on an HTTP client, interceptors, and encryption helpers. Before the request was sent to the backend, the functional body was replaced with an encrypted structure that included the data blob and auxiliary values necessary for the server to process it.

Following the flow, the observed process was as follows:

  1. The app built the original functional JSON from the user's action.
  2. An interceptor or network module intercepted the request before sending it.
  3. The encryption logic derived the key or retrieved the required values at runtime.
  4. The original body was encrypted with a symmetric scheme and encapsulated in an external structure.
  5. The final request sent by the client contained the encrypted blob, not the functional plaintext JSON.
  6. As can be seen in the following figures (2 to 7), the complete request chain encompasses the definition of endpoints, pre-send interception, payload encapsulation, encryption routine, and derivation of the key used by the client.
Figure 2. Definition of external endpoints in the service interface used by the client before applying body encryption.
Figure 2. Definition of external endpoints in the service interface used by the client before applying body encryption.
Figure 3. Creation of the Retrofit client and logic that intercepts the request before sending to replace the functional body with its encrypted version.
Figure 3. Creation of the Retrofit client and logic that intercepts the request before sending to replace the functional body with its encrypted version.
Figure 4. Construction of the DataEncrypt object and encapsulation of the functional payload along with the deviceUUID.
Figure 4. Construction of the DataEncrypt object and encapsulation of the functional payload along with the deviceUUID.
Figure 5. The requestEncryptByFingerPrint() method that delegates the encryption of the body to the main cryptographic routine.
Figure 5. The requestEncryptByFingerPrint() method that delegates the encryption of the body to the main cryptographic routine.
Figure 6. encryptWithFingerPrint() routine with IV generation and AES/CBC encryption of the message before sending it to the backend.
Figure 6. encryptWithFingerPrint() routine with IV generation and AES/CBC encryption of the message before sending it to the backend.
Figure 7. Reproducible generation of the fingerprint key from the day, the TLS certificate fingerprint, and the deviceUUID.
Figure 7. Reproducible generation of the fingerprint key from the day, the TLS certificate fingerprint, and the deviceUUID.

The most relevant observation was not that encryption existed, but that all the logic necessary to construct the encrypted message was available client-side.

1.2 Decoding Responses

The response flow followed the reverse logic. The HTTP client received an encrypted response, extracted the data field, derived or retrieved the necessary values, and replaced the received body with the plaintext JSON before delivering it to the rest of the application.

This design allowed the application to work internally with normal structures, but it also prevented intermediary tools from seeing the actual content without first reproducing the same decryption process.

The following figures (8 to 11) show the reverse path:

  • The incorporation of the interceptor, the selection of the decryption method, and the routine that transforms the encrypted response into a functional JSON for the application.
Figure 8. Construction of the OkHttp client for responses and incorporation of the DecryptFingerPrintInterceptor within the network flow.
Figure 8. Construction of the OkHttp client for responses and incorporation of the DecryptFingerPrintInterceptor within the network flow.
Figure 9. Interceptor responsible for receiving the encrypted response and replacing the body with the decrypted content before delivering it to the app.
Figure 9. Interceptor responsible for receiving the encrypted response and replacing the body with the decrypted content before delivering it to the app.
Figure 10. Selection of the decryption method and entry into the responseDecryptByFingerPrint() stream to retrieve the working content.
Figure 10. Selection of the decryption method and entry into the responseDecryptByFingerPrint() stream to retrieve the working content.
Figure 11. decryptWithFingerPrint() routine used to reconstruct the plaintext JSON from the received encrypted blob.
Figure 11. decryptWithFingerPrint() routine used to reconstruct the plaintext JSON from the received encrypted blob.

1.3 Runtime Values and Keystore

Some of the information needed to reproduce the flow was not hardcoded as a static secret. Some values were generated or retrieved at runtime and, in certain cases, stored using secure operating system mechanisms.

This point was important to avoid misinterpreting the finding:

  • The problem wasn't necessarily poor local protection of secrets. The weakness was the architecture. The application needed those values to operate, so the team could instrument the legitimate flow, observe when they appeared, and use them to reproduce the encryption outside the app.

2. Reimplementation of the encryption and decryption flow

Once the logic was understood, the team proceeded to reimplement it in external scripts. Two approaches were tested, Python and Java, because reimplementing part of the logic in a language close to the original can reduce compatibility differences, cryptographic defaults, and serialization details that often cause subtle errors.

The goal wasn't to break AES, TLS, or any cryptographic algorithm. The goal was to replicate the same logic already distributed within the mobile client, using values obtained from the team's test environment.

Figures (12 and 13) show this reimplementation in Java and Python, both used to validate that the client logic could be reproduced in a controlled manner outside of the app.

Figure 12. Reimplementation in Java of the key derivation and decryption logic later used by the Burp extension.
Figure 12. Reimplementation in Java of the key derivation and decryption logic later used by the Burp extension.
Figure 13. Python reimplementation of the fingerprint key and AES/CBC routines to validate the flow outside the application.
Figure 13. Python reimplementation of the fingerprint key and AES/CBC routines to validate the flow outside the application.

2.1 Manual validation of the flow

With the scripts ready, the Just Mobile Security team manually validated the workflow before integrating it into Burp Suite. This stage confirmed that the data extracted at runtime was sufficient to decrypt requests, analyze responses, and generate valid messages for the backend.

  1. The team opened the application on the test device.
  2. He implemented the process with Frida to obtain the necessary values at runtime.
  3. He performed an action that generated network traffic.
  4. It intercepted the encrypted request.
  5. He executed the decryption script with the request and the captured information.
  6. He validated that the functional JSON could be observed in plain text.
  7. He repeated the same process on encrypted responses.
  8. He confirmed that the content could be modified, re-encoded, and maintained in a format accepted by the backend or the client application, depending on the direction of the flow.
  9. Figures (14 to 19) document this process step by step:
Figure 14. Instrumentation with Frida to extract at runtime the fingerprint and other values necessary to reproduce the cryptographic flow.
Figure 14. Instrumentation with Frida to extract at runtime the fingerprint and other values necessary to reproduce the cryptographic flow.
Figure 15. Initial interception of encrypted request and response before the manual decryption process.
Figure 15. Initial interception of encrypted request and response before the manual decryption process.
Figure 16. Manual decryption of the request and visualization of the functional JSON in plain text.
Figure 16. Manual decryption of the request and visualization of the functional JSON in plain text.
Figure 17. Manual decryption of the encrypted response and validation of the functional content returned by the backend.
Figure 17. Manual decryption of the encrypted response and validation of the functional content returned by the backend.
Figure 18. Controlled modification of the plain JSON before re-encrypting it and forwarding it to the backend.
Figure 18. Controlled modification of the plain JSON before re-encrypting it and forwarding it to the backend.
Figure 19. Re-encoding the modified request, regenerating a valid blob to complete the test flow.
Figure 19. Re-encoding the modified request, regenerating a valid blob to complete the test flow.

3. From manual scripts to a Burp extension

At this point, the team could already decrypt traffic, modify functional content, and regenerate valid messages. The problem was obvious: doing it request by request was slow, cumbersome, and unrealistic for a comprehensive evaluation.

Therefore, the next step was to integrate the logic directly into a Burp Suite extension. The extension allowed the encrypted request to be intercepted, the body decrypted, the functional JSON exposed for analysis or modification, the request re-encrypted before being sent to the backend, and the process repeated on responses before they were returned to the application.

The following video shows the step-by-step process:

Burp Extension Automating Encryption Flow - Demo

The extension transformed a manual test into a usable workflow for mobile pentesting. From that moment on, the Just Mobile Security team could work on the actual application content within Burp (Repeater, Intruder, Collaborator, etc.), run tamper tests, and observe how the backend responded without modifying the app's binary.

3.1 Extension Test

The first validation involved intercepting real application traffic, automatically decrypting it within Burp, modifying controlled values, and re-encrypting the message before sending it to the backend. The team then repeated the process on responses to ensure the app received a valid structure.

Figures (20 to 24) show the extension's load, its configuration, and different views of the traffic intercepted and processed within Burp Suite.

Figure 20. Loading and initializing the extension in Burp Suite to automate the encryption and decryption flow.
Figure 20. Loading and initializing the extension in Burp Suite to automate the encryption and decryption flow.
Figure 21. Extension configuration screen with fingerprint, UUID and base day used to operate on encrypted traffic.
Figure 21. Extension configuration screen with fingerprint, UUID and base day used to operate on encrypted traffic.
Figure 22. View of the traffic intercepted in Burp before applying the automatic content transformation using the extension.
Figure 22. View of the traffic intercepted in Burp before applying the automatic content transformation using the extension.
Figure 23. Visualization of the request and response already decrypted within Burp using the extension JSON view.
Figure 23. Visualization of the request and response already decrypted within Burp using the extension JSON view.
Figure 24. Inspection of the original encrypted request and the associated response during testing with the extension.
Figure 24. Inspection of the original encrypted request and the associated response during testing with the extension.

3.2 Traffic modification tests

In addition to validating that the traffic could be automatically decrypted, the team used the extension to edit the functional content, re-encrypt it, and forward valid requests to the backend. Figures 25 to 27 show examples of this stage.

  1. Modification of plain text JSON
  2. Re-encoded request and observed response within the same instrumented flow.
Figure 25. Modification of the functional payload in plain text within Burp before re-encrypting the request.
Figure 25. Modification of the functional payload in plain text within Burp before re-encrypting the request.
Figure 26. Request already re-encoded and forwarded to the backend after modifying the functional content within the instrumented flow.
Figure 26. Request already re-encoded and forwarded to the backend after modifying the functional content within the instrumented flow.
Figure 27. Additional example of the edited request and associated response observed within the instrumented flow.
Figure 27. Additional example of the edited request and associated response observed within the instrumented flow.

4. When the WAF went blind

With the full cycle working, the team returned to a key question: What would happen if a payload that was previously blocked by the WAF now traveled within the functional JSON, but was re-encrypted before going out to the backend?

Figure 28. Example of SQLi payload located within the decrypted functional JSON, before re-encrypting the request to send it to the backend.
Figure 28. Example of SQLi payload located within the decrypted functional JSON, before re-encrypting the request to send it to the backend.
Figure 29. Comparative example where the payload is visible for inspection and the intermediate control returns a block.
Figure 29. Comparative example where the payload is visible for inspection and the intermediate control returns a block.

The answer was the critical point of the research. The middle control wasn't decrypting, analyzing, and re-encoding the content. It was inspecting the encrypted body as if it were plaintext. When the actual payload traveled inside the encrypted blob, the middle control couldn't see it, and the request continued its path to the backend.

The backend, however, did decrypt the content and process the functional JSON. In other words, the architecture created a dangerous discrepancy between what the WAF could inspect and what the application actually executed.

Figure 30. Additional example of the decrypted request and the injected payload within a functional parameter, with the response processed by the backend.
Figure 30. Additional example of the decrypted request and the injected payload within a functional parameter, with the response processed by the backend.

This scenario doesn't prove that WAFs are useless or that encryption is inherently flawed. It demonstrates that controls must be placed at the correct point in the flow. If inspection occurs before decryption and server-side validation doesn't compensate for this lack of visibility, intermediate controls can remain blind to critical payloads.

5. Critical Finding: SQL Injection OOB

From that point on, the investigation ceased to be merely an exercise in reverse engineering and tooling. The ability to send payloads within the encrypted content allowed for the validation of behavior consistent with out-of-band SQL injection.

Figure 31. Payload validation within the encrypted flow and response observed during the test.
Figure 31. Payload validation within the encrypted flow and response observed during the test.
Figure 32. OOB interactions received on infrastructure controlled by the evaluation team.
Figure 32. OOB interactions received on infrastructure controlled by the evaluation team.

The weakness wasn't solely the presence of an SQL injection vulnerability. The broader lesson is that a client-side encryption layer implemented as a black box can create a false sense of security if the backend and intermediate controls aren't designed to validate the encryption after the decryption point.

6. Defensive learning

The main point is that application-level encryption can be useful for protecting data in certain (debatable) scenarios, but it shouldn't replace server-side controls or become a trusted boundary. This is where the false sense of security it creates comes from.

  1. The mobile application should be considered an untrusted environment. All logic sent to the client can be studied, instrumented, and reproduced by an attacker with control of their own device.
  2. Inspection controls must operate on normalized, plaintext data. If a WAF, API gateway, or validation engine only observes encrypted blobs, it cannot make effective decisions about the actual content.
  3. The backend must validate inputs after decryption, regardless of whether the request arrived encrypted and in an apparently valid format.
  4. Proprietary encryption designs should be reviewed as part of threat modeling. It is not enough to verify that the cryptography works; it is also necessary to validate how it affects observability, monitoring, detection, and response.
  5. Mobile security testing should include scenarios where the team reproduces the legitimate customer flow, modifies the functional content, and re-encodes it before sending it to the backend.
  6. Evidence of WAF blocking must be validated within the actual application flow. A block observed on plaintext payloads does not guarantee protection if those payloads are encapsulated or encrypted in production.
  7. It is always recommended to perform Mobile Security Assessments recursively.
  8. The development team must be trained to ensure that the application development is carried out securely, minimizing all risks to the applications.

7. Conclusion

This research shows how a defensive decision can have unforeseen side effects. Encrypting traffic at the application level may increase the difficulty of passive analysis, but if the architecture leaves the controls that should inspect and validate the content blind, the protection becomes incomplete, significantly diminishing its responsiveness.

In this case, the team's technical approach was clear:

  1. Identify the pattern.
  2. Study the customer's logic.
  3. Instrument values at runtime.
  4. Play the encryption.
  5. Integrate it into Burp Suite.
  6. Verify that the intermediate controls did not see the real payload.

The conclusion is not that encryption is the problem. The problem arises when you rely on client secrecy and assume that an encrypted blob equates to a secure request.

Effective security requires server-side validation, controls implemented after decryption, and an architecture that doesn't rely on the attacker's ignorance of how the application works. It also requires code-level mitigations, where vulnerabilities like SQL injection should be addressed from the very beginning of development.

Implementing layered protection remains one of the most effective approaches today; the important thing here is to be able to minimize risk by reviewing what was mentioned above.

Don't miss part III, where we'll show more examples at API level, insecure encryption, frameworks, and more!

Stay tuned to the following posts and don't forget to follow us!!



Try Tungstenic: Next-level mobile application vulnerability protection.


Contact Us

If you need to analyze your application contact us here:

sales@justmobilesec.com | Sales

Any other doubts or questions, don’t hesitate to write to us!

info@justmobilesec.com | Contact

Work with us!

Join us! | Jobs