Exercises
Hands-on practice in a defensive frame: each exercise is a vulnerable snippet plus the task of writing the fix. Run them in an authorised lab only. Working through the exercises is the fastest way to build the intuition that turns code review into a fast pattern-match.
Fix four vulnerable snippets
EXAMPLE
<!-- ============================================================
Exercise 1 — Reflected XSS in a PHP search box
The page echoes $_GET['q'] back as the HTML title.
============================================================ -->
<!-- VULNERABLE (do NOT ship) -->
<?php
$q = $_GET['q'] ?? '';
?>
<h1>Results for <?= $q ?></h1>
<!-- YOUR TASK: rewrite the echo so it cannot inject HTML. -->
<!-- SOLUTION:
<?php $q = $_GET['q'] ?? ''; ?>
<h1>Results for <?= htmlspecialchars($q, ENT_QUOTES | ENT_HTML5, 'UTF-8') ?></h1>
-->
<!-- ============================================================
Exercise 2 — DOM XSS via location.hash
The current code reads the URL fragment and inserts it as HTML.
============================================================ -->
<div id='greeting'></div>
<script>
// VULNERABLE
document.getElementById('greeting').innerHTML =
'Hello, ' + decodeURIComponent(location.hash.slice(1));
</script>
<!-- YOUR TASK: render the fragment as TEXT, not HTML. -->
<!-- SOLUTION:
<script>
document.getElementById('greeting').textContent =
'Hello, ' + decodeURIComponent(location.hash.slice(1));
</script>
-->
<!-- ============================================================
Exercise 3 — Stored XSS in a Blade comment field
A user-supplied comment is echoed with the raw {!! !!} operator.
============================================================ -->
{{-- VULNERABLE --}}
<div class='comment-body'>{!! $comment->body !!}</div>
{{-- YOUR TASK: render the comment safely. If you NEED some HTML
(bold, italic, links), sanitise it instead of escaping. --}}
{{-- SOLUTION 1: plain text only (safest) --}}
{{-- <div class='comment-body'>{{ $comment->body }}</div> --}}
{{-- SOLUTION 2: allow a small whitelist via HTMLPurifier --}}
{{-- <div class='comment-body'>{!! $purifier->purify($comment->body) !!}</div> --}}
<!-- ============================================================
Exercise 4 — JSON drop into a script block
Server-side data passed into a JS variable is escaped as HTML
but NOT for the JS string literal context.
============================================================ -->
<script>
// VULNERABLE — htmlspecialchars is NOT enough here
const user = '<?= htmlspecialchars($user_name, ENT_QUOTES, 'UTF-8') ?>';
</script>
<!-- YOUR TASK: produce a value safe for a JS string literal. -->
<!-- SOLUTION: json_encode with flags makes the value safe both as
HTML and as a JS string. -->
<script>
const user = <?= json_encode($user_name,
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
</script>
<!-- ============================================================
Bonus drill — CSP
Imagine you added a Content-Security-Policy with 'default-src self'.
Which of the four bugs above does CSP still NOT prevent?
============================================================ -->
<!-- Answer: NONE of them. CSP would block inline scripts loaded by an
attacker via injection, but Exercises 1 and 2 inject HTML, not
<script> tags. Always escape correctly even with CSP in place. -->
Why it matters
Stored XSS is the variant that scales — one bad comment can attack every reader. Treat any code path that echoes user-saved data without context-correct escaping as a high-severity bug, regardless of how rarely the endpoint is hit.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…