CSIS study materials
CSIS study resources

Twelve Records, One Bug Bounty, Four Partners

CSIS · Put your knowledge to work in context.

All case studies

Twelve Records, One Bug Bounty, Four Partners

A bug bounty report arrives for a telehealth API. GET /v2/patients/{id} returns the full record when the caller presents any valid OAuth token issued to the patient portal; the handler verifies a token exists but never that its subject may access that patient. Patient IDs are sequential integers starting at 100001. The researcher says she pulled 12 records to confirm the issue and stopped. The mobile client also caches the JSON response in plaintext on device storage and writes request URLs to a third-party analytics SDK. Engineering says it can ship a patch in three days. The API serves 60,000 requests a day from four partner integrations.

1. What do you do in the next 24 hours versus the three-day fix, and what does 'mitigate' actually mean when four partners depend on the endpoint?

Read the worked answer

Three days is not acceptable as the only response; the endpoint is exploitable by anyone with a portal account, and sequential IDs mean enumeration is trivial. The script is for i in range(100001, 200001).

Next 24 hours - reduce exploitability without breaking partners:

  1. Rate-limit and alert on enumeration patterns per token and per IP (high volume of sequential or near-sequential IDs, high 404/403 ratio, single token hitting many patients). This doesn't fix the bug but it removes the trivial bulk-extraction path and gives you detection you currently lack.
  2. Enable access logging on this endpoint if it isn't already - subject, patient ID, timestamp, source - because you cannot scope the incident without it and it does not exist retroactively.
  3. Contact the researcher to confirm scope, timestamps, and whether records were retained or deleted; that is evidence you will need.
  4. Ask the partners what they actually call. The risk of an emergency patch is breaking a partner's integration at 60,000 requests/day. Determine whether all four use patient-scoped calls with their own authorisation context; if any partner relies on cross-patient access (e.g. a scheduling service legitimately reading many patients), that pattern needs an explicit, scoped grant rather than being blocked.
  5. If a safe partial fix exists - a WAF rule restricting the endpoint to a partner allowlist, or gating the endpoint behind a service-level check - deploy it as an interim measure today. A blunt control now plus the correct control in three days beats a clean design shipped into an active exploitation window.

What 'mitigate' means here: the attacker's cost of extracting records goes from 'one loop' to 'requires a token that fails authorisation'. Rate limiting achieves part of that; nothing else in 24 hours achieves the rest without the code change. Do not describe rate limiting as a fix in the incident record.

Compress the three days: pull the fix forward, ship behind a feature flag to one partner first, and have the rollback ready. If engineering says three days, the honest answer is that the first version can usually ship in one.

2. What is the design fix that prevents this class of bug, and why is randomising patient IDs not it?

Read the worked answer

Randomising IDs is obfuscation, not authorisation. It reduces the chance of guessing, but it does nothing about the underlying flaw: the endpoint never asks whether this subject may access this record. An attacker who obtains a legitimate ID - from a shared link, a partner response, a log, a support ticket, a screenshot - still reads the record. It also fails on the insider and confused-deputy paths, where the ID is known. And it complicates integration without changing the security property. Use opaque identifiers if you like them for other reasons; do not count them as the control.

The real fix is centralised, deny-by-default object-level authorisation:

  1. One policy decision point for record access, called by the handler, taking (subject, action, resource, context) and returning allow/deny. Authorisation logic scattered across handlers is how this bug recurs.
  2. Deny by default: a handler that does not call the policy check should fail closed. Enforce this structurally - a route decorator/middleware that refuses to serve patient data unless a policy check has run - not by code review intent.
  3. Ownership modelled explicitly: patient-to-account, patient-to-clinician, patient-to-partner-scope relationships in the data model with their own tests. The check must cover the partner's delegated scope, not just 'is a clinician'.
  4. Test for the negative case in CI: for every patient-data endpoint, an automated test asserts that an authenticated but unauthorised subject receives 403 and that no body leaks. Bug bounty found this because a human tried; the test suite should try every time. Include sequential-ID and enumeration tests.
  5. Detection: alert on subject-to-record fan-out anomalies and on 403 rates per token. If authorisation is working, the attack pattern is visible.

Why the class recurs: it is the most common API flaw because it's invisible to unauthenticated scanners, the happy path works, and reviewers test with an authorised user. The fix that lasts is the middleware that makes forgetting impossible, plus the CI test that proves it.

3. What must you establish before deciding whether this is a reportable incident, and how do the mobile-client findings change the picture?

Read the worked answer

Establish, factually, in this order:

  1. What data class was exposed. A patient record may contain identifiers plus sensitive health information - that classification drives the legal analysis and the timeline. If it's regulated health data, the notification clock may have started when the unauthorised access occurred, not when the report arrives.
  2. Scope of actual unauthorised access. Logs for the endpoint over the exposure window: number of distinct subjects, distinct patient IDs, patterns of enumeration, source IPs. The researcher says 12; that is one data point, and it is not the scope. If logging is absent or insufficient, state that honestly - 'scope unknown' is a finding, and it must be treated as wider rather than narrower.
  3. Whether the researcher's access counts as unauthorised access under the applicable regime. Likely yes, in law, even though it was good-faith research - the notification analysis usually doesn't have a 'helpful attacker' exemption. Get legal to make this call; do not make it in the SOC.
  4. Which partners were affected and what their contracts require - notification obligations to partners are often shorter than the regulator's clock.

The mobile findings change the picture in three ways:

  • They expand the exposure from a server-side authorisation bug to a device-side data-at-rest problem. Plaintext caching means records persist on lost, stolen, or shared devices and survive logout - a separate harm, with a separate population, that the API fix does not address.
  • The analytics SDK is the worst of the three. Request URLs containing patient IDs are being sent to a third-party processor. That is a disclosure to a vendor, potentially a contractual breach and a cross-border transfer issue, and it may be happening to every user continuously - orders of magnitude larger than 12 records. Establish exactly what fields the SDK captures and whether patient identifiers or query parameters are included.
  • It converts the incident from 'contains' to 'assess and contains', and the containment now includes a client release cycle measured in weeks, not a three-day server patch. Plan the notification analysis around that longer tail.

Do not do: close the ticket once the server patch ships, or describe the 12 records as the scope in any external communication.