XSS学习笔记

本文探讨了跨站脚本(XSS)攻击的原理、不同类型(如反射型、存储型、DOM型)及其目的,强调了输入检查、输出编码和框架如Django和web2py的安全措施。同时,文章指出了正确防御XSS的关键在于理解输出语境和在适当位置使用适当的编码策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

跨站脚本攻击,英文全称为Cross Site Script,为与层叠样式表(Cascading Style Sheet,CSS)有所区别,缩写为XSS。XSS是指攻击者向网页中插入恶意代码,当用户浏览该页面时,嵌入在其中的代码会被执行,从而达到恶意攻击用户的目的。相较于SQL注入攻击,XSS攻击的对象是网站用户,SQL注入的攻击对象是网站服务器。XSS漏洞通常是通过PHP的输出函数将JavaScript代码输出到HTML页面中,通过用户本地浏览器执行的,所以XSS漏洞关键就是寻找参数未过滤的输出函数。一开始这种攻击的演示案例是跨域的,所以称其为跨站脚本,但是发展到如今,由于JavaScript的强大功能以及网站前端应用的复杂化,是否跨域已经不再是重点。因为历史缘故,XSS这个名字一直保留了下来。

XSS攻击手段和目的

  1. 盗用cookie,获取敏感信息
  2. 利用植入Flash,通过crossdomain权限进一步获取更高权限;或者利用Java得到等类似的操作,即提权
  3. 利用iframe、frame、XMLHttpRequest或上述Flash等方式,以被攻击者的身份执行一些管理动作,或执行一些一般操作如发微博、加好友、发私信等操作。
  4. 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如不当的投票活动等
  5. 在访问量极大的页面上的XSS可以攻击一些小型网站,实现DDOS攻击的效果。

分类

反射型XSS

攻击者事先制作好攻击链接,用户自己去点击链接时,服务器端接受数据后处理,把带有XSS代码的数据发送到浏览器,浏览器解析这段带有XSS代码的数据后,触发XSS代码。一般容易出现在搜索页面,是一次性攻击,又称为“非持久性XSS”。

存储型XSS

存储型XSS。允许用户存储数据的Web应用程序都肯出现存储型XSS漏洞,当攻击者在某网页提交一段XSS代码后,服务器端会接收并存储。当有用户访问该页面时,这段XSS代码被程序读出来相应给浏览器,造成XSS跨站攻击。这种XSS有很强的稳定性,隐蔽性也比较强。这种XSS攻击多出现在个人信息或发表文章的地方、留言板等。攻击者在这类地方写下代码,如果网页没有过滤或过滤不严,每当有用户访问该页面的时候都会出发代码执行,这种XSS非常危险,容易造成蠕虫,大量盗窃cookie。又称为“持久性XSS“。

DOM型XSS

DOM型XSS从效果上来看也是属于反射型XSS,但是被单独划了出来,因为它的成因较为特别。DOM型XSS就是通过修改页面的DOM节点形成的XSS。

文档对象模型(Document Object Model),DOM是一与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM通常用于代表在HTML、XHTML和XML中的对象。使用DOM可以允许程序和脚本动态的访问和更新文档的内容、结构和样式。

通过JavaScript可以重构整个HTML页面,而要重构页面或者页面中的某个对象,JavaScript就需要知道HTML文档中所有元素的“位置“。而DOM为文档提供了结构化表示,并定义了如何通过脚本来访问文档结构。根据DOM规定,HTML文档中的每个成分都是一个节点。

DOM的规定如下:

  • 整个文档是一个文档节点;
  • 每个HTML标签是一个元素节点;
  • 包含在HTML元素中的文本是文本节点;
  • 每一个HTML属性是一个属性节点;
  • 节点与节点之间都有等级关系。

HTML的标签都是一个个节点,这些节点组成了DOM的整体结构:节点树↓

可以发现,DOM本身就代表文档的意思,而基于DOM型的XSS是不需要与服务器端交互的,它只发生在客户端处理数据阶段。

当我们访问网页时,会向网页服务器发送请求,服务器收到请求会进行DOM分发,依次将主页、css、js、媒体文件等发过来(我们常能注意到网速慢时网页的主页或文字内容会先于图片、音视频内容加载出来),然后在本地浏览器上执行DOM树(按照顺序加载)。

DOM型XSS即是在DOM分发后加载执行来的。所以它不依赖提交数据到服务器端,而是从客户端获得DOM中的数据在本地执行。

XSS payload

XSS攻击成功后,攻击者能够对用户当前浏览的页面植入恶意脚本,通过恶意脚本,控制用户的浏览器。这些用以完成各种具体功能的恶意脚本,被称为“XSS payload“。这个payload可以翻译为载荷。XSS payload实际上就是JavaScript脚本,还可以是Flash或其他富客户端

(Rich Client,胖客户端【富客户端】,与瘦客户端相对。富客户端和瘦客户端的区别可见blog.youkuaiyun.com/csdnlijingran/article/details/100162691)的脚本。所以任何JavaScript脚本能实现的功能,XSS payload都能做到。正如上文提到过的,XSS的常见作用就是读取浏览器的Cookie,发起Cookie劫持攻击。

XSS的防御:

HttpOnly

HttpOnly最早由微软提出,并在IE 6 中实现的,至今已经成为一个标准。浏览器将禁止页面的JavaScript访问带有HttpOnly属性的Cookie。在我之前的HTTP与Cookie笔记(blog.youkuaiyun.com/Yisitelz/article/details/131954649)中的cookie与session一节提到过,Set-Cookie消息头就包含这个属性。

如果该Cookie设置了HTTP Only,那么XSS偷Cookie的攻击就会失败,因为JavaScript不允许读取Cookie的值。另外,服务器上可能会设置多个Cookie,HttpOnly可以选择性的加在其中的任何一个上。有选择地将HttpOnly属性加给用于认证功能的关键Cookie。

输入检查

常见的web漏洞如XSS、SQL注入等,都会要求攻击者构造一些特殊字符,而这些特殊字符可能是正常用户不会用到的,所以输入检查就有存在的必要了。输入检查的逻辑必须放在服务器端代码中实现。

有些JavaScript能对用户的输入进行验证处理,但其归根结底是属于前端验证,在浏览器未提交数据时进行的验证,而我们可以用正常的输入通过验证,然后在拦截HTTP请求后修改数据。此时看来JavaScript的验证根本起不了任何作用。由此可见前端验证是不可靠的。前端JavaScript验证是为了防止用户输入错误,服务器端验证是为了防止恶意攻击。

在这方面的防御上,键入检查一般是检查用户输入的数据中是否包含了一些特殊字符,比如

<   >   ‘    “  等,如果发现特殊字符,则将这些字符过滤或编码。

一些较为智能的键入检查还可以匹配XSS的特征,比如查找用户数据中是否包含了<script>、JavaScript 等字样的敏感字符。

这种XSS防御方式可以称为XSS Filter,XSS Filter在用户提交数据时获取变量,并进行XSS检查,但此时用户数据并没有结合渲染页面的HTML代码,因此XSS Filter对语境的理解并不完整。比如下面这个XSS漏洞

 

其中$var是用户可以控制的变量,这时候用户只需要在表单中提交一个恶意脚本所在的URL地址,就能直接实施XSS攻击。而对于一些XSS Filter来说,无法看到用户数据的输出语境,只能看到用户提交的URL,这就很容易产生漏报,因为多数情况下URL是合法的用户数据。XSS Filter对  <   > 等字符的处理还有可能改变数据语义,本来  x>2 或y<1 取值范围表示的好好的,被粗暴的过滤了,未免贻笑大方。

还有,如果用户的昵称中包含一些特殊字符(比如早年的杀马特非主流昵称),因为其用途或性质,可能会在不同的情境中多次出现,在各个地方的语境也不一定相同,展示的需求也不相同,如果单一地使用替换、过滤操作,也是会影响用户体验。

 

输出检查

一般来说,除了富文本的输出外,在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。

安全的编码函数

编码分为很多种,针对HTML代码的编码方式是HtmlEncode,HtmlEncode并非专用名词,它只是一种函数实现。它的作用是将字符转化成HTMLEntities。

为了对抗XSS,在HtmlEncode中要求至少转换以下字符。

  • &  转义成 &amp
  • <   转义成 &lt
  • >   转义成 &gt
  • “   转义成 &quot
  • ‘   转义成 &#x27 &apos;不推荐用后面的,因为它不在HTML4entity的官方列表上
  • /   转义成 &#x2F  包含反斜线是因为他可能会闭合一些HTML entity。

在PHP里,有htmlentities()和htmlspecialchars()两个函数可以满足安全要求。

相应地,JavaScript的编码方式可以使用JavascriptEncode。

JavascriptEncode和HtmlEncode的编码方法不然,它需要用  “ \ ” 对特殊字符进行转义。在对抗XSS时,还要求输出的变量必须要在引号内部,以避免造成安全问题。比如下面两种写法:

如果escapeJavascript()函数只是转义了几个危险字符,比如 “ ‘ < > \ 等,那么上面两行的代码输出后有可能会变成

我们可以发现第一行会执行额外的代码,而第二行则是安全的,对于第二行的,攻击者若想逃出引号的范围,也会比较困难

所以要求使用JavascriptEncode的变量输出一定要在引号内。如果开发者没有这样的习惯,就只能用更严格的JavascriptEncode函数来保证安全,把除了数字、字母外的所有字符都采用十六进制 “ \xHH “ 的方式进行编码。

 

只用编码还不够

XSS攻击主要发生在MVC架构中的View层。大部分的XSS漏洞都可以在目标系统中解决。在Python的开发框架Django自带的模板系统“Django Templates“中,可以使用escape 进行HtmlEncode。比如   {{ var| escape }} 这样写的变量会被HtmlEncode编码。

这一特性在Django 1.0中得到了加强,默认所有的变量都会被escape,这个做法符合之前的那篇文章中提到的“Secure By Default“原则。

在Python的另一个框架web2py中,也默认escape了所有的变量。Django和web2py都选择了在view层默认HtmlEncode所有变量以对抗XSS,出发点是好的,但是这不能解决所有的XSS问题。前面的文章安全方案设计中提到过,要在正确的地方使用正确的编码方式

比如这个例子

 

结果就是浏览器弹了两次窗。导致XSS攻击发生的原因就是没有分清楚输出变量的语境。XSS的防御需要分情况对待。

正确地防御XSS

见我的另一篇笔记blog.youkuaiyun.com/Yisitelz/article/details/132060413

参考资料:《Web安全深度剖析》张炳帅

                  《白帽子讲web安全》吴瀚清

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值