September 14, 2018

Zabbix高危sql注入漏洞分析

0x00.漏洞背景

zabbix是一个开源的企业级性能监控解决方案。zabbix的jsrpc的profileIdx2参数存在insert方式的SQL注入漏洞,攻击者无需授权登陆即可登陆zabbix管理系统,也可通过script等功能轻易直接获取zabbix服务器的操作系统权限。
通过zoomeye搜索zabbix组件,共搜索到大约13963个主机,而其中很大一部分主机未禁用guest账户,因而漏洞影响相对很广。

0x01.影响版本

version 2.x(<2.2.14)
version 3.x(<3.0.4)

0x02.漏洞说明


POC:url+/jsrpc.php?sid=0bcd4ade648214dc&type=9&method=screen.get&tim
estamp=1471403798083&mode=2&screenid=&groupid=&hostid=0&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=2'3297&updateProfil
e=true&screenitemid=&period=3600&stime=20160817050632&resourcetype=
17&itemids%5B23297%5D=23297&action=showlatest&filter=&filter_task=&
mark_color=1

注:该poc只在未禁用guest账户的情况下生效,禁用guest账户时无法进行报错注入 0x03.漏洞详情


参数走向jsrpc.php->CScreenBuilder.php构造函数->CScreenBase.php calculateTime函数->profiles.inc.php update函数->profiles.inc.php get函数
-> profiles.inc.php update函数
sql语句执行:page_footer.php46行->profile.inc.php::flush函数->profiles.inc.php::inserDB函数->db.inc.php::DBexecute函数

最初的注入产生在latest.php,这里根据其衍生注入jsrpc.php的poc进行分析: pocjsrpc.php文件profileIdx2参数存在注入 首先跟进jsrpc文件case:’screen.get’(178行)出现resourcetype的if判断 全局搜索SCREEN_RESOURCE_HISTORY以及SCREEN_RESOURCE_CHART在include/defines.inc.php知,SCREEN_RESOURCE_HISTORY=17,SCREEN_RESOURCE_CHART为18, poc中resourcetype参数为17,满足if语句,函数执行至大约205行 $screenBase=CScreenBuilder::getScreen($options)

$options进入 CScreenBuilder类的getScreen函数,跟进include/classes/screens/CScreenBuilder.php函数148行
在CScreenBuilder类的构造函数中(大约148行)我们可以看到profileIdx2参数进入了CScreenBase类calculateTime函数

继续跟进include/classes/screens/CScreenBase.php,我们看到经过344行的if语句判断,profileIdx2进入CProfile类的update函数

跟进include/classes/profiles.inc.php 函数CProfile类的update方法(大约154行)
$current由 get函数获得,跟进get函数(88行)

我们打印CWebUser::data发现,在不禁用以及禁用guest账户两种情况下,该变量均不为空,
而根据profile变量可知get函数中两个if语句均不成立,函数在104行返回空值
进而update方法中$current变量为空,函数执行至161行self::$insert[$idx][$idx2] = $profile;到这儿程序仍未执行任何db操作,仅仅把参数代入了$insert变量;

接下来我们回到jsrpc.php,发现末尾包含了page_footer.php,跟进该文件
在经过一系列if语句后,在46行出调用了include/classes/class.cwebuser.php中CProfile::flush();
跟进flush函数:

我们看到如果$insert或者$update变量发生改变,即执行db相关操作,调用insertDB函数,因此变量最终进入insertDB函数187行执行,而include/db.inc.php中仍然对恶意参数没有任何过滤,进而导致注入产生

最后我们需要明确两个问题:
1.CWebUser::data变量为什么始终不为空,跟进include/classes/core/Zbase.php
我们可以看到在整个程序框架初始化时会验证用户ssession,如果为空,则调用setDefault()方法

跟进include/classes/class.cwebuser.php的setdefault()方法
我们发现setDefault()方法会默认赋值给data

因此未禁用以及禁用guest账户两张情况,CWebUser::data都不会为空

2.禁用guest账户为什么无法进行注入?
我们看到执行flush函数是需要userid>0,当未禁用账户时,虽然setDefault()方法会使得userid=0,我们跟进include/classes/class.cwebuser.php中checkAuthentication函数

由defines.inc.php知,ZBX_GUEST_USER为guest账户,guest账户userid=2,因此在未禁用guest账户时可以进行注入。

我们继续跟进API::User()->login()方法

我们发现这儿对userid有权限检查,当我们禁用guest账户时,就会在1066行产生异常,以至于userid仍然为0,flush函数无法执行,因此禁用guest账户时无法进行注入。

0x03.漏洞证明

0x04.漏洞修复

1.在include/classes/class.cwebuser.php中flush函数,将75行foreach处$idx2更改为zbx_dbstr($idx2),即使用zabbix自带过滤函数对参数$idx2(即profileIdx2参数)进行过滤(临时)
2.Version2.x版本更新至2.2.14,Version3.x 版本更新至3.0.4(推荐)