What You'll Build
In this phase you will fix the vulnerabilities you confirmed, applying the root-cause remediations taught throughout the course, and then re-test each one to prove it is closed. The deliverable is a set of code fixes plus documented re-test results showing every finding now fails to exploit. This closes the loop from discovery to verified resolution.
The aim is to fix at the root, not to paper over symptoms, and to prove closure rather than assume it. For each finding you will apply the structural fix, parameterisation, output encoding, authorization checks, pinned JWT algorithms, rate limits, then re-run the exact exploit that previously worked and confirm it now fails. Documented re-testing is what turns a claimed fix into a verified one.
Prerequisites
- The confirmed, evidenced findings list from Lesson 33, plus access to the capstone application's source code to apply fixes.
- Completion of the Modules 2–4 readings, whose root-cause remediations — parameterisation, encoding, authorization, JWT hardening — you will apply here.
- Your engagement log open, ready to record each fix applied and each re-test result as evidence of closure.
- An understanding that all work targets your local capstone app, and that these techniques apply only to systems you are authorised to test.
Setup & Project Structure
Work through your findings list one finding at a time, applying the root-cause fix in the source and then re-testing. For each, record the vulnerability, the fix applied with a code reference, and the re-test result showing the previous exploit now fails. Keep fixes minimal and targeted so each maps clearly to one finding, making the remediation section of your report easy to follow and verify.
# Remediate finding-by-finding, documenting fix + re-test each time.
# For each finding, record in remediation.md:
# - finding id + vuln class
# - root-cause fix (file + change summary)
# - re-test: the exact previous exploit, now returning a safe result
# Apply fixes in the capstone source, restart the app, and re-run exploits
# against http://localhost:3000 (your machine only).Step 1 — Foundation: Fix Injection at the Root
Start with the injection findings. Replace concatenated SQL with parameterised queries so input can never alter the command, and apply contextual output encoding so reflected input renders as text rather than executing. These are the structural fixes from Module 2, not input filters. After applying each, restart the app and move to re-testing to confirm the exploit no longer works.
// Injection remediations (root-cause, from Module 2)
// SQLi: parameterise the search query
const rows = db.prepare('SELECT * FROM items WHERE name LIKE ?').all(`%${q}%`);
// (q is bound as data; ' OR '1'='1 becomes a literal string)
// Reflected XSS: encode output for the HTML context
res.send(`<p>Results for: ${escapeHtml(q)}</p>`); // tags rendered as text
// Restart the app, then re-test in Step 4-style verification below.Step 2 — Core Logic: Harden Authentication
Next, fix the authentication and session findings. Pin the JWT verification algorithm and reject none so forged tokens fail, and replace any weak secret with a strong one. Regenerate the session identifier at login and set secure cookie flags. Add a rate limiter to the login endpoint. Each fix maps to a Module 3 lesson and directly neutralises a confirmed finding, ready for re-testing.
// Auth remediations (root-cause, from Module 3)
// Pin the JWT algorithm and use a strong secret -> defeats 'none' + weak signing
jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'],
issuer: 'capstone', audience: 'capstone' });
// Regenerate session at login + secure cookie flags
req.session.regenerate(() => { req.session.userId = user.id; });
res.cookie('sid', id, { httpOnly: true, secure: true, sameSite: 'lax' });
// Rate-limit login -> defeats brute force
app.post('/login', rateLimit({ windowMs: 15*60*1000, max: 5 }), handleLogin);Step 3 — Integration & Enhancement: Enforce API Authorization
Now fix the API findings. Scope every object lookup to the authenticated caller so broken object level authorization is closed, add explicit role checks so privileged routes reject normal users, and project responses to safe fields so sensitive data is not exposed. These Module 4 remediations enforce authorization on the server for every request, which is exactly what the confirmed findings showed was missing.
// API remediations (root-cause, from Module 4)
// BOLA: scope object access to the caller
app.get('/api/orders/:id', requireAuth, async (req, res) => {
const order = await db.orders.findOne({ id: req.params.id, ownerId: req.user.id });
if (!order) return res.status(404).end();
res.json(publicOrder(order)); // projection -> no data exposure
});
// BFLA: explicit role check on privileged routes
app.post('/api/admin/refund', requireAuth, requireRole('admin'), issueRefund);Step 4 — Testing & Verification: Re-Test Every Fix
Finally, re-test each finding by running the exact exploit that previously succeeded and confirming it now fails safely. Record the before-and-after for every finding: the previous impact and the new safe result. Any finding that still exploits returns to remediation, not the report as fixed. This documented re-test table is the proof of closure and a key section of your final report.
# Re-test table (proof of closure) — remediation.md
#
# F1 SQLi /search?q=x' OR '1'='1 before: extra rows after: 0 rows (bound) CLOSED
# F2 XSS /search?q=<b>xss</b> before: tag executes after: <b> text CLOSED
# F3 JWT none /admin/config before: 200 admin after: 401 (alg pinned) CLOSED
# F4 BOLA /api/orders/2 as user1 before: other order after: 404 (scoped) CLOSED
# F5 BFLA /api/admin/refund before: succeeds after: 403 (role check) CLOSED
# F6 rate lim 6th login attempt before: 200 after: 429 (limited) CLOSED
#
# Any finding NOT closed returns to remediation before the report is written.Warning: Apply and re-test all fixes only against your local capstone application. Re-running exploits to verify closure sends the same attack payloads, which is unauthorised against any system you do not own. Keep everything on localhost, and never verify a fix by testing against a live or third-party environment without explicit written permission.
Extension Challenge: Strengthen your remediation three ways. Add an automated regression test for each finding, in the style of earlier labs, so a future change cannot silently reopen a fixed flaw. Add a Content Security Policy as defence in depth behind the XSS fix, demonstrating layered protection. Finally, re-run the SAST/DAST tools from Module 5 against the fixed app and confirm the previously reported findings no longer appear.
- Remediation fixes each confirmed finding at its root — parameterisation, encoding, authorization, JWT hardening, rate limits — not with symptom-level filters.
- Each fix is kept minimal and targeted so it maps clearly to one finding, making the remediation report easy to follow and verify.
- Injection is closed with parameterised queries and contextual output encoding; authentication with pinned JWT algorithms, session regeneration, cookie flags, and rate limits.
- API flaws are closed by scoping object access to the caller, adding explicit role checks, and projecting responses to safe fields.
- Every fix is re-tested by re-running the exact exploit that previously worked and confirming it now fails safely, with before-and-after recorded.
- A finding is only 'fixed' once re-testing proves closure; any still-exploitable finding returns to remediation rather than being reported as resolved.