During the initial reconnaissance phase, I spent a few hours just getting a feel for the application and mapping its attack surface. It had the usual suspects: "Send to email," "Ask AI," "Set reminders," and, crucially, "Share with others" (collaboration).One feature particularly fascinated me: user collaboration. The application allowed users to share notes and attachments with varying levels of access. By default, free-tier users were restricted to granting "View Only" permissions. Accessing the second and third privilege tiers required an upgrade to a "Pro" subscription. This is exactly the kind of paywall boundary I love to test.
I decided to capture the legitimate request for granting standard "View Only" access. Firing up Burp Suite, I observed the following body being sent in a POST request:
Endpoint: POST /api/v1/resources/{resource_id}/share/private/users
JSON
JSONString={"users":[{"email_id":"victim@example.com","permission":"READ_ONLY"}]}As soon as I saw the permission parameter being defined directly in the client-side request, I knew exactly what to try next.
I intercepted a new sharing request and modified the value from "READ_ONLY" to "READ_WRITE".
BOOM 💥. The server returned a 201 Created status. I checked my victim account, and sure enough, it now had the second-level "Edit" privileges—a paid feature unlocked for free.
But I wasn't satisfied yet. I wanted the highest privilege tier: full control to share, edit, and view. I started by trying common enumerations for this, like ADMIN, OWNER, or FULL_ACCESS, but none of my guesses worked.
After about an hour of hitting a wall, I took a step back and restarted my hunt. Instead of guessing, I decided to look for the answer in the source. I began digging through the application's minified JavaScript files. After sifting through tons of code, I finally struck gold. I found a reference indicating that the internal name for the highest privilege level was actually "CO_OWNER".
I threw that value into my repeater tab, hit send, and just like that, I had elevated access.
Key Takeaways
- Never Trust the Client: This is rule number one. The backend explicitly trusted the
permissionvalue sent by the frontend without validating if the user's subscription tier authorized that level of access. - Business Logic is Often Fragile: Monetization controls and paywalls are frequent points of failure. When a feature is grayed out in the UI due to subscription status, always check if the API enforces that restriction or if it's just cosmetic.
- When Guessing Fails, Read the Source: I wasted an hour guessing enum values like
OWNERorADMIN. A targeted search through the client-side JavaScript files revealed the correct"CO_OWNER"value in minutes. Static analysis of JS is a crucial reconnaissance skill. - Incremental Escalation: Don't just stop at the first bypass. Once I confirmed I could get
READ_WRITEaccess, I pushed harder to find the maximum impact possible (CO_OWNER), increasing the severity of the report.
Let's Hunt ! > Satyam > 🔗 Follow me on X