EMwI更新:版本1.0.2 防XSS攻击

8月底的时候External Media without Import插件在github上收到一个pull request。对方指出我的代码存在XSS漏洞。惭愧,直到最近才腾出时间仔细研究他说的问题。插件的1.0.2版本合并了对方的pull request,修复了该漏洞。

在修复之前,我的插件中有如下代码(点击查看源文件):

这个函数将width 、height 和mime-type等信息通过 urlencode编码后设置为url的参数,然后重定向到该url。在url的响应函数中,我调用了 urldecode读取来这些信息:

问题就出在这段响应函数的代码中。从 $_GET中取出的值已经是经过 urldecode的。对 $_GET再调用 urlencode就可能会出问题。比如 $info['mime-type'] = 'image/svg+xml'的情况下, urlencode( $info['mime-type'] )的结果是 'image%2fsvn%2bxml', $_GET['mime-type']的值已经是解码后的 'image/svn+xml',再对它调用一次 urldecode就变成了 'image/svn xml',这就导致了错误的mime-type。PHP官方文档对这个问题也有说明

然而直接使用 $_GET也有安全性问题。以响应函数中将mime-type的值显示在输入框的代码为例,假设我们直接将 $_GET的值打印到 inputvalue中:

这种情况下如果 $_GET['mime-type'] 的值当中包含一段可执行代码,那其中的代码就会在浏览器中执行。比如,如果url的末尾是mime-type=”><script>alert(window.location.hash)%3B<%2Fscript>”#Attack!,那么上面的php代码在浏览器中的输出就会变成下面这样:

(上面这行代码是在Chrome中实验得出的结果,其中的\可能是浏览器自己转义加上的。)

于是用户浏览网页的时候, alert调用就会被执行,弹出Attack!提示框。

这样会有什么问题呢?假如有一个恶意用户老王。他构造了一个url,url的mime-type(或其它任意参数)实际包含了一段老王自己编写的代码。然后他把这个url发给小红让小红点击。不明就里的小红点击了这个url之后,由于网站的PHP代码不够健壮,将 $_GET 直接输出到了网页中,因此老王的代码就得以在小红的浏览器中执行。这时老王的代码就可以做很多事,比如窃取小红浏览器里的cookie,以及存储在localStorage里的用户名、密码等隐私信息。

老王的这种手段就叫XSS攻击,全称Cross-Site Scripting,跨站脚本攻击。其原理和SQL注入类似,均是利用网站后台代码的漏洞,在参数里填入可执行代码,从而将攻击者的代码注入到本不允许其执行的地方(在XSS场景中就是用户浏览器,在SQL注入场景中则是后台数据库),从而实现攻击者的目的。

为了应对XSS攻击,就需要对 $_GET的值做些处理,对其中的特殊字符进行转义,比如将<转换成&lt;,然后才能将其输出到网页的html中。WordPress为此提供了一个很方便的函数: esc_html (见:https://codex.wordpress.org/Function_Reference/esc_html)。这个函数不但会对html特殊字符进行转义,还会检查非法的UTF-8字符,考虑了各种情况。

因此将 $_GET 的值输出到网页之前应先对其调用 esc_html ,于是上文将 $_GET 的值打印到 input 的 value 中的代码应改为:

这样整个 $_GET['mime-type'] 的值都会被当成纯字符串而不存在被执行的可能。

值得一提的是,现代主流浏览器大都已经对XSS攻击采取了防护措施。比如在Chrome上浏览含有XSS漏洞的网页的话,其实会被Chrome拦截(Safari也一样):

如果要重现上文写到的XSS漏洞,允许攻击者代码执行的话,需要从命令行启动Chrome,并且用 --disable-xss-auditor 选项关闭Chrome的XSS检查。Mac上的启动命令如下:

 

《EMwI更新:版本1.0.2 防XSS攻击》上有1条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注