防止跨站脚本攻击

如今,我们创建的几乎每个服务都有某种形式的 Web 界面,无论是用于管理、监控还是服务的核心功能。这些界面变得越来越复杂和动态,并且越来越具有交互性。然而,在增加这些 Web 服务的交互性时,存在一种风险,即我们无意中允许用户提供可能破坏或扰乱该服务正常运行的数据。

跨站脚本攻击 (XSS) 是一种漏洞类型,攻击者能够向 Web 服务呈现活动 Web 内容,随后该内容被回显给用户并在浏览器中执行。这些内容可能看起来无害,例如令人尴尬的图像或文本,也可能具有恶意性,例如基于浏览器的漏洞,旨在窃取和利用用户的 Web 会话,甚至破坏用户的 Web 浏览器并控制他们的客户端系统。

主要有三种类型的 XSS 问题:持久性、反射性和基于 DOM 的。持久性 XSS 问题是指用户输入被服务器存储,例如在数据库或服务器文件中,稍后呈现给访问受影响网页的任何用户。反射性 XSS 问题是指请求中的用户输入在未经清理的情况下立即回显给用户。

基于 DOM 的问题不太常见,存在于具有丰富的客户端 JavaScript 客户端的 Web 应用程序中,这些应用程序使用用户可控的数据(例如 URL 参数)生成动态代码或 Web 内容。

在开发 Web 应用程序时,我们必须格外小心地防范所有这些类型的问题。为此,我们绝不能信任任何来自客户端或受客户端控制的数据。所有数据都必须以适合该数据使用方式的方式进行清理。为此,许多语言都提供了内置功能,以确保任何潜在的危险控制字符都以使其失效的方式进行编码。以下是一个 PHP 示例:

错误

以下是一个反射性 XSS 漏洞可能发生的示例。如果攻击者向 ‘http://example.com/?name=<script>alert(1)</script>’ 提交请求,那么查看该 URL 的任何用户都会在他们的浏览器中执行 JavaScript 代码。这可用于恶意目的。

# flask example
@app.route("/")
def hello():
    name = request.args.get('name')
    return "Hello %s" % name

大多数现代 Python Web 框架都会转义通过模板呈现的任何输入,从而减轻这些类型的攻击中的大部分。但是,存在禁用此功能的方法。

<!– 默认情况下,flask 会 html 转义 var –> <p>{{ var }}</p>

<!– 在这种情况下不会! –> <p>{{ var | safe }}</p>

正确

防止 XSS 攻击的正确方法是验证用户输入并确保模板呈现的数据经过转义。以预期的方式使用模板是首选方法

# flask example
@app.route("/")
def hello():
    name = request.args.get('name')
    return render('hello.html', name=name)

# where hello.html is:
# <html>Hello {{ name }}</html>

在请求处理程序中直接生成的任何 HTML 内容应使用适当的转义函数

from flask import escape
@app.route("/")
def hello():
    name = request.args.get('name')
    return "Hello %s" % escape(name)

允许某些特殊字符

当我们需要允许特定的一组特殊字符时,例如允许发布包含 HTML 标签的内容时,问题会变得更加复杂。在这种情况下,我们可以只接受已知良好的数据,或者拒绝所有已知不良的数据。两种方法都有优点和缺点,具体的实现选择取决于给定的应用程序。但是,通常情况下,以下应该是优先列表

  1. 编码 - 将 ALL 控制字符替换为已知的安全替代方案

  2. 正向验证(白名单) - 只允许特定的一组值

  3. 负向验证(黑名单) - 阻止指定的危险值列表

在采用正向验证的情况下,还应将其与额外的清理结合使用。例如,在允许某些 HTML 标签时,应删除这些标签的某些属性,例如事件处理程序。例如:

<img src='someimage.jpg' onload='do_evil()'/>

同样,首选的方法是只允许已知的安全属性,并清理这些属性值的内容。如果内容未经过清理,可能会出现以下易受攻击的代码

function add_image(link) {
  document.write('<img src="' + link + '"'></img>'');
}

如果将前面的 JavaScript 函数与包含以下值的 link 参数一起调用,则该函数可能会被利用来执行任意代码

x" onerror="do_evil()

上述更安全的实现方式是

function add_image(link) {
  clean = link.replace(/"/g, '&quot;');
  document.write('<img src="' + clean + '"'></img>'');
}

请注意,这只是一个具体的示例,仅用于说明。对于更大的应用程序,应采取更全面的清理方法。

后果

  • 劫持合法用户会话

  • 泄露敏感信息

  • 访问特权服务和功能

  • 从我们信任的域传递恶意软件和浏览器漏洞

参考