CVE-2018-8893:
看到某大佬在群里发了一个 CVE 通告,打开网页有看到:
正好想找点事做,遂做一波分析。
首先定位到 plugin_edit.php 文件,在 /zb_user/plugin/AppCentre/plugin_edit.php ,看下代码:
注意到红框框里的代码,是一个判断的流程,本意是判断输入的插件的 ID 只能是数字或字母,并且长度只能在3到30个之间,但是这个正则引起了我的注意,他的正则是:
^[A-Za-z0-9_]{3,30}
它这个正则的意思是:以字母或数字开头,匹配3到30个。 然后,然后就没了。所以只要前3个字符是数字或字幕就能通过它正则的检查了,比如我输入:test<?php phpinfo();?> ,虽然后面出现了各种符合和空格,但是前面的 test 符合了它的正则,所以这个正则依然能匹配到,就能通过它正则的检测了,如图:
而正确的正则应该是:
^[A-Za-z0-9_]{3,30}$
注意最后加了一个$符号,意思是匹配以字幕或数字开头,并且以数字或字母结束,长度在3到30个字符之间:
所以这个正则的判断就可以绕过了。 然后怎么 Getshell 呢? 看上面代码的两个箭头,有两处有写入文件的操作,就在这里。 结合上面的代码代码可以知道它的逻辑是从一个既有的插件的文件模板里把对应的文件复制到新建的插件文件夹里面,文件夹的名字就是插件的ID,再把模板中一些插件的信息替换一下写入到新建插件的目录里。
好了,那看下最主要的需要复制过去的模板文件 main.html (/zb_user/plugin/AppCentre/tpl/main.html):
两处箭头所指的就是要替换的插件信息,替换逻辑在上面的 plugin_edit.php 的58到61行:
$file = file_get_contents('tpl/main.html'); $file = str_replace("<%appid%>", $app->id, $file); $path = $zbp->usersdir . 'plugin/' . $app->id . '/' . trim($_POST['app_path']); @file_put_contents($path, $file);
就是单纯的把 <%appid%> 替换为新建插件的 ID 值。那么就开始构造了,构造的目标就是把一句话木马写进去,并且符合 PHP 的语法保证能让这个脚本文件跑起来,而因为上面绕过了正则检测,所以能写入除了字母和数字以外的字符。 但是这里有存在一个问题:仔细观察需要替换的两处位置会发现第一个需要替换的地方在语法上比第二处多了一个右括号,这就意味着无论怎么构造,要符合两处替换位置的语法是不可能的,如果符合第一处的语法,第二处肯定不符合,反之亦然(反正我没想到,不知道大佬们有什么姿势没有?)。
那就换一个思路,既然需要符合两处替换位置的语法不可能,那就让它符合一处的就好了,想到利用 ?> 闭合掉 PHP 的代码解析,让第二处替换位置不再当作 PHP 代码执行而是当作普通的文本,最终构造出的 payload:
AppCentre') || eval($_POST[z]))?>
替换后的代码如下(注意代码高亮的变化):
此时第二处替换的位置就已经不被解析为 PHP 的代码而是普通的文本了,此时已经把一句话木马写入到 main.php 文件中了,Getshell达成,测试一下:
证明成功 Getshell 了,注意下上面圈起来的部分,因为写入的 payload 有问号,而问号在 URL 中是当作 GET 参数的开始符号,所以这里需要进行编码,否则会找不到路径。
结合这个操作有 CSRF 漏洞,就可以构造如下 payload 来通过 CSRF 来 Getshell:
But... 当我再次测试的时候发现了一个问题:
这次发送的数据包和上次相比少了 cookie,发现报错了,报的错误是没有管理员权限,这个时候我们回头去看看 main.html 这个新建插件的模板文件:
看到第二行进行了是否为管理员权限的检查。 这么说的话这个 CVE 就非常鸡肋了,虽然能通过 CSRF 来 Getshell,但是生成的 Webshell 需要管理员才能访问,那意义就不大了。
但是... 我一不小心在插件管理的位置挖到一枚 XSS 了(手动斜笑脸),然后我不要脸的去申请了一个 CVE,请看下面的:CVE-2018-9169
CVE-2018-9169:
这枚 XSS 没什么技术含量,也是在测试上一个 CVE 的时候无意中发现的。
测试的时候,插入了 payload 之后在插件管理的位置发现了如下情况:
一看,有戏。 看下输出位置的 HTML 代码:
把我插入的 payload 输出到页面上了,但是注意一下我插入的 payload:
AppCentre') || eval($_POST[z]))?>
这里我插的是单引号,但是浏览器输出的闭合的引号是双引号。那看看输出的代码吧,在/zb_user/plugin/AppCentre/plugin.js 的38行:
$(this).parent().children().eq(4).append(" ");
注意到这里的输出也是使用单引号的,但是输出到页面上的时候就变成双引号了。 这其实涉及到浏览器的容错性问题,浏览器对 HTML 的语法其实不是那么的严格,为了给某些抠脚的前端程序猿做容错,会自动把一些能识别的错误纠正(比如补全、闭合之类的),或者把一些不太标准的 HTML 代码改为比较标准的形式,而这种容错在不同的浏览器内核中还不一样,这也是为什么能看到一些奇葩的 XSS payload 在某些特定的浏览器中能触发的原因。
好了,科普结束,上面也是这个原因,浏览器把单引号改为了双引号导致的。
既然有 XSS 了,那么,弹个窗?在新建插件的插件 ID 处输入:aaaa‘><svg onload=alert(1)>,保存后在插件管理处触发:
是的,这也是一枚非常鸡肋的 Self-XSS。
Exploit 的编写:
好了,整理下现在手上有的小洞洞: 一个CSRF,一个Getshell(生成的 shell 需要管理员权限才能访问),一个XSS。
那么怎么组合让鸡肋的小洞洞发挥大大的威力呢?!然后想到如下思路:
当管理员点击我们的恶意链接之后,先使用 CSRF 插入一个 XSS 的 payload(CVE-2018-9169),再当管理员点击插件管理功能的时候,触发 XSS,而 XSS 里 payload 的操作如下:先使用 CVE-2018-8893 拿到一个只有管理员才能访问的 shell,再通过这个 shell 生成一个不需要权限就能访问的 shell,而因为这些需要管理员的交互,所以我需要知道管理员什么时候触发了我们的 exp,就写一个回调,加载 XSS 平台的 payload,最后,因为利用 CVE-2018-9169 和 CVE-2018-8893 会在插件管理的位置产生利用痕迹,所以还需要清除痕迹。
那先放 CVE-2018-9169 的利用 exp:
注意下圈圈的位置,上面的分析里说了,会把新建的插件 ID 当作路径,所以如果使用斜杠/ 的话会跟 Linux 路径的分隔符产生冲突,导致插入不成功,但是使用反斜杠\ 的话,处理的时候系统会自动帮我们转成斜杠/ ,这也算是一种容错机制把。 然后利用的时候把 script 标签里的脚本换成自己的上传的脚本的路径,而这个脚本就是后续利用过程的 exp,下面放出。
exp.js:
18行之前都是一些配置,还有生成 AJAX 请求对象,19和20行是构造 payload 的过程,22到25行是利用 CVE-2018-8893 的过程,27到30行是使用 CVE-2018-8893 生成的 payload 再生成一个不需要权限就能访问的一句话木马的过程(因为在后台触发 XSS 的时候是同源了,所以能使用 AJAX,再因为此时是管理员权限,所以能访问到CVE-2018-8893生成的 shell,而为什么要生成在那个目录呢?因为这个是插件管理的目录,如果能生成插件,证明这个目录肯定是有写入权限的,而其他目录就未必有写入权限了,所以把后门写在这个目录会稳妥一点),32到37行是加载回调脚本的过程(如果有的话),39到47行是清除痕迹的操作,49行是刷新页面(清除痕迹之后不能让看到)。
执行效果:
管理员点击恶意链接再点击插件管理就生成了后门,在生成了一句话后门之后,XSS平台也可以收到打过来的 cookie 等的信息(因为我 XSS 平台的密码忘了,之前测试的图也截不了了,有兴趣的自己测试一下)。
再稍微说一下,写文章的时候我又想到一些小 trick 和不足。 在 CSRF 的时候其实就可以一次性把所有的操作都做了,像我上面的流程需要交互其实最主要是因为触发 CSRF 之后页面会跳转,跳转后的页面就不受我们控制了,但最近想到可以利用 iframe 标签来触发 CSRF,这样就可以触发 XSS 后依然不跳转,算是一个小 trick。 还有就是插入 payload 的时候会在插件管理的地方有很明显的痕迹,其实可以该下 payload,让痕迹不明显,把单引号改成双引号就可以了,就不展开了,有兴趣的自己去测试一下。
CVE-2018-8893 官方已经在3月29日修复了并且发布补丁了(其实就修了CSRF),所以触发链已经断了,如果要测试可以点击:链接: https://pan.baidu.com/s/16scHzjhjjb_aqqrUWytHYg 密码: kj8b ,这个是没修复之前的版本。(上传附件老是不成功,不知道是不是 bug,就贴链接吧)
CVE-2018-9153
这枚 CVE 也是顺便的厚着脸皮去申请的。没什么技术含量,顺便提一下吧。
在插件管理的位置注意到有插件上传:
代码的位置在 /zb_system/function/lib/app.php:
注意到22行执行了 UnPack 函数,跟过去:
注意到503行有一个 simplexml_load_string($xml); 这里其实存在 XXE,我也复现成功了,但是懒得去申请了,SRC 又不收,就算了。
然后根据529到536行的逻辑构造 payload:
上传后,成功得到后门:
噢!顺便一提,这个接口也有 CSRF,这个 Exploit 就不放了,没什么意思
本文作者为Mr.Wu,转载请注明,尊守博主劳动成果!
由于经常折腾代码,可能会导致个别文章内容显示错位或者别的 BUG 影响阅读; 如发现请在该文章下留言告知于我,thank you !