SecurePHPWebAppCoding - 跨站脚本攻击 (XSS) - 它是什麽以及如何阻止它?





5.00/5 (3投票s)
本文解释了可以进行XSS攻击的几种场景,我们在创建Web应用程序时会导致XSS漏洞的错误,以及应该采取哪些措施来预防XSS攻击。
什么是跨站脚本 (XSS)?
跨站脚本(XSS)是一种Web应用程序漏洞,它允许攻击者攻击访问该网站的访问者/用户。攻击者将客户端脚本注入网页,当有人访问该网站时,脚本会被执行,用户就成了攻击的受害者。与其他注入攻击(SQL注入、命令注入等)不同,XSS不攻击Web应用程序服务器或数据库。它利用Web应用程序作为媒介/平台,在用户浏览器上执行恶意脚本,并允许攻击者在正常情况下不允许的情况下获得未经授权的访问。XSS攻击主要使用JavaScript,但也可以使用VBScript、ActiveX、Flash。
可以使用XSS做什么?
XSS可以用来做很多事情。这取决于攻击者想如何使用它。下面提到的几点仅仅是其中的一些例子。
- 窃取用户Cookie:- 攻击者可以使用注入的脚本收集用户的Cookie详细信息并在其浏览器中设置Cookie。由于大多数Web应用程序使用Cookie中存在的会话ID来验证用户,因此Web应用程序将允许攻击者登录到该用户的帐户。
- 抓取机密信息:- 注入的脚本可用于抓取网页以收集信用卡详细信息、SSN等机密信息。
- 代表登录用户发布数据/执行操作:- XSS可用于执行表单发布,如发布评论、触发点赞帖子等事件,而无需登录用户的同意。
- 恶意重定向:- 注入的脚本可用于将用户重定向到攻击者想要的任何URL,例如一个要求用户提供登录详细信息的假登录页面。一旦用户提供详细信息,就会将其发送给攻击者。
- 恶意软件攻击:- 攻击者可以使用脚本触发浏览器漏洞,在用户系统中安装恶意软件,并利用该恶意软件控制/使用用户的系统。
- 社会工程学:- 攻击者可以在页面中注入新的HTML代码,要求用户提供个人信息,如地址、账单详情等,或者显示错误的错误消息来欺骗用户下载和安装恶意软件到其系统中。
如何将脚本注入网页?
脚本可以通过Web应用程序收集输入的各种方式轻松地注入到网页中。XSS可以通过以下方式传递脚本来执行:
- 输入字段 (文本框/文本区):- 脚本可以插入用于收集用户信息 的表单字段中。
- 查询字符串:- 攻击者也可以将脚本作为查询字符串参数传递。
- Cookie:- 如果Web应用程序使用Cookie在保存到数据库之前临时存储数据,攻击者可以更改Cookie值并注入脚本。
- 来自外部源的数据:- 来自外部源的数据可能包含脚本,如果直接在浏览器中显示该数据,脚本就会执行,并允许攻击者做任何他们想做的事情。
- DOM:- 有时DOM中可用的数据也包含脚本/恶意代码,如果处理不当,可能会导致XSS攻击。我们将在下面看到一个例子。
XSS攻击向量
让我们看看可以用来注入和执行脚本的几个攻击向量。
- <script> 标签: 这是注入脚本最简单的方式。恶意代码可以像
<script>alert(1);</script>
一样被注入。 - 事件处理器: 浏览器自动触发 (无需用户交互) 的事件处理器,例如
body
标签的onload
,img
标签的onerror
。onload
事件在 body 加载时触发。onerror
事件在img
标签指定的路径错误或浏览器无法加载它时触发。 -
<body onload="alert(1);"> // this will show an alert message when page is loaded <img src="http://false.domain.com/12eqwewqeqw" onerror="alert(1);"/> // this shows an alert message if browser fails to load the image.
- CSS样式属性: 从源加载数据的CSS样式属性可用于XSS。样式属性如
background-image:url(), -moz-binding
都是可以用于XSS的例子。
如果您想了解XSS攻击向量的完整列表,可以访问OWASP的 XSS过滤器规避备忘单 页面。
XSS类型
XSS攻击有三种类型。一种是存储型XSS(或持久型XSS),第二种是反射型XSS(或非持久型XSS),最后一种是DOM型XSS。在存储型XSS和反射型XSS中,恶意数据(注入的脚本)会经过服务器,但在DOM型XSS中,数据永远不会经过服务器,它仅通过客户端脚本在浏览器中渲染。
存储型XSS (或持久型XSS):-
当注入到输入字段(或通过Web应用程序收集用户数据的任何其他数据选项)的脚本被保存在/存储在Web服务器(数据库、文件等)中,并且相同的数据显示给其他访问者/用户时,就会发生存储型XSS。
反射型XSS (或非持久型XSS):-
在反射型XSS中,注入的脚本不会存储在Web服务器中,而是直接发送到浏览器进行显示,当浏览器渲染数据时,脚本就会被执行。
DOM型XSS:-
当客户端脚本使用DOM中的数据来显示内容或渲染网页的某个部分时,就会发生DOM型XSS。在DOM型XSS中,不需要服务器端交互。DOM型XSS攻击的主要来源是:
- document.URL
- document.baseURI
- document.location.href
- document.location.hash
- document.location.search
- docuemnt.location.pathname
- window.name
- document.referrer
以及有助于DOM型XSS的函数/HTML属性有:
- document.write()
- (element).innerHTML
- eval()
- setTimeout()
我们将看到所有类型XSS攻击的示例以及阻止这些攻击的步骤。
如何预防XSS?
XSS可以通过过滤和HTML编码从用户或外部源接收到的数据来预防。选择哪种方法来预防XSS完全取决于需求/情况。让我们看看使用上述两种方法来预防XSS会发生什么。
过滤XSS数据
保护免受XSS的最简单易行的方法是将数据传递给过滤器,该过滤器会从用户提供的数据中删除所有危险的关键字,如 <script>
,危险的样式属性,如 background-image:url()
,包含在无需用户交互即可触发的事件处理程序中的HTML标记,例如 <img src="non_existent_path" onerror="alert(1)"/>
。所有面向Web的编程语言都提供函数/方法来从字符串(用户提供的数据)中删除HTML标签。
如果用户在任何输入字段中提供 <script>alert(1);</script>
,经过过滤后,它会变成 alert(1);
,当这些数据发送到浏览器时,会显示为 alert(1);
。
由于过滤数据会从数据中删除HTML标签,因此并非总是适合使用数据过滤方法,例如在撰写关于Web设计的技术文章时。当我们撰写Web设计文章时,我们需要在文章中提供代码,以便读者能更好地理解代码。但是,如果我们将文章保存功能实现为删除所有HTML标签以防止XSS,它将删除所有HTML标签,文章将无法达到其目的。为了克服这种情况,我们通常会对用户提供的数据进行编码。
编码XSS数据
这是预防XSS最受推荐的方法。当我们对用户提供的数据进行编码并将其发送到浏览器时,这意味着我们告诉浏览器将其视为数据而不是HTML。在这种预防XSS的方法中,所有具有HTML字符实体等价物的字符都被转换为这些实体。因此,当编码后的数据发送到浏览器时,浏览器会渲染编码后的数据,显示为HTML字符。让我们看一些HTML字符及其编码值。
字符 | 实体名称 | 实体代码 |
---|---|---|
< | 小于 | < |
> | 大于 | > |
" | 双引号 | " |
& | 和号 | & |
如果用户在任何输入字段中提供 <script>alert(1);</script>
,经过编码后,它将被转换为 <script>alert(1);</script>
,当这些数据发送到浏览器时,它将显示为 <script>alert(1);</script>
而不是作为脚本被执行。
我们知道在应用程序中预防XSS的选项,可能会出现一个问题——何时应用过滤器或编码数据?在回答这个问题之前,让我们先了解其他一些东西。
通常,一个Web应用程序有多个开发人员。他们开发模块,一两个开发人员负责一个模块。在一个模块中收集的数据也可能在其他相关模块中显示,负责收集数据的模块的开发人员可能不负责显示数据的模块。如果负责收集数据的模块的开发人员不过滤或转义数据,那么所有使用该数据的开发人员都必须收到通知,并在显示数据时应用过滤器或进行编码。有时,开发人员在使用另一个模块的数据时可能会遗漏过滤或编码数据,这将导致XSS漏洞。即使所有开发人员都使用过滤器或进行数据编码,在许多地方做同样的事情也没有意义,这些事情可以在一个地方完成——在将数据保存到数据库时。这对在其他模块工作的开发人员来说会更容易,他们不必担心来自其他模块的数据可能导致安全问题,他们可以专注于他们工作的模块。因此,过滤或编码数据的最佳位置是在保存到数据库时进行,这样其他模块也能获得过滤/编码后的数据。
示例
现在让我们看一些实际的例子。我将使用PHP来展示示例。但同样的事情也可以使用其他面向Web的编程语言来完成。
1.管理员的注册确认
假设在一个网站上,用户填写注册表单后,需要管理员验证和激活账户,账户激活后即生效。在注册过程中,攻击者在地址字段中插入了脚本,例如:
E Drachman St, Tucson, AZ 85705 <script type="text/javascript">alert('XSS');</script>
然后地址字段的数据按原样保存在数据库中。当管理员访问“待处理注册确认”页面时,将从数据库中拉取待处理注册确认列表并显示。但是,在显示数据时,用户的地址会显示在“地址”列下,脚本会被执行。因此,管理员会看到一个带有消息XSS的警报。
为了在这种情况下防止XSS,我们可以在保存到数据库之前,将输入数据通过htmlspecialchars
或htmlentities
函数,该函数会将所有HTML特殊字符转换为等效的HTML实体。
$address = 'E Drachman St, Tucson, AZ 85705<script type="text/javascript">alert("XSS");</script>'; $address = htmlspecialchars($address); //htmlentities can also be used here echo $address; //E Drachman St, Tucson, AZ 85705 <script type="text/javascript">alert('XSS');</script>
将地址字段通过htmlspecialchars
函数处理后,地址字段中的数据看起来像这样:
E Drachman St, Tucson, AZ 85705 <script type="text/javascript">alert('XSS');</script>
当它在浏览器中显示时,看起来像这样:
E Drachman St, Tucson, AZ 85705 <script type="text/javascript">alert('XSS');</script>
这里的 <script> 只是普通文本,而不是HTML标签,所以它不会被执行,也不会有警报消息。
2.使用查询字符串的XSS攻击
假设您的雇主在其网站上显示员工个人资料。员工个人资料的URL类似于:http://www.employersite.com/profile.php?id=112
。个人资料页面显示的内容如下。
Employee ID:- 112 //ID shown here is pulled from query string and displayed using $_GET['id'] Name:- John Smith Address:- E Drachman St, Tucson, AZ 85705 Designation:- Sales Manager
页面上显示的员工ID是从查询字符串中获取的,并使用 $_GET['id']
显示,而没有进行过滤和编码。因此,如果我们修改URL如下:
http://www.employersite.com/profile.php?id=<script type="text/javascript">alert('XSS');</script>
当员工访问页面时,会显示一个带有消息XSS的警报。这是反射型XSS的一个例子,因为数据没有保存在服务器上。
为了在此处防止XSS,我们过滤在查询字符串中接收到的ID值。对上述提供的员工ID应用过滤器会删除HTML标签 <script>,只留下 alert('XSS'),它被视为一个普通的文本,将在浏览器中显示,而不是显示警报消息。过滤输入参数后,输出将如下所示:
Employee ID:- alert('XSS'); //ID shown here is pulled from query string and displayed after filtering the value of $_GET['id'] Sorry!!! No employee exists with that ID.
上面显示的“Employee ID alert('XSS');”是从查询字符串中获取的,并在通过strip_tags函数处理后输出,该函数会删除HTML标签,只留下其中的文本。由于 alert('XSS'); 不是一个有效的ID,数据库中没有具有此ID的员工,因此个人资料详细信息页面会显示错误消息“Sorry!!! No employee exists with that ID.”。
在以上两个示例中,我们看到了两种使用服务器端方法编码数据以阻止XSS和过滤数据以阻止XSS的案例。让我们看一个XSS仅在客户端脚本渲染数据时发生的例子。之后,我们将看看如何使用客户端方法阻止XSS。
3. DOM型XSS攻击示例
假设以下页面 http://www.somewebsite.com/xss-test.html 包含以下代码:
<script> document.write("<strong>URL</strong> : " + document.baseURI); </script>
如果发送类似 http://www.example.com/test.html#<script>alert(1)</script> 的请求,JavaScript代码将被执行,因为页面使用document.write函数将URL中键入的内容写入页面。如果您查看页面的源代码,您将看不到 <script>alert(1)</script>
,因为它都发生在DOM中,并且是由注入的JavaScript完成的。一旦代码注入到页面中,就可以利用这种基于DOM的跨站脚本漏洞来窃取用户Cookie,更改页面内容等。
那么我们如何解决这个问题呢?最好的方法是将其写入一个元素,如div、span,在写入时不要使用innerHTML,而是使用textContent或innerText。让我们看看如何通过修改我们的初始代码来解决这个问题。
<strong>URL</strong><span id="xss_text"></span> <script type="text/javascript"> document.getElementById('xss_text').textContent = document.baseURI; document.getElementById('xss_text').innerText = document.baseURI; // can write this also </script>
上面的代码将在浏览器中写入URL,但它将显示文本 <script>alert(1)</script>
而不是执行它。
4. 特殊情况 - 过滤HTML元素属性
让我们看另一种情况——假设一个网站允许您撰写文章并格式化内容,设置文本颜色,添加图片等。这种功能通常通过一个允许设置字体大小、添加图片、格式化文本的编辑器来实现——几乎所有让我们的文章看起来好读的东西。当文章保存时,Web应用程序会按原样将HTML内容保存在数据库中,以便更容易地渲染文章作者格式化的确切设计。但是,如果攻击者想要,他可以使用编辑器提供的源代码查看器来更改HTML代码,并添加像onclick、onmouseover、onerror这样的事件处理属性,并将脚本添加到这些事件处理程序中。当文章保存时,它还将保存所有事件处理属性,这些属性将在文章渲染后被触发,并且附加到该事件的脚本将在用户执行触发该事件所需的行为后执行。在这种情况下,所有事件处理属性都是危险的,但onerror等事件由于在加载图像出错时自动触发而更加危险。
<img src="http://falseurl.com/false123.jpg" onerror="alert(1);"/>
如果文章包含一个源文件不存在的图像和一个onerror属性,当文章渲染给用户查看时,他们会看到一个警报消息。在这种情况下,我们不能过滤所有HTML标签,因为文章的外观会与文章作者想要的有所不同,我们也无法对HTML字符进行编码,因为这将导致浏览器显示所有HTML内容。所以我们需要做一些事情,既能保留文章作者设计的文章风格,又能移除危险的属性。
为了在这种情况下阻止XSS,我们需要创建一个自定义过滤器,该过滤器会移除文章HTML标签中存在的所有属性,以便文章源代码中只包含安全的HTML代码。
在以上所有示例中,我们都看到了 alert('XSS') 或 alert(1),这些都没有什么危害。但是,它们可以被替换为加载外部JavaScript文件并执行其中代码的代码。上面的例子展示了攻击者可以注入代码并运行自己脚本的几种可能场景。上述示例中提到的阻止XSS的步骤仅为示例目的。这并不意味着您只能在那种情况下这样做。这完全取决于您的需求——当用户提供的数据包含恶意代码时,您想如何处理。您甚至可以验证数据并对无效数据显示错误消息。
结论
跨站脚本存在于Web应用程序中,是因为开发人员在处理数据时疏忽,或者错误地认为用户不会提供恶意数据。有时,这是开发人员对安全Web应用程序编程知识不足的结果。通过在构建应用程序时付出额外的努力,并牢记我们构建的Web应用程序既能被普通用户访问,也能被攻击者访问,我们可以摆脱XSS。