August 13, 2026
How a Broken “Preview” Button Exposed 800,000 Private Documents for a $8,500 Bounty
If there’s one lesson every bug hunter learns the hard way, it’s this: The most dangerous features are often the ones built purely for user…

By T4nv1
2 min read
If there's one lesson every bug hunter learns the hard way, it's this: The most dangerous features are often the ones built purely for user convenience.
A few months ago, I was poking around a major cloud-based HR and payroll platform. Companies use this service to manage everything from employee contracts and tax forms to NDA agreements.
Naturally, their main authentication endpoints were pristine. They had multi-factor authentication everywhere, strict rate limits, and short-lived JWT tokens.
Then, I noticed a minor feature inside the employee onboarding portal: "Document Preview."
Before signing a contract, employees could click a tiny magnifying glass icon to preview the PDF directly inside their browser window. That simple convenience button turned out to be the gateway to a $8,500 bounty.
Phase 1: The Predictable Identifier
When you click "Preview," the frontend application makes an API call to fetch the document stream. I intercepted the request in Burp Suite to see how it worked.
GET /api/v2/documents/preview?doc_id=DOC-883921-PDF
The first thing I tested was a classic Insecure Direct Object Reference (IDOR). I changed the document ID from DOC-883921-PDF to DOC-883920-PDF.
The server responded with: 403 Forbidden - Unauthorized Access.
Good. The backend was actually checking if my logged-in account owned DOC-883920-PDF. It seemed like a dead end.
Phase 2: The "Export to Image" Side-Door
I decided to look closer at how the preview was rendered. Instead of serving the raw PDF file directly, the application rendered the first page of the document as a temporary PNG image so users couldn't copy-paste the text before signing.
When I looked at the network logs, I noticed that clicking "Preview" actually triggered a secondary, hidden API request in the background:
POST /api/v2/render/thumbnail
{"document_path": "s3://hr-private-docs-prod/contracts/2026/DOC-883921-PDF.pdf", "page": 1}
My jaw dropped. The client-side JavaScript was telling the backend server the exact internal storage path of the file.
The application didn't validate if I owned the file path sent in the POST body — it only checked permissions on the original doc_id from the GET request.
The Twist: The Path Traversal Escalation
I immediately modified the document_path parameter to point to a document belonging to another organization:
POST /api/v2/render/thumbnail
{"document_path": "s3://hr-private-docs-prod/contracts/2026/DOC-000001-PDF.pdf", "page": 1}
The Result: The server returned a 200 OK along with a Base64-encoded image string. When I decoded the string, I was looking at a crystal-clear preview image of a CEO's executive employment agreement from a completely different company.
I had found a massive Cross-Tenant IDOR.
[Image: A diagram showing a client bypassing permission checks by directly targeting the internal S3 path parameter in the background rendering service]
I could iterate through document numbers and render page-by-page thumbnails of over 800,000 private corporate documents — tax forms, W-2s, direct deposit details, and government IDs — stored in that bucket.
Key Takeaways for Hunters
- Watch the Background Requests: The primary URL might be secure, but auxiliary microservices (like thumbnail generators, export tools, or converters) often bypass standard authorization checks.
- Look for Client-Controlled Paths: Anytime you see an API accepting an internal path string (like
s3://or/var/www/), test it for logic bypasses and path traversal. - Check the Rendered Output: Sometimes apps don't leak the raw file, but they will happily render a picture of the file for you.