Arkendia Classrooms
Lesson 13 of 28 · 17 min

Reflected cross-site scripting (XSS)

So far we attacked the server’s logic. Now we turn to the user’s browser. Cross-site scripting, or XSS, lets an attacker make a trusted website deliver malicious script to its own visitors. The browser has no way to know the script was not written by the site, so it runs it with the site’s full privileges.

Legal and ethical reminder

Sending a crafted link to a real person, or injecting script into a real website, is harmful and illegal even as a “joke” or a demonstration. In this course, XSS tests are made only in your own lab (DVWA, OWASP Juice Shop, PortSwigger Web Security Academy) and with harmless markers. When you find XSS on a real site through a bug bounty programme, you report it; you do not use it on others.

A reminder: how a browser treats a page

A web page is made of HTML, and HTML can contain scripts written in JavaScript. The browser reads the page from the server and executes any script it finds. Scripts have powerful abilities: they can read the page content, modify it, send requests with the user’s cookies, and react to what the user types.

There is an important security rule built into browsers, the same-origin policy: a script from one website cannot freely read data from another. XSS defeats that protection by making the malicious script come from the legitimate site itself. From the browser’s point of view, the script belongs to the trusted origin.

What “reflected” means

In a reflected XSS, the application takes a value from the request, and immediately places it in the response page without proper treatment. The value is “reflected” back, like an echo. Nothing is stored; the attack only works when a victim sends that particular request, typically by clicking a crafted link.

A very common source is the search page: you type a term, and the page says “Results for your term”. Here is the server-side code behind a vulnerable version, shown for understanding:

$term = $_GET['q'];
echo "<h2>Results for " . $term . "</h2>";

The visitor’s text goes straight into the HTML. If the text contains HTML markup, the browser will interpret it as markup, not as text. The text has become part of the page’s code, which is the exact same pattern as injection, but this time the interpreter is the browser.

Why it matters

If an attacker can run script in a victim’s browser on the legitimate site, they can potentially:

  • Perform actions as the victim (change their email, post content, make purchases) using their session.
  • Read information displayed on the page, including personal data.
  • Steal session tokens, when the cookie is not protected by HttpOnly (see chapter 2).
  • Rewrite the page to show a fake login form that sends credentials to the attacker.
  • Redirect the visitor to another website.

The attack needs the victim to open a link, which is why social engineering is part of the picture. But links in emails, messages and posts are clicked every day, so the risk is real.

Where input lands: contexts

A decisive concept for testers is the context: the place in the page where your input is reflected. The way to break out is different in each one.

ContextExampleWhat a tester observes
HTML body<p>Hello, NAME</p>Can input add HTML elements?
HTML attribute<input value="NAME">Can input close the attribute or add one?
Inside a script blockvar user = 'NAME';Can input leave the string and add code?
URL<a href="NAME">Does the application accept dangerous URL schemes?

This is why “just filter the angle brackets” is not a real defence: in an attribute or script context, other characters matter.

Testing safely: the marker method

You do not need to run a script to prove that input is reflected unsafely. Use a harmless marker, step by step, in your lab:

  1. Find reflection. Submit a unique, harmless word such as zq7marker in each parameter and search the response in Burp for that word. Every place it appears is a reflection point. Note the context of each.
  2. Check treatment. Submit the marker surrounded by simple, harmless HTML such as emphasis tags. Look at the response: are the angle brackets returned as is, or converted into entities like &lt;?
  3. Decide. If the tags are returned unchanged and the browser displays the marker in italics, the application interprets input as HTML: the injection is confirmed at the HTML level.
Input:    zq7marker <em>test</em>
Safe:     response contains   zq7marker &lt;em&gt;test&lt;/em&gt;   (displayed literally)
Unsafe:   response contains   zq7marker <em>test</em>           (rendered as italics)

This proof is sufficient to report the flaw: you have shown that the application lets users control the page’s markup. The lab’s guided material shows how this can lead to script execution, and you can follow it in DVWA’s “XSS (Reflected)” module or the matching Academy labs. Keep it to those environments.

Common filtering mistakes

At higher DVWA levels you will see typical defences that fail:

  • Removing a specific word once. If a filter deletes a forbidden word one time, variations of the input can still reassemble it. Single-pass filters are weak.
  • Case sensitivity. A filter looking for one spelling can miss another.
  • Blacklisting a few tags. HTML has many ways to run script; the list is never complete.

The takeaway is the same as for injection: blacklists lose. The sound fix is not about guessing what is dangerous, but about treating all user input as text when writing it into a page.

The real fix: output encoding

The correct defence is contextual output encoding: when the application writes a value into a page, it converts characters that have a special meaning in that context into harmless representations. In an HTML body, this means converting < to &lt; and so on, so the browser displays them as literal characters.

// PHP: encode on output
$term = $_GET['q'] ?? '';
echo "<h2>Results for " . htmlspecialchars($term, ENT_QUOTES, 'UTF-8') . "</h2>";

Modern front-end frameworks usually do this encoding automatically when you insert text through their normal templating features, and they warn you in their documentation when you use an “unsafe HTML” option. The risk returns when developers bypass the safe route.

Further layers help: validating input on arrival (rejecting what is clearly not expected, such as letters in a numeric field), marking session cookies HttpOnly, and a Content Security Policy, which we study in lesson 4 of this chapter.

Tip

Reflection often occurs in unexpected places: error pages, “page not found” messages that echo the URL, redirect parameters, and values from HTTP headers. Search for your marker everywhere in the response, not only in the visible page.

Your turn: find and classify reflection points

In your lab, use the marker method on the search feature and on two other parameters. Record, for each reflection, the context (HTML body, attribute, script, URL), whether the angle brackets were encoded, and your conclusion. Then in DVWA, compare the low level with the impossible level’s source code for the “XSS (Reflected)” module.

See a solution

A possible notebook table:

ParameterContextBrackets encoded?Conclusion
q (search)HTML bodyNoReflected XSS confirmed at HTML level (low)
page (pagination)Not reflectedn/aNo finding
lang (language)AttributeYesEncoded, appears safe

In the impossible level, you should see the output being encoded and an anti-forgery token present, which is exactly the fix described above.

Key takeaways

  • XSS makes a trusted site deliver attacker-controlled script to a visitor’s browser, bypassing the same-origin protection.
  • Reflected XSS echoes request data into the response; it needs the victim to send that request, often via a link.
  • The context of reflection (body, attribute, script, URL) determines what a tester must check.
  • Prove it safely with harmless markers and simple markup; do not use real victims.
  • Fix it with contextual output encoding, plus defence in depth (HttpOnly cookies, CSP); blacklists are not a defence.

Next, two variants that are more persistent and sometimes harder to spot: stored XSS and DOM-based XSS.

Advertisement
Log in to save your progress. Log in