description = [[ Extracts a list of Citrix servers from the ICA Browser service. ]] --- -- @usage sudo ./nmap -sU --script=citrix-enum-servers -p 1604 -- -- @output -- PORT STATE SERVICE -- 1604/udp open unknown -- | citrix-enum-servers: -- | CITRIXSRV01 -- |_ CITRIXSRV02 -- -- Version 0.2 -- Created 11/26/2009 - v0.1 - created by Patrik Karlsson -- Revised 11/26/2009 - v0.2 - minor packet documentation author = "Patrik Karlsson" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "safe"} require "comm" require "shortport" portrule = shortport.portnumber(1604, "udp") -- -- process the response from the server -- @param response string, complete server response -- @return string row delimited with \n containing all published applications -- function process_server_response(response) local pos, packet_len = bin.unpack("SS", response) local server_name local server_list = {} if packet_len < 40 then return end -- the list of published applications starts at offset 40 offset = 41 while offset < packet_len do pos, server_name = bin.unpack("z", response:sub(offset)) offset = offset + pos - 1 table.insert(server_list, server_name) end return server_list end action = function(host, port) local packet, counter, socket local query = {} local server_list = {} -- -- Packets were intercepted from the Citrix Program Neighborhood client -- They are used to query a server for it's list of published applications -- -- We're really not interested in the responses to the first two packets -- The third response contains the list of published applications -- I couldn't find any documentation on this protocol so I'm providing -- some brief information for the bits and bytes this script uses. -- -- Spec. of response to query[2] that contains a list of published apps -- -- offset size content -- ------------------------- -- 0 16-bit Length -- 12 32-bit Server IP (not used here) -- 30 8-bit Last packet (1), More packets(0) -- 40 - null-separated list of applications -- query[0] = string.char( 0x1e, 0x00, -- Length: 30 0x01, 0x30, 0x02, 0xfd, 0xa8, 0xe3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ) query[1] = string.char( 0x2a, 0x00, -- Length: 42 0x01, 0x32, 0x02, 0xfd, 0xa8, 0xe3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ) counter = 0 socket = nmap.new_socket() socket:set_timeout(5000) try = nmap.new_try(function() socket:close() end) try(socket:connect(host, port)) -- send the two first packets and never look back repeat try(socket:send(query[counter])) packet = try(socket:receive()) counter = counter + 1 until (counter>#query) -- process the first response server_list = process_server_response( packet ) -- -- the byte at offset 31 in the response has a really magic function -- if it is set to zero (0) we have more response packets to process -- if it is set to one (1) we have arrived at the last packet of our journey -- while packet:sub(31,31) ~= string.char(0x01) do packet = try( socket:receive() ) local tmp_table = process_server_response( packet ) for _, v in ipairs(tmp_table) do table.insert(server_list, v) end end if #server_list>0 then nmap.set_port_state(host, port, "open") end socket:close() return stdnse.format_output(true, server_list) end