← Back to all reports

Plaintext Credentials in Public Cloud Blob, Production API Access Bypassing Bot Protection

Reported Mar 16, 2026
Severity High
Platform Android / Web API
Vulnerability Class Information Exposure (CWE-200)
Target Type Beauty Retail E-commerce
Impact Production API access across three countries

The Risk

A beauty retailer's mobile app pulled its setup from a public cloud storage folder. One forgotten file in that folder still held the production password to the company's online shopping system in plain text, plus a special header that switched off the bot-protection check on gift card and voucher endpoints. Anyone could read this file in their browser. Two web requests were enough to obtain a 24-hour key that worked across the company's web shops in three countries, with no account and no app needed. This was the equivalent of leaving the keys to the front office posted on the company website.

The Vulnerability

A beauty retailer's Android app fetched its runtime configuration from a public cloud storage container at startup. Most of the configuration files in that container were AES-encrypted, showing the team understood the contents were sensitive. One file was not. A plaintext "example" config sat in the same folder, holding the production OAuth client identifier, the production client secret (the literal string secret, never rotated from a development placeholder), and a hardcoded header used to bypass server-side CAPTCHA on voucher and gift card endpoints.

The container itself was anonymously listable, returning more than 5,000 objects including staging configs and unreleased app versions.

The Attack

Step 1: Read credentials from the public blob

A single anonymous HTTP GET to the storage URL returned the JSON config. The relevant fields:

{`{
  "ClientIdKey": "",
  "ClientSecret": "secret",
  "ReCaptchaBypass": {
    "headerKey": "Request-Key",
    "headerValue": ""
  }
}`}

Step 2: Exchange credentials for a production token

A standard client credentials grant against the production authorization server returned an RS256 JWT valid for 86,399 seconds (24 hours), signed by the production issuer with roles ROLE_CLIENT and ROLE_GUEST.

Step 3: Bot Protection bypass

Without the token, requests to the production commerce API were blocked by the WAF / Bot Protection (JavaScript challenge or HTTP 403). With the token attached, every request passed unchallenged. The same token worked against three country sites covering the brand's full European footprint.

Step 4: Full anonymous checkout flow

The token allowed creation of unlimited carts, addition of products, setting of delivery addresses, and progression to the payment step, all programmatically and without bot detection or rate limiting. End-to-end execution against production was confirmed.

Step 5: Server-side CAPTCHA bypass

The static header from the config disabled CAPTCHA protection on voucher and gift card endpoints. The behaviour difference made this unambiguous:

RequestResponseMeaning
Voucher POST with bypass headerHTTP 400 "invalid voucher code"Server processed the request, CAPTCHA skipped
Voucher POST without the headerHTTP 403 "captcha blocked"Server rejected before processing

The Impact

Bot Protection bypass at scale

The Bot Protection is the primary defence against automated abuse of the production commerce API. The leaked credentials defeat it completely. One token refresh per day is enough for sustained automated access: scraping prices, monitoring stock, and exercising checkout flows against the entire NL, BE, and LU operation.

Voucher and gift card abuse

The static bypass header allows automated brute-forcing of voucher codes and gift card balance checks without solving any CAPTCHA. A single attacker with a list of guessable code patterns could harvest valid codes overnight.

Reconnaissance on staging

The same container exposed staging backend hostnames that resolved in DNS and were reachable from the internet, providing intelligence for targeting pre-production environments.

Why the encryption did not help

The team had AES-encrypted every other config file in the container, recognising the data as sensitive. Leaving one plaintext example file with the same production secrets next to the encrypted ones meant the encryption work added zero protection. An attacker reads the plaintext file and skips every protected one.

Remediation

  • Remove the plaintext example file from the public container immediately. This is the fastest fix and ends the credential exposure
  • Rotate the OAuth client secret. Replace the development placeholder with a strong randomly generated value
  • Rotate the CAPTCHA bypass header value, or better, remove the static bypass entirely and use device attestation (Play Integrity on Android, DeviceCheck on iOS)
  • Set the storage container to private and use short-lived signed URLs for app config downloads
  • Reduce the production token lifetime. Twenty four hours is excessive for client credentials issued to a mobile app. Use shorter-lived tokens with refresh rotation
  • Audit the container for any other plaintext files containing secrets. Add a continuous scan that flags new objects checked into public storage