什么是XSS,又称跨站脚本?
XSS是我们用来定义一种特殊攻击的术语,由于对用户输入的不安全处理,一个网站(你的网站,如果你不注意的话)可能被用作攻击其用户的载体。
基本上,一个不良行为者(攻击者)可以通过某种方式将JavaScript注入我们的网站,利用我们在代码中留下的漏洞。
利用这个漏洞,他们可以窃取用户的信息。
根据XSS漏洞被利用的方式,我们有3种主要的XSS攻击。
- 持续的XSS
- 反映的XSS
- 基于DOM的XSS
为什么XSS是危险的?
想象一下,你有一个网站。攻击者可以以某种方式注入由你的网站提供的JavaScript代码,并在你不知道的情况下由你的用户的浏览器执行--没有你的意愿,也没有你知道。
这是非常危险的。
由于你在修复XSS漏洞方面的疏忽,你的网站可能被用作攻击媒介,你的用户信息处于危险之中。
特别是,当任何人都可以在一个页面中注入JavaScript时,他们可以访问与网站相关的用户的cookies,并读取其中包含的任何信息。并将这些信息发送到他们自己的服务器。他们可以监听键盘事件,并获得用户在页面中输入的任何信息,并使用fetch或XHR将其发送到攻击者的服务器。例如,用户名和密码。他们还可以操纵DOM,有了这种能力,他们可以做很多坏事。
XSS是前端还是后端问题?
两者都是。它是一个网站架构问题,涉及到前端和后端。
一个XSS攻击的例子
当你允许用户输入信息时,XSS基本上就被启用了,你把这些信息储存起来(在你的后端),然后再呈现出来。
比如你有一个博客,你允许用户在博客中发表评论。如果你盲目地接受用户输入的任何内容,一个恶意的用户可以尝试输入一个JavaScript片段,在其最基本的形式中包含在<script></script> 。例如:<script>alert('test')</script> 。
你可能会把这个评论储存在你的数据库中,当页面被重新加载时--同样,如果你没有任何预防措施,所有加载该页面的用户都会运行这个JavaScript片段。
我使用了一个简单的alert() 调用来做例子,但如上所述,用户可以输入任何类型的脚本。在这一点上,该网站已经被破坏了。
什么是持久性XSS?
持久性XSS是我们在野外发现的3种XSS之一。它就是我在上面的博文例子中描述的那种。
在这种情况下,漏洞的代码被储存在数据库或其他来源中,而这些来源是由你自己托管的。
一旦有人能输入一个JavaScript片段,它就会被你的网站自动提供,而不需要任何其他动作。
什么是反射式XSS?
反射式XSS是一种通过向终端用户提供一个内含脚本的链接来即时利用你的网站漏洞的方法。
通过这种方式,攻击者提供一个类似于
yoursite.com/?example=<script>alert('test')</script>
如果你的网站使用example GET变量来执行一些东西,并在页面上显示出来,而你没有检查和消毒它的值,现在这个脚本将被用户的浏览器执行。
一个典型的例子是一个搜索表单。它可能存在于/search URL中,你可能使用GETterm 变量接受搜索词。
当有人搜索的时候,你可能会显示You searched for <term> 这个字符串。现在,如果你没有对该值进行消毒,你现在有一个问题。
垃圾邮件/钓鱼邮件是这种XSS攻击的一个常见媒介。当然,网站越大、越重要,黑客就会越频繁地试图入侵它。
什么是基于DOM的XSS?
对于持久性XSS,攻击代码必须被发送到服务器,在那里它可以被(并希望它被)净化掉。对于反射型XSS,情况也是如此。
基于DOM的XSS是一种XSS,恶意代码从未被发送到服务器上。这通常是通过使用URL的片段部分,或通过引用document.URL/document.location.href 。你在网上找到的一些例子其实已经不起作用了,因为现代浏览器会自动在地址栏中为我们转义JS。他们只有在你取消转义的情况下才能工作,这有点可怕(不要这样做!)。
这里有一个简单的工作例子。假设你有一个页面在收听http://127.0.0.1:8081/testxss.html。你的客户端JavaScript查看URL的片段部分中传递的`test` 变量。
http://127.0.0.1:8081/testxss.html#test=something
#test=something 的值永远不会被发送到服务器上。它只是本地的。持久性/反射性XSS不会起作用。但是,如果你的脚本使用". "访问这个值,那么你就会发现,这个值并没有被发送到服务器上。
const pos = document.URL.indexOf("test=") + 5;
const value = document.URL.substring(document.URL.indexOf("test=") + 5, document.URL.length)
并将其直接写入DOM中。
一切都很好,直到有人像这样调用这个URL。
http://127.0.0.1:8081/testxss.html#test=
现在,由于引用document.URL ,发生了自动转义,在这种特定的情况下不会发生什么。
你会被
%3Cscript%3Ealert('x')%3C/script%3E
打印到页面上。该值被转义了,所以它不会被解释为HTML。
但是,如果由于某种原因,你取消了对document.URL值的转义,你现在就有问题了,因为JavaScript已经运行了。任何JS都可以在你的用户浏览器上运行。
在旧的浏览器上,这是一个更大的问题,因为他们没有自动屏蔽放在地址栏中的JS。
静态网站是否容易受到XSS的攻击?
是的。任何类型的网站,实际上都是如此。因为静态并不意味着没有从其他来源加载信息。例如,你可能会滚动你自己的表单或评论,即使没有数据库。
或者,我们可能有一个搜索功能,接受来自HTTP GET或POST变量的输入。你不能仅仅因为没有数据库而免疫。
我们怎样才能防止XSS?
有3种技术我们可以使用。
- 编码
- 验证
- CSP
编码是为了逃避数据。这样做,浏览器将无法解释JavaScript,因为,例如,<script> 将被编码为%3Cscript%3E 。
编码,作为一般规则,应该总是做。
服务器端的框架通常提供辅助函数来为你提供这个功能。
在客户端的JavaScript中,我们根据不同的使用情况使用不同的编码机制。
如果你需要向一个HTML元素添加内容,最好的方法是使用textContent 属性将用户生成的输入分配给该元素。浏览器将为你做所有的转义处理。
document.querySelector('#myElement').textContent = theUserGeneratedInput
如果你需要创建一个元素,使用document.createTextNode() 。
const el = document.createTextNode(theUserGeneratedInput)
如果你需要向一个HTML属性添加内容,使用该元素的setAttribute() 方法。
document.querySelector('#myElement').setAttribute('attributeName', theUserGeneratedInput)
如果你需要在URL中添加内容,可以使用 window.encodeURIComponent()函数。
window.location.href = window.location.href + '?test=' + window.encodeURIComponent(theUserGeneratedInput)
验证通常是在你不能使用转义来过滤输入时进行的。一个常见的例子是CMS,它让用户用HTML定义页面的内容。你不能转义。
你可以使用黑名单或白名单策略进行验证。不同的是,在黑名单中,你决定哪些标签是你想禁止的。在白名单中,你决定你想允许哪些标签。白名单是比较安全的,因为黑名单容易出错,比较复杂,也不能保证未来。
CSP是指内容安全策略。这是一个由浏览器实施的新标准,强制要求只执行来自安全和可信来源的JavaScript代码,你可以禁止在你的代码中运行内联JavaScript。例如,允许上述XSS漏洞的那种JavaScript。
CSP是由Web服务器启用的,通过在提供页面时添加Content‑Security‑Policy HTTP头。