Full-scope web application penetration test of a SaaS platform for asset management and compliance workflows. Eight findings identified including Stored XSS, high-severity CVEs, and session management flaws. One finding challenged by the client's AI reviewer — and successfully defended.
Plia is a SaaS platform serving asset management and compliance teams, providing questionnaire workflows, counterparty management, and reporting tools. The engagement covered external web application testing including authentication mechanisms, session management, API endpoint analysis, and profile management functionality, conducted under OSSTMM v3 methodology.
Initial discovery confirmed the application was fronted by an AWS Elastic Load Balancer with dynamically rotating IPs. No WAF integration was detected during testing. HSTS was enforced with a two-year max-age including subdomains. Zone transfer testing returned no vulnerabilities.
The RAV (Results, Actual Value) score measures the balance between operations, vulnerabilities, and controls — where 100rav represents perfect balance. An attack surface above 10% is considered problematic reactive security. Above 20% is considered likely already breached.
The current score places Plia just above the 10% problematic threshold — indicating the application has meaningful controls in place but carries compounding risk from the identified session management and injection vulnerabilities. Resolving all findings reduces the attack surface from 11.08% to 1.01%.
The application uses moment.js version 2.9.0, which is affected by a path traversal vulnerability in its locale loading functionality. An attacker could manipulate locale parameters to access files outside the intended directory. While the specific exploit parameters were not identified as active in this application's implementation, the library version itself carries high-severity risk and should be updated immediately.
The application uses underscore.js version 1.6.0, which is vulnerable to arbitrary code execution via the template function when processing user-controlled input. Testing did not find evidence that underscore.template was called with user input in this application, meaning the CVE was not actively exploitable during the engagement. However, the outdated library version represents ongoing risk and the vulnerable code path exists in the codebase.
The Name field in the profile management interface at app.plia.com/#management/profile was not sanitized or encoded before rendering. An authenticated user could inject arbitrary JavaScript payloads via the Name field, which were then persistently stored and executed in the browser context of any other authenticated user who accessed their own profile management page.
A proof-of-concept payload was injected using Pete Herzog's test account. The injected payload loaded an external image and triggered a JavaScript alert displaying "st4rry" — confirmed executing in another authenticated user's browser session, demonstrating full cross-user script execution.
The application uses three cookies for session handling: plia.apiKey, plia.apiToken, and _session_id. Testing confirmed that only _session_id was required for authentication — arbitrary or invalid values for apiKey and apiToken did not affect authorization. The server returned authenticated user data based solely on _session_id, indicating the additional tokens provided no meaningful security benefit.
This means the application's apparent multi-factor session binding was not enforced server-side, reducing the effective authentication surface to a single reusable identifier.
An authenticated session cookie set was exported from a browser after login and imported into a fresh, unauthenticated browser instance. Without performing any authentication, the /user_session.json endpoint returned full authenticated user details and issued a new session identifier. This behavior persisted even after the original session should have expired, enabling session replay attacks using previously captured cookies.
The /sso_request.json endpoint responded differently for valid versus non-existent users — returning 0 for valid accounts and null for non-existent ones. A simple Bash enumeration script was used to confirm valid account existence for multiple email addresses without any rate limiting or authentication required. The lack of response normalization and absence of rate limiting made this fully exploitable as written.
When the application detected scanning or abusive activity from a session, it froze all active sessions associated with that user account for approximately 20 minutes — not just the abusing session. This overly broad control means an attacker could intentionally trigger the abuse detection mechanism against a target account, causing a denial of service for the legitimate user while the lockout persists.
The SSO step at /sso_request.json did not perform authentication or identity validation. Both valid and invalid identifiers were accepted without affecting authentication outcomes, reducing the security value of the SSO step and indirectly contributing to the user enumeration surface. The SSO identifier was not bound to the subsequent authentication flow.
After the report was delivered, the client reviewed the findings using an AI assistant. One finding — the session authentication weakness relating to the HMAC-based authentication design — was flagged by the AI as incorrect. The AI argued that the existing HMAC design using api_key and api_signature was secure by design, and that the pentest observation about cookie behavior was actually correct application behavior.
Rather than accepting the challenge or withdrawing the finding, the technical argument was laid out clearly and directly to the client's mentor.
The HMAC-based authentication using api_key and api_signature is secure by design. The observation that plia.apiKey and plia.apiToken cookies don't affect authorization is correct behavior — the backend validates via query parameters using HMAC signatures, not cookies. No code changes needed.
The HMAC may be secured by hashing, but in this case: any of those values (api_key, api_signature, apiKey, apiToken) can be anything and still make a successful request as long as the session_id is correct. If the backend validates via the session_id alone and the other parameters are effectively ignored, that is a failure of resilience control — the combination requirements are not actually required. From the outside, if the system accepts a request as long as the right session_id is presented, that is a problem regardless of what the HMAC design intends.
The developer reviewed the argument and confirmed: "The penetration tester is correct on both counts." The HMAC validation passes but the session_id alone effectively controls authorization. The finding was validated. The client passed the message to Pete Herzog: "Tell Starry good work."
Security testing is not just about running tools. It is about understanding what findings mean at a systems level — well enough to explain and defend them under scrutiny, including against an AI reviewer backed by the client's own developer. The finding stood because the reasoning was sound.
| # | Finding | Severity | CVSS |
|---|---|---|---|
| 1 | Session auth relies solely on reusable _session_id | Medium | 5.3 |
| 2 | Session re-hydration via /user_session.json | Medium | 5.3 |
| 3 | User enumeration via differential API responses | Medium | 5.3 |
| 4 | Global session freeze on abuse detection — DoS risk | Low | 3.7 |
| 5 | SSO identifier decoupled from authentication flow | Info | — |
| 6 | CVE-2022-24785 — moment.js 2.9.0 | High | 7.5 |
| 7 | CVE-2021-23358 — underscore.js 1.6.0 | High | 7.2 |
| 8 | Stored XSS in user profile management interface | Medium | 5.4 |
Following delivery and technical review — including a challenge from the client's AI reviewer that was successfully rebutted — the client confirmed the findings and passed direct feedback through Pete Herzog: "Tell Starry good work."