EMwI插件更新:防XSS攻击

来源:互联网 发布:网络教育笔试考试 编辑:程序博客网 时间:2024/05/24 04:53

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


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


function admin_post_add_external_media_without_import() {$info = add_external_media_without_import();$redirect_url = 'upload.php';if ( ! isset( $info['id'] ) ) {$redirect_url = $redirect_url .  '?page=add-external-media-without-import&url=' . urlencode( $info['url'] );$redirect_url = $redirect_url . '&error=' . urlencode( $info['error'] );$redirect_url = $redirect_url . '&width=' . urlencode( $info['width'] );$redirect_url = $redirect_url . '&height=' . urlencode( $info['height'] );$redirect_url = $redirect_url . '&mime-type=' . urlencode( $info['mime-type'] );}wp_redirect( admin_url( $redirect_url ) );exit;}

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


<label><?php echo __('Width'); ?></label><input id="emwi-width" name="width" type="number" value="<?php echo urldecode( $_GET['width'] ); ?>"><label><?php echo __('Height'); ?></label><input id="emwi-height" name="height" type="number" value="<?php echo urldecode( $_GET['height'] ); ?>"><label><?php echo __('MIME Type'); ?></label><input id="emwi-mime-type" name="mime-type" type="text" value="<?php echo urldecode( $_GET['mime-type'] ); ?>">


问题就出在这段响应函数的代码中。从$_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的值打印到input的value中:


<input id="emwi-mime-type" name="mime-type" type="text" value="<?php echo $_GET['mime-type']; ?>">

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


<input id="emwi-mime-type" name="mime-type" type="text" value="\"><script>alert(window.location.hash);</script>"\"">

(上面这行代码是在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中的代码应改为:


<input id="emwi-mime-type" name="mime-type" type="text" value="<?php echo esc_html( $_GET['mime-type'] ); ?>">

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

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




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


'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' --disable-xss-auditor

本文在我的独立博客上的地址:http://zxtechart.com/2017/10/24/emwi-xss/