Everyone Can Learn Program
中文 Start learning
Specialty · SecurityLesson 6 of 9Free to read

XSS and path traversal · Cyber Security Basics

How can a comment box run code in other people's browsers?

Course contents

Why you need it

Web apps constantly take text from one person and show it to others: comments, names, file names. If that text is treated as code or as a path, one visitor can attack every other visitor, or read files on your server. This lesson covers the two classic cases: cross-site scripting (XSS) and path traversal.

The intuition

A browser reads a page as HTML. If a comment contains <script>...</script> and you paste it into the page, the browser runs it, in every visitor's browser, with their login cookies.

The fix is to escape on output: turn < into &lt; and > into &gt;, so the browser shows the characters instead of obeying them. Python's html.escape does this.

File names have the same problem. ../ means "go up one folder", so ../../etc/passwd climbs out of your upload folder. Normalise the path (resolve every ..) and then check it still starts inside the allowed folder.

In both cases, prefer an allow-list (accept only what you expect) over a block-list (reject what you've thought of).

Definition

Cross-site scripting (XSS)

An attack where text supplied by a user is shown as HTML, so a script inside it runs in other users' browsers.

Path traversal

An attack where a file name such as `../../secret` makes a program open a file outside the folder it meant to use.

Examples

Escape on output

Try it yourself

The first line is what a careless page would contain; the second is safe to put in HTML.

import html
comment = '<script>steal(document.cookie)</script>'
print("<p>" + comment + "</p>")
print("<p>" + html.escape(comment) + "</p>")
print(html.escape('Tom & "Jerry"'))
Step by step
  1. L3Pasted as-is: a browser would run steal(...).
  2. L4Escaped: the browser shows the text and runs nothing.
  3. L5& and quotes are escaped too, so attributes are safe.

Climbing out of a folder

Try it yourself

os.path.normpath resolves every ... Then we check the result is still inside BASE.

import os
BASE = "/srv/files"

def resolve(name):
    return os.path.normpath(os.path.join(BASE, name))

for name in ["report.pdf", "../../etc/passwd", "img/../logo.png"]:
    path = resolve(name)
    print(path, path.startswith(BASE + "/"))
Step by step
  1. L5Join the folder and the name, then resolve the dots.
  2. L9A normal file stays inside: True./srv/files/report.pdf True
  3. L9../../ climbs to the top: the check catches it./etc/passwd False
  4. L9.. that stays inside is fine./srv/files/logo.png True

Common mistakes

Block-lists

Removing <script> isn't enough: <img src=x onerror=...> runs code too, and so do many other tags. Escape everything on output instead of hunting for dangerous words.

A prefix check without the slash

path.startswith("/srv/files") also accepts /srv/files_backup/secret. Check for the folder plus /.

Quick quiz

1. What does this print?

import html
print(html.escape("<b>hi</b>"))
  1. A
    &lt;b&gt;hi&lt;/b&gt;
  2. B
    <b>hi</b>
  3. C
    hi
Show the answer

Answer: A

Every < becomes &lt; and every > becomes &gt;. A browser shows that as the literal text <b>hi</b>.

2. A sign-up form asks for a username. Which rule is an allow-list?

  1. AReject names that contain `<script>`
  2. BAccept only 3 to 20 letters, digits or underscores
  3. CRemove quotes from the name
Show the answer

Answer: B

An allow-list describes exactly what is accepted. Everything else, including attacks nobody thought of, is rejected.

Practice

3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.

  1. PredictWhere does it point?

    Type exactly what this program prints.

  2. ModifyEscape the comment

    render(name, text) builds the HTML for one comment by pasting both values in. Change it so both the name and the text are escaped with html.escape.

  3. WriteSafe file paths

    Write safe_path(name) for a download page. Join name onto BASE, normalise it, and:

    • return the full path if it is inside BASE (it starts with BASE + "/")
    • otherwise return None

Practise this lesson

Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.

Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.

Terms in this lesson

TermChineseDefinition
Cross-site scripting (XSS)跨站脚本(XSS)Running an attacker's script in other users' browsers through unescaped text.
Escaping转义Replacing special characters such as < with safe codes such as &lt;.
Path traversal路径穿越Using ../ in a file name to reach files outside the intended folder.
Normalise a path规范化路径Resolve . and .. so the path shows where it really points.