人人都能学编程
English 开始学习
专项 · 网络安全第 6 课 / 共 9 课免费阅读

XSS 与路径穿越 · 网络安全入门

一个评论框,怎么能在别人的浏览器里运行代码?

课程目录

为什么需要它

网站时时刻刻都在把一个人的文字展示给别人看:评论、昵称、文件名。如果这些文字被当成代码或路径来处理,一个访客就能攻击所有其他访客,甚至读到服务器上的文件。这一课讲两个经典案例:跨站脚本(XSS)和路径穿越。

直觉理解

浏览器把页面当作 HTML 来读。如果一条评论里有 <script>...</script>,而你把它原样放进页面,浏览器就会执行它:在每个访客的浏览器里,带着他们的登录 Cookie 执行。

修复办法是输出时转义:把 < 变成 &lt;,> 变成 &gt;,让浏览器把这些字符显示出来,而不是照着执行。Python 的 html.escape 就是干这个的。

文件名也有同样的问题。../ 表示「往上一级目录」,所以 ../../etc/passwd 能爬出你的上传目录。先把路径规范化(把每个 .. 都算掉),再检查它是不是还在允许的目录里。

两种情况都一样:优先用允许清单(只接受你预期的东西),而不是阻止清单(只拒绝你想到的东西)。

定义

跨站脚本(XSS)

An attack where text supplied by a user is shown as HTML, so a script inside it runs in other users' browsers.
一种攻击:用户提供的文字被当成 HTML 显示,里面的脚本就在其他用户的浏览器里运行。

路径穿越

An attack where a file name such as `../../secret` makes a program open a file outside the folder it meant to use.
一种攻击:像 `../../secret` 这样的文件名,让程序打开了原本目录之外的文件。

示例

输出时转义

动手试试

第一行是粗心的页面会生成的内容;第二行放进 HTML 是安全的。

import html
comment = '<script>steal(document.cookie)</script>'
print("<p>" + comment + "</p>")
print("<p>" + html.escape(comment) + "</p>")
print(html.escape('Tom & "Jerry"'))
逐步执行
  1. L3原样放进去:浏览器会执行 steal(...)。
  2. L4转义之后:浏览器只显示文字,不执行任何东西。
  3. L5& 和引号也会被转义,放进属性里也安全。

爬出目录

动手试试

os.path.normpath 会把每个 .. 都算掉。然后检查结果是否还在 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 + "/"))
逐步执行
  1. L5把目录和文件名拼起来,再把点号算掉。
  2. L9正常文件留在里面:True。/srv/files/report.pdf True
  3. L9../../ 爬到了最顶层:检查拦住了它。/etc/passwd False
  4. L9.. 只要没出去就没问题。/srv/files/logo.png True

常见错误

只靠阻止清单

删掉 <script> 远远不够:<img src=x onerror=...> 也能执行代码,类似的标签还有很多。输出时全部转义,比去找危险词靠谱。

前缀检查少了斜杠

path.startswith("/srv/files") 也会放过 /srv/files_backup/secret。要检查目录名后面再加 /。

小测验

1. 这段代码会输出什么?

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
查看答案

答案:A

每个 < 都变成 &lt;,每个 > 都变成 &gt;。浏览器会把它显示成字面上的 <b>hi</b>。

2. 注册表单要填用户名。下面哪条规则是允许清单?

  1. A拒绝包含 `<script>` 的名字
  2. B只接受 3 到 20 个字母、数字或下划线
  3. C把名字里的引号删掉
查看答案

答案:B

允许清单精确描述了什么会被接受。其他一切,包括没人想到的攻击,都会被拒绝。

动手练习

3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。

  1. 预测输出它指向哪里?

    准确写出这段程序的输出。

  2. 改需求转义评论

    render(name, text) 把两个值直接拼进去,生成一条评论的 HTML。修改它,让名字和内容都用 html.escape 转义。

  3. 从零写安全的文件路径

    给下载页面写一个函数 safe_path(name)。把 name 拼到 BASE 后面并规范化,然后:

    • 如果它在 BASE 里面(以 BASE + "/" 开头),返回完整路径
    • 否则返回 None

在互动课里练这一课

打开互动课,这些例子都能直接改、直接运行,练习题提交马上判题。免费开始,不用注册。

互动课按顺序解锁:前面的课还没学,会先带你到课程地图。

本课术语

术语中文英文定义
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.