Use Burp Suite Community Edition for manual API security testing, follow the OWASP WSTG checklist, and document findings professionally with CVSS scores.
Automated security tools catch known patterns. They miss logic flaws, business rule violations, and novel attack chains requiring human creativity. A manual penetration test discovers these gaps. For developers, basic pentest skills help you understand how defenses are tested and what 'secure by design' means at the HTTP protocol level.
Burp Suite Community Edition is free and the industry standard for manual web security testing.
1# Download Burp Suite Community Edition:
2# https://portswigger.net/burp/communitydownload
3
4# Or use mitmproxy (open source, CLI):
5pip install mitmproxy
6mitmweb # opens browser UI at http://127.0.0.1:8081Configure Burp as a proxy:
Key Burp tools:
IDOR Testing Workflow:
1# Original request (User A's session):
2GET /api/orders/order-abc123 HTTP/1.1
3Host: api.staging.example.com
4Authorization: Bearer eyJhbGci...[UserA Token]...
5
6# Modified in Repeater (User B's session):
7GET /api/orders/order-abc123 HTTP/1.1
8Host: api.staging.example.com
9Authorization: Bearer eyJhbGci...[UserB Token]...
10
11# Expected (safe): 403 Forbidden
12# Vulnerable: 200 OK with order-abc123 dataAlso test:
The OWASP Web Security Testing Guide provides a systematic checklist. Key items for REST APIs:
1Authentication (AUTHN):
2[ ] WSTG-AUTHN-01: Credentials in transport (HTTP vs HTTPS)
3[ ] WSTG-AUTHN-03: Lockout mechanism after N failures
4[ ] WSTG-AUTHN-06: Browser/proxy cache bypass for auth responses
5
6Authorization (AUTHZ):
7[ ] WSTG-AUTHZ-02: Bypass authorization schema (direct object access)
8[ ] WSTG-AUTHZ-04: IDOR — cross-user resource access
9
10Input Validation (INPVAL):
11[ ] WSTG-INPVAL-05: SQL injection
12[ ] WSTG-INPVAL-07: XML/JSON injection
13
14API-Specific Tests:
15[ ] 1. JWT algorithm confusion (alg:none, RS256→HS256)
16[ ] 2. Mass assignment (POST/PATCH with unexpected fields)
17[ ] 3. Excessive data exposure (response has unexposed fields)
18[ ] 4. Broken function-level authorization (user accesses admin endpoints)
19[ ] 5. Rate limiting on authentication endpoints
20[ ] 6. CORS misconfiguration (echo Origin header)
21[ ] 7. HTTP method override headers (X-HTTP-Method-Override)
22[ ] 8. GraphQL introspection enabled in production1# Test mass assignment:
2curl -X PATCH https://api.staging.example.com/user/profile \
3 -H 'Authorization: Bearer TOKEN' \
4 -H 'Content-Type: application/json' \
5 -d '{"name":"hacked","role":"admin","isVerified":true}'
6# Expected: role and isVerified ignored (ValidationPipe whitelist)
7# Vulnerable: user gains admin roleSecurity findings must be documented with enough detail for developers to reproduce and fix them.
1## Finding: IDOR in Order API
2
3**Severity**: HIGH (CVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N = 6.5)
4**OWASP**: A01:2021 Broken Access Control
5**CWE**: CWE-284
6
7### Description
8Authenticated users can access orders belonging to other users by modifying
9the order ID in GET /orders/:id. No ownership verification is performed.
10
11### Reproduction Steps
121. Authenticate as User A ([email protected] / TestPass123!)
132. Create order: POST /orders → orderId: order-abc123
143. Authenticate as User B ([email protected] / TestPass123!)
154. Request: GET /api/orders/order-abc123
16 with User B's Authorization header
175. Observe: 200 response with User A's order data
18
19### Evidence
20HTTP/1.1 200 OK
21{"id":"order-abc123","userId":"user-A-id","total":299.99,...}
22
23### Business Impact
24Any authenticated user can read, modify, or cancel orders belonging
25to other users — exposing payment amounts and delivery addresses.
26
27### Remediation
28// Add to OrdersController:
29if (order.userId !== currentUser.id) throw new ForbiddenException();
30
31### References
32- OWASP WSTG-AUTHZ-04
33- CWE-284Automated tools find:
Manual testing finds:
1// Race condition example — automated tools cannot detect this
2// Two simultaneous enrollment requests for the same paid course:
3const [res1, res2] = await Promise.all([
4 request(app.getHttpServer())
5 .post('/progress/enroll')
6 .set('Authorization', 'Bearer ' + userToken)
7 .send({ courseId: 'paid-course-123' }),
8 request(app.getHttpServer())
9 .post('/progress/enroll')
10 .set('Authorization', 'Bearer ' + userToken)
11 .send({ courseId: 'paid-course-123' }),
12]);
13// Vulnerable: both succeed, user gets two enrollments for one payment
14// Fixed: database UNIQUE constraint on (userId, courseId)
15expect([res1.status, res2.status]).toContain(409); // one must conflictCadence recommendation:
An automated security check script that validates basic controls before a manual penetration test, covering JWT, CORS, headers, and SSRF.
1// scripts/pre-pentest-check.cjs
2// Run: node scripts/pre-pentest-check.cjs https://staging.api.example.com
3const https = require('https');
4const http = require('http');
5
6const BASE = process.argv[2] || 'http://localhost:3000';
7const TOKEN = process.argv[3] || '';
8
9async function fetchUrl(url, options) {
10 return new Promise((resolve, reject) => {
11 const { headers = {}, method = 'GET', body } = options || {};
12 const parsed = new URL(url);
13 const client = parsed.protocol === 'https:' ? https : http;
14 const req = client.request(
15 { hostname: parsed.hostname, port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
16 path: parsed.pathname + parsed.search, method,
17 headers: { ...headers, 'User-Agent': 'PentestPrecheck/1.0' } },
18 (res) => {
19 let data = '';
20 res.on('data', c => data += c);
21 res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: data }));
22 }
23 );
24 req.on('error', reject);
25 if (body) req.write(body);
26 req.end();
27 });
28}
29
30let passed = 0, failed = 0;
31
32function check(name, condition, detail) {
33 const status = condition ? 'PASS' : 'FAIL';
34 if (condition) passed++; else failed++;
35 console.log('[' + status + '] ' + name + (detail ? ': ' + detail : ''));
36}
37
38async function main() {
39 console.log('=== Pre-Pentest Security Check ===');
40 console.log('Target: ' + BASE + '\n');
41
42 // Security Headers
43 console.log('--- Security Headers ---');
44 const healthRes = await fetchUrl(BASE + '/api/health');
45 const h = healthRes.headers;
46 check('X-Content-Type-Options: nosniff', h['x-content-type-options'] === 'nosniff', h['x-content-type-options']);
47 check('X-Frame-Options: DENY', h['x-frame-options'] === 'DENY', h['x-frame-options']);
48 check('No X-Powered-By', !h['x-powered-by'], h['x-powered-by'] || 'absent');
49 check('CSP present', !!h['content-security-policy']);
50 check('CSP no unsafe-inline', !h['content-security-policy']?.match(/script-src.*unsafe-inline/));
51
52 // JWT algorithm confusion
53 console.log('\n--- Authentication ---');
54 const noneToken = Buffer.from(JSON.stringify({ alg: 'none', typ: 'JWT' })).toString('base64url') + '.' +
55 Buffer.from(JSON.stringify({ sub: 'admin' })).toString('base64url') + '.';
56 const noneRes = await fetchUrl(BASE + '/user/profile', {
57 headers: { Authorization: 'Bearer ' + noneToken },
58 });
59 check('JWT alg:none rejected', noneRes.status === 401, 'status: ' + noneRes.status);
60
61 // Rate limiting
62 const loginAttempts = await Promise.all(
63 Array.from({ length: 15 }, () =>
64 fetchUrl(BASE + '/auth/login', {
65 method: 'POST',
66 headers: { 'Content-Type': 'application/json' },
67 body: JSON.stringify({ email: '[email protected]', password: 'wrong' }),
68 })
69 )
70 );
71 const limited = loginAttempts.filter(r => r.status === 429);
72 check('Login rate limiting (15 rapid requests)', limited.length > 0, limited.length + '/15 rate limited');
73
74 // CORS
75 console.log('\n--- CORS ---');
76 const corsRes = await fetchUrl(BASE + '/api/courses', {
77 headers: { Origin: 'https://evil-attacker.com' },
78 });
79 const acao = corsRes.headers['access-control-allow-origin'];
80 check('Evil origin not reflected', acao !== 'https://evil-attacker.com', 'ACAO: ' + (acao || 'absent'));
81
82 // Information Disclosure
83 console.log('\n--- Information Disclosure ---');
84 const errorRes = await fetchUrl(BASE + '/api/courses/not-a-valid-uuid',
85 TOKEN ? { headers: { Authorization: 'Bearer ' + TOKEN } } : {});
86 check('No stack trace in errors', !errorRes.body.match(/at Object\.|node_modules/));
87 check('No DB info in errors', !errorRes.body.match(/prisma|postgres|sql/i));
88
89 // Summary
90 console.log('\n=== Results: ' + passed + ' PASS, ' + failed + ' FAIL ===');
91 if (failed > 0) {
92 console.error('Fix FAIL items before the manual penetration test.');
93 process.exit(1);
94 }
95 console.log('Ready for manual penetration test.');
96}
97
98main().catch(err => { console.error(err); process.exit(1); });This pre-pentest script automates the baseline security checks a penetration tester performs in the first 30 minutes. It validates: security headers (must all pass), JWT algorithm confusion resistance, rate limiting effectiveness, CORS misconfiguration, and information disclosure in error messages. Running this before a manual pentest ensures the tester's time is spent on complex logic flaws rather than re-discovering basic misconfigurations. All checks print PASS/FAIL and exit non-zero when failures exist.
The final lesson covers building a complete DevSecOps pipeline — integrating SAST, DAST, SCA, and secrets detection into a GitHub Actions security workflow with gate enforcement.
Your progress is saved in this browser, on this device only.