October 1, 2026
How a Predictable Activity ID Bypassed an Unguessable Invite Link and Earned Me a $1,000 Bounty
How a Predictable Activity ID Bypassed an Unguessable Invite Link and Earned Me a $1,000 Bounty

By Adini Rodini Rodney
4 min read
Researcher: papjm Vulnerability: Insecure Direct Object Reference (IDOR) Initial bounty: $1,000 Total rewards including retests: $1,200
The company, application, hostname, API paths, usernames, and identifying values have been redacted.
Summary
I discovered an IDOR vulnerability in an activity-planning application that allowed an uninvited user to access private activity details and retrieve the activity's invitation link.
The application protected private activities using long, unguessable invitation links. An activity creator was expected to share this link only with people permitted to participate.
However, the backend also identified each activity using a short, sequential numeric ID. This identifier appeared inside a delete request and could be used with a GET request to retrieve the private activity.
The server did not verify whether the requesting user had created or been invited to the activity.
How Private Activities Were Supposed to Work
When creating an activity, the organizer could set its visibility to private.
A private activity was not supposed to be discoverable by other users. Instead, the organizer received a long, unguessable invitation link that had to be shared directly with the intended participants.
The expected access model was therefore:
- The organizer creates a private activity.
- The application generates an unguessable invitation link.
- The organizer sends the link to selected participants.
- Only users who receive the link can access the private activity.
At first, this appeared secure because guessing the complete invitation link would have been extremely difficult.
The problem was that the same activity was represented internally by a much shorter and predictable numeric ID.
Step-by-Step Discovery
Step 1: Create a Private Activity
I signed in using my first account and started creating a new activity.
I completed the creation process by:
- Selecting myself as the activity organizer.
- Choosing a destination.
- Continuing without adding a route.
- Entering the required activity information.
- Changing the visibility from public to private.
- Finishing the creation process.
The application created the private activity and generated an unguessable invitation link for it.
At this point, another user should not have been able to access the activity unless I deliberately shared that link with them.
Step 2: Discover the Internal Activity ID
I opened the private activity and selected the option to delete it while monitoring the application's network traffic through Burp Suite.
The application generated a request similar to:
DELETE /[REDACTED]/[ACTIVITY_ID]DELETE /[REDACTED]/[ACTIVITY_ID]The exact hostname and API path have been removed from this write-up.
The important part of the request was the ACTIVITY_ID. Instead of using the long invitation token, the backend referenced the activity using a short numeric identifier.
I recorded the identifier and dropped the delete request so that the activity would remain active.
Step 3: Test the Identifier Using Another Account
I signed out of the organizer's account and signed in using a second account.
This second account:
- Did not create the private activity.
- Had not been invited to it.
- Did not possess the unguessable invitation link.
- Should not have been permitted to view it.
Using the second account, I took the endpoint discovered through the delete request and changed the HTTP method from DELETE to GET:
GET /[REDACTED]/[ACTIVITY_ID]GET /[REDACTED]/[ACTIVITY_ID]The server returned:
HTTP/2 200 OKHTTP/2 200 OKThe response contained the private activity's information, including its invitation link.
This meant that an unauthorized user could bypass the long, unguessable invitation link entirely. The user only needed the much shorter internal numeric identifier.
Step 4: Test for Enumeration
I then changed the numeric activity ID to nearby values.
Because the identifiers were sequential, requesting different numbers returned information about other private activities.
For example, if one valid activity used an identifier such as:
5279652796an attacker could test nearby values by incrementing or decrementing the number.
This process could be automated using a tool such as Burp Intruder. Requests that returned 200 OK indicated valid activities.
The attacker did not need to guess the unguessable invitation links. The vulnerable API returned those links after the attacker supplied a valid numeric activity ID.
Why This Was Vulnerable
The application relied on the secrecy of the invitation link to protect private activities.
However, the backend API did not enforce the same access restrictions.
When it received an activity ID, the server should have checked whether the requesting user was:
- The activity creator.
- An invited participant.
- An authorized member of the activity.
- Otherwise permitted to access the requested object.
Instead, the server returned the object solely because the supplied numeric ID existed.
This was a classic IDOR caused by missing object-level authorization.
Impact
An attacker with an ordinary account could potentially:
- Access private activities without receiving an invitation.
- View information belonging to private activities.
- Retrieve supposedly unguessable invitation links.
- Share those invitation links with additional unauthorized users.
- Enumerate other private activities using sequential IDs.
- Automate the collection of private activity information.
The unguessable invitation link provided little protection because a separate API endpoint exposed the same resource through a predictable identifier.
Disclosure and Resolution
I reported the vulnerability in October 2023.
The affected API was primarily associated with the company's mobile application, even though it was also accessible through a web interface. The company accepted the report and awarded me a $1,000 bounty.
In June 2025, I was invited to retest the vulnerability. I repeated the original process and confirmed that the issue was still reproducible. I received an additional $50 retest reward.
In June 2026, the company contacted me for another retest after attempting to block access to the vulnerable endpoint.
My initial test showed that the vulnerability still worked. The company then discovered that the affected infrastructure existed in multiple regions and that each region needed to be reconfigured.
After the remaining infrastructure was updated, I tested the endpoint again. Unauthorized requests now returned:
HTTP/2 403 ForbiddenHTTP/2 403 ForbiddenThe private activity details and invitation link were no longer accessible. The company resolved the report and awarded me another $150 retest reward.
Lessons Learned
A long, unguessable link does not protect a resource if the same resource is available through another endpoint using a predictable identifier.
Whenever an application places an object ID inside a request:
- Test the identifier from a different account.
- Test whether the object is private or user-specific.
- Try other supported HTTP methods.
- Check whether the identifiers are sequential.
- Confirm that the server validates ownership or membership on every request.
Identifiers are references โ not authorization controls. Every request for a private object must be protected by a server-side permission check.