网站防范XSS攻击的关键思考

攻击网站通常都是通过跨站指令代码(Cross-siteScripting)攻击网站的后台漏洞。它和信息隐性代码攻击 (SQLInjection)攻击的目标不同。前者是透过从Web前端输入信息至网站,导致网站输出了被恶意控制的网页内容,使得系统安全遭到破坏。而后者则是输入了足以改变系统所执行之SQL语句内容的字串,使得系统最终达到攻击的目的。

但从更一般性的角度来看,这两种攻击手法基本上是相通的。他们都是透过系统对于输入信息的毫无检核或是检核不足,利用刻意制造出来的输入信息,来让系统产生不在预期内的有害行为。

过滤输入信息有效吗?

因此,当要防备这类型的攻击时,大多数人直觉想到的方式,便是对使用者所提供的输入信息进行过滤。在面对信息隐性代码攻击时,许多人会想到针对输入信息中,可能会含有的SQL关键字串或字元进行过滤,例如,使用者的输入信息中若含有单引号,那么便可能制造出危险的SQL语句,因此,许多程序设计者便会想到要针对单引号进行escape,来杜绝信息隐性代码的攻击。而这种escape或过滤的方法,其实不难规避,例如,针对单引号,有心人士还是可以利用信息库服务器支持的特殊写法,将单引号字元替换成其他的型式,使得escape的程序失效。

同样的,面对跨站指令代码攻击,许多程序设计者首先会想到的,也是针对有可能造成疑虑的使用者信息进行过滤。例如,针对字元、SCRIPT字串进行过滤,倘若使用者的输入信息中含有可疑的字元或字串时,则进行改写或禁止使用。这种针对特定的目标进行筛选过滤的方式,可以称为黑名单式的过滤,因为,程序是先列出不能出现的对象清单,然后进行过滤。

当然,在跨站指令代码攻击中,如果想利用黑名单式的过滤方式,当然也是行不通的。因为,单是javascript这个字串,有心人士就可以产生在HTML中等价、但是字串形式不为javascript的写法。例如,使用#x的十六进位字元表示方式,来表示javascript这个字串中的每一个字元。由于改写形式太多了,这使得想要利用黑名单的方式,几乎是不可能的事。

因此,无论何种攻击,想要有更严密的防备,在信息的过滤上,都应该采取白名单式的过滤。而这正和黑名单相反,它不是列出不被允许的对象,而是列出可被接受的对象。

以正向表列的方式管制

在面对信息隐性代码攻击时,我们建议针对每个栏位都明确定义出它该有的形式,例如日期栏位就只能出现数字和斜线字元,而ID栏位,仅能出现英文字母及底线字元等等。如此一来,想要透过信息植入有害的组成,就不是那么容易可以办到了。

对于同样需要对输入信息进行检核的跨站指令代码攻击来说,要做到输入信息的过滤,最好的策略也是基于白名单来过滤。例如,允许使用者输入HTML语法的地方,若仅允许输入图片,则可开放形式的输入,其余则否。这么一来,想要规避过滤的规则,难度就比较高了。

当然,采取较严格的白名单政策,程序在撰写难度上比较高,此外,允许使用者信息输入的形式也就更为受限,但这是为了安全必须付出的代价。

将网页内容编代码,提升防御力

除了针对输入信息进行白名单式的过滤之外,针对输出的页面内容进行编代码,也是实务上能派上用场的技巧。输入信息的过滤是针对可疑的信息进行防范,而针对输入进行编代码,则是让可能造成危害的信息变成无害。

庆幸的是,有许多程序语言都推出了为了防范跨站指令代码攻击的程序库,协助程序设计者针对HTML输出内容进行编代码。例如PHP的 htmlentities()或是htmlspecialchars()、ASP的Server.HTMLEncode()、ASP.NET的 Server.HtmlEncode()等等。让专门的程序库来处理输入内容的编代码,也可以减少程序设计者自行开发的额外成本,同时也能提供更为完善的防备考虑。而像微软,更提供了一个名为MicrosoftAnti-CrossSiteScriptingLibrary的程序库,提供了各种HTML、 JavaScript、URL、XML、VBScript的过滤及编代码机制。如此一来.便可以透过这一套程序库,将来自于使用者输入的字串,或是以使用者输入字串为基础的输出字串进行转换,成为单纯的文字,而不含可于浏览器上执行script程序,因此能够降低遭受到攻击的风险。

彻底分析程序代码可能的弱点

在撰写程序时,如果能够留意输入信息的输出,以及输出页面内容的编代码,相信可以增加不少防范的强度。但是,针对既有的程序代码,倘若存在跨站指令代码攻击的漏洞,又该如何察觉并进行防范呢?基本上,你可以采取一个系统性的分析方法。

若想要审视你的程序代码是否具有跨站指令代码攻击的问题,根据《WritingSecureCode》一书中的建议,首先,你必须要列举出你的网站程序中所有接收自使用者端送出信息的地方。所谓使用者端的信息,包括了你的网站程序所读取表格中的每个栏位、来自于网址中的查询字串、cookies 的值、HTTP的标头等等。因为,不要忘了,所有来自于使用者的信息“都是邪恶的”。

找到了每个接收使用者端信息的地方后,便可以逐一追踪每笔信息在应用程序中的流向,检验所接收到的信息,最终是否会反映到输出的页面结果中。这中间,你可能直接把接收来的信息稍微加工后,就当做输出结果送出去了也有可能,先把所接收到的信息储存在信息库或档案中,于日后才做为输出结果送出。

倘若,你所找出来的信息最终会成为输出页面的一部份,那么,你就应该检查这份信息是否足够“乾净”,也就是说,你是否有针对这份信息进行足够的过滤或在输出时加上了编代码的动作。倘若没有,那么,这份信息就有可能成为跨站指令代码攻击被发动的点。也就是说,你应该针对这份信息的输入及输出,进行相对应的处理。透过以上的步骤,有助于你审视既有的程序代码的问题。

除此之外,检查你的JavaScript程序中,动用到innerHTML以及document.write()的地方,是否有安全疑虑,也会有帮助。另外,在JavaScript程序中使用eval(),同样有可能造成安全问题。eval()函数允许在浏览器上直接将传入该函数的参数做为 JavaScritp算式或是可执行的语句,动态的评估算式之值或是加以执行。倘若,eval()所接收的参数之值,是来自于使用者输入的部份信息,那么可以造成的危机就大了,因为这意谓着,使用者有机会控制透过eval()所执行的JavaScript语句,系统安全形同门户洞开。这也就是近来,为什么许多守则都建议不要使用eval()的原因,因为太容易形成安全性的漏洞了。

跨站指令代码攻击的确是十分难以防守的攻击形式,但不小心,就有可能造成颇大的伤害。遗憾的是,还是有不少Web应用程序设计者并没有小心的提防,希望可以透过这里,带给你基本的认知。关于跨站指令代码攻击其中还充满许多高深的探讨和技巧,不在本文范围之内,建议有兴趣者自行钻研之。