1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-15 20:29:03 +00:00

Add the scripts

citrix-brute-xml
  citrix-enum-apps
  citrix-enum-apps-xml
  citrix-enum-servers
  citrix-enum-servers-xml
and the citrixxml modules, all by Patrik Karlsson.
This commit is contained in:
david
2009-12-14 07:30:38 +00:00
parent 304762b07d
commit f2ae05968b
8 changed files with 1240 additions and 0 deletions

View File

@@ -16,6 +16,17 @@ o Added new NSE scripts:
system, server build date, and upstream time server IP address.
[Richard Sammet]
o citrix-brute-xml uses the unpwdb library to guess credentials for
the Citrix PN Web Agent Service. [Patrik Karlsson]
o citrix-enum-apps and citrix-enum-apps-xml get a list of published
applications from the Citrix ICA Browser or XML service,
respectively. [Patrik Karlsson]
o citrix-enum-servers and citrix-enum-servers-xml.nse get a list of
Citrix servers from the Citrix ICA Browser or XML service,
respectively. [Patrik Karlsson]
o Removed a limitation of snmp.lua that only allowed it to properly
encode OID component values up to 127. The bug was reported by
Victor Rudnev. [David]

571
nselib/citrixxml.lua Normal file
View File

@@ -0,0 +1,571 @@
---
-- This module was written by Patrik Karlsson and facilitates communication
-- with the Citrix XML Service. It is not feature complete and is missing several
-- functions and parameters.
--
-- The library makes little or no effort to verify that the parameters submitted
-- to each function are compliant with the DTD
--
-- As all functions handling requests take their parameters in the form of tables,
-- additional functionality can be added while not breaking existing scripts
--
-- Details regarding the requests/responses and their parameters can be found in
-- the NFuse.DTD included with Citrix MetaFrame/Xenapp
--
-- This code is based on the information available in:
-- NFuse.DTD - Version 5.0 (draft 1) 24 January 2008
--
require 'http'
module(... or "citrix",package.seeall)
--- Decodes html-entities to chars eg. &#32; => <space>
--
-- @param str string to convert
-- @return string an e
function decode_xml_document(xmldata)
local hexval
if not xmldata then
return ""
end
local newstr = xmldata
for m in xmldata:gmatch("(\&\#%d+;)") do
hexval = m:match("(%d+)")
if ( hexval ) then
newstr = xmldata:gsub(m, string.char(hexval))
end
end
return newstr
end
--- Sends the request to the server using the http lib
--
-- NOTE:
-- At the time of the development (20091128) the http
-- lib does not properly handle text/xml content. It also doesn't
-- handle HTTP 100 Continue properly. Workarounds are in place,
-- please consult comments.
--
-- @param host string, the ip of the remote server
-- @param port number, the port of the remote server
-- @param xmldata string, the HTTP data part of the request as XML
--
-- @return string with the response body
--
function send_citrix_xml_request(host, port, xmldata)
local header = "POST /scripts/WPnBr.dll HTTP/1.1\r\n"
header = header .. "Content-type: text/xml\r\n"
header = header .. "Host: " .. host .. ":" .. port .. "\r\n"
header = header .. "Content-Length: " .. xmldata:len() .. "\r\n"
header = header .. "Connection: Close\r\n"
header = header .. "\r\n"
local request = header .. xmldata
-- this would have been really great! Unfortunately buildPost substitutes all spaces for plus'
-- this ain't all great when the content-type is text/xml
-- local response = http.post( host, port, "/scripts/WPnBr.dll", { header={["Content-Type"]="text/xml"}}, nil, xmldata)
-- let's build the content ourselves and let the http module do the rest
local response = http.request(host, port, request)
local parse_options = {method="post"}
-- we need to handle another bug within the http module
-- it doesn't seem to recognize the HTTP/100 Continue correctly
-- So, we need to chop that part of from the response
if response and response:match("^HTTP/1.1 100 Continue") and response:match( "\r?\n\r?\n" ) then
response = response:match( "\r?\n\r?\n(.*)$" )
end
-- time for next workaround
-- The Citrix XML Service returns the header Transfer-Coding, rather than Transfer-Encoding
-- Needless to say, this screws things up for the http library
if response and response:match("Transfer[-]Coding") then
response = response:gsub("Transfer[-]Coding", "Transfer-Encoding")
end
local response = http.parseResult(response, parse_options)
-- this is *probably* not the right way to do stuff
-- decoding should *probably* only be done on XML-values
-- this is *probably* defined in the standard, for anyone interested
return decode_xml_document(response.body)
end
--- Request information about the Citrix Server Farm
--
-- Consult the NFuse.DTD for a complete list of supported parameters
-- This function implements all the supported parameters described in:
-- Version 5.0 (draft 1) 24 January 2008
--
-- @param socket socket, connected to the remote web server
-- @return string HTTP response data
--
function request_server_farm_data( host, port )
local xmldata = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
xmldata = xmldata .. "<!DOCTYPE NFuseProtocol SYSTEM \"NFuse.dtd\">\r\n"
xmldata = xmldata .. "<NFuseProtocol version=\"1.1\">"
xmldata = xmldata .. "<RequestServerFarmData></RequestServerFarmData>"
xmldata = xmldata .. "</NFuseProtocol>\r\n"
return send_citrix_xml_request(host, port, xmldata)
end
--- Parses the response from the request_server_farm_data request
-- @param response string with the XML response
-- @return table containing server farm names
--
function parse_server_farm_data_response( response )
local farms = {}
response = response:gsub("\r?\n","")
for farm in response:gmatch("<ServerFarmName.->([^\<]+)</ServerFarmName>") do
table.insert(farms, farm)
end
return farms
end
--- Sends a request for application data to the Citrix XML service
--
-- Consult the NFuse.DTD for a complete list of supported parameters
-- This function does NOT implement all the supported parameters
--
-- Supported parameters are Scope, ServerType, ClientType, DesiredDetails
--
-- @param host string the host which is to be queried
-- @param port number the port number of the XML service
-- @param params table with parameters
-- @return string HTTP response data
--
function request_appdata(host, port, params)
-- setup the mandatory parameters if they're missing
local scope = params['Scope'] or "onelevel"
local server_type = params['ServerType'] or "all"
local client_type = params['ClientType'] or "ica30"
local desired_details = params['DesiredDetails'] or nil
local xmldata = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
xmldata = xmldata .. "<!DOCTYPE NFuseProtocol SYSTEM \"NFuse.dtd\">\r\n"
xmldata = xmldata .. "<NFuseProtocol version=\"5.0\">"
xmldata = xmldata .. "<RequestAppData>"
xmldata = xmldata .. "<Scope traverse=\"" .. scope .. "\" />"
xmldata = xmldata .. "<ServerType>" .. server_type .. "</ServerType>"
xmldata = xmldata .. "<ClientType>" .. client_type .. "</ClientType>"
if desired_details then
if type(desired_details) == "string" then
xmldata = xmldata .. "<DesiredDetails>" .. desired_details .. "</DesiredDetails>"
elseif type(desired_details) == "table" then
for _, v in ipairs(desired_details) do
xmldata = xmldata .. "<DesiredDetails>" .. v .. "</DesiredDetails>"
end
else
assert(desired_details)
end
end
xmldata = xmldata .. "</RequestAppData>"
xmldata = xmldata .. "</NFuseProtocol>\r\n"
return send_citrix_xml_request(host, port, xmldata)
end
--- Extracts the Accesslist section of the XML response
--
-- @param xmldata string containing results from the request app data request
-- @return table containing settings extracted from the accesslist section of the response
local function extract_appdata_acls(xmldata)
local acls = {}
local users = {}
local groups = {}
for acl in xmldata:gmatch("<AccessList>(.-)</AccessList>") do
if acl:match("AnonymousUser") then
table.insert(users, "Anonymous")
else
for user in acl:gmatch("<User>(.-)</User>") do
local user_name = user:match("<UserName.->(.-)</UserName>") or ""
local domain_name = user:match("<Domain.->(.-)</Domain>") or ""
if user_name:len() > 0 then
if domain_name:len() > 0 then
domain_name = domain_name .. "\\"
end
table.insert(users, domain_name .. user_name)
end
end
for group in acl:gmatch("<Group>(.-)</Group>") do
local group_name = group:match("<GroupName.->(.-)</GroupName>") or ""
local domain_name = group:match("<Domain.->(.-)</Domain>") or ""
if group_name:len() > 0 then
if domain_name:len() > 0 then
domain_name = domain_name .. "\\"
end
table.insert(groups, domain_name .. group_name)
end
end
end
if #users> 0 then
acls['User'] = users
end
if #groups>0 then
acls['Group'] = groups
end
end
return acls
end
--- Extracts the settings section of the XML response
--
-- @param xmldata string containing results from the request app data request
-- @return table containing settings extracted from the settings section of the response
local function extract_appdata_settings(xmldata)
local settings = {}
settings['appisdisabled'] = xmldata:match("<Settings.-appisdisabled=\"(.-)\".->")
settings['appisdesktop'] = xmldata:match("<Settings.-appisdesktop=\"(.-)\".->")
for s in xmldata:gmatch("<Settings.->(.-)</Settings>") do
settings['Encryption'] = s:match("<Encryption.->(.-)</Encryption>")
settings['AppOnDesktop'] = s:match("<AppOnDesktop.-value=\"(.-)\"/>")
settings['AppInStartmenu'] = s:match("<AppInStartmenu.-value=\"(.-)\"/>")
settings['PublisherName'] = s:match("<PublisherName.->(.-)</PublisherName>")
settings['SSLEnabled'] = s:match("<SSLEnabled.->(.-)</SSLEnabled>")
settings['RemoteAccessEnabled'] = s:match("<RemoteAccessEnabled.->(.-)</RemoteAccessEnabled>")
end
return settings
end
--- Parses the appdata XML response
--
-- @param xmldata string response from request_appdata
-- @return table containing nestled tables closely resembling the DOM model of the XML response
function parse_appdata_response(xmldata)
local apps = {}
xmldata = xmldata:gsub("\r?\n",""):gsub(">%s+<", "><")
for AppData in xmldata:gmatch("<AppData>(.-)</AppData>") do
local app_name = AppData:match("<FName.->(.-)</FName>") or ""
local app = {}
app['FName'] = app_name
app['AccessList'] = extract_appdata_acls(AppData)
app['Settings'] = extract_appdata_settings(AppData)
table.insert(apps, app)
end
return apps
end
--
--
-- @param flags string, should be any of following: alt-addr, no-load-bias
--
function request_address(host, port, flags, appname)
local xmldata = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
xmldata = xmldata .. "<!DOCTYPE NFuseProtocol SYSTEM \"NFuse.dtd\">\r\n"
xmldata = xmldata .. "<NFuseProtocol version=\"4.1\">"
xmldata = xmldata .. "<RequestAddress>"
if flags then
xmldata = xmldata .. "<Flags>" .. flags .. "</Flags>"
end
if appname then
xmldata = xmldata .. "<Name>"
xmldata = xmldata .. "<AppName>" .. appname .. "</AppName>"
xmldata = xmldata .. "</Name>"
end
xmldata = xmldata .. "</RequestAddress>"
xmldata = xmldata .. "</NFuseProtocol>\r\n"
return send_citrix_xml_request(host, port, xmldata)
end
--- Request information about the Citrix protocol
--
-- Consult the NFuse.DTD for a complete list of supported parameters
-- This function implements all the supported parameters described in:
-- Version 5.0 (draft 1) 24 January 2008
--
-- @param host string the host which is to be queried
-- @param port number the port number of the XML service
-- @param params table with parameters
-- @return string HTTP response data
--
function request_server_data(host, port, params)
local params = params or {}
local server_type = params.ServerType or {"all"}
local client_type = params.ClientType or {"all"}
local xmldata = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
xmldata = xmldata .. "<!DOCTYPE NFuseProtocol SYSTEM \"NFuse.dtd\">\r\n"
xmldata = xmldata .. "<NFuseProtocol version=\"1.1\">"
xmldata = xmldata .. "<RequestServerData>"
for _, srvtype in pairs(server_type) do
xmldata = xmldata .. "<ServerType>" .. srvtype .. "</ServerType>"
end
for _, clitype in pairs(client_type) do
xmldata = xmldata .. "<ClientType>" .. clitype .. "</ClientType>"
end
xmldata = xmldata .. "</RequestServerData>"
xmldata = xmldata .. "</NFuseProtocol>\r\n"
return send_citrix_xml_request(host, port, xmldata)
end
--- Parses the response from the request_server_data request
-- @param response string with the XML response
-- @return table containing the server names
--
function parse_server_data_response(response)
local servers = {}
response = response:gsub("\r?\n","")
for s in response:gmatch("<ServerName>([^\<]+)</ServerName>") do
table.insert(servers, s)
end
return servers
end
--- Request information about the Citrix protocol
--
-- Consult the NFuse.DTD for a complete list of supported parameters
-- This function implements all the supported parameters described in:
-- Version 5.0 (draft 1) 24 January 2008
--
-- @param host string the host which is to be queried
-- @param port number the port number of the XML service
-- @param params table with parameters
-- @return string HTTP response data
--
function request_protocol_info( host, port, params )
local params = params or {}
local xmldata = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
xmldata = xmldata .. "<!DOCTYPE NFuseProtocol SYSTEM \"NFuse.dtd\">\r\n"
xmldata = xmldata .. "<NFuseProtocol version=\"1.1\">"
xmldata = xmldata .. "<RequestProtocolInfo>"
if params['ServerAddress'] then
xmldata = xmldata .. "<ServerAddress addresstype=\"" .. params['ServerAddress']['attr']['addresstype'] .. "\">"
xmldata = xmldata .. params['ServerAddress'] .. "</ServerAddress>"
end
xmldata = xmldata .. "</RequestProtocolInfo>"
xmldata = xmldata .. "</NFuseProtocol>\r\n"
return send_citrix_xml_request(host, port, xmldata)
end
--- Request capability information
--
-- Consult the NFuse.DTD for a complete list of supported parameters
-- This function implements all the supported parameters described in:
-- Version 5.0 (draft 1) 24 January 2008
--
-- @param host string the host which is to be queried
-- @param port number the port number of the XML service
-- @param params table with parameters
-- @return string HTTP response data
--
function request_capabilities( host, port )
local xmldata = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
xmldata = xmldata .. "<!DOCTYPE NFuseProtocol SYSTEM \"NFuse.dtd\">\r\n"
xmldata = xmldata .. "<NFuseProtocol version=\"1.1\">"
xmldata = xmldata .. "<RequestCapabilities>"
xmldata = xmldata .. "</RequestCapabilities>"
xmldata = xmldata .. "</NFuseProtocol>\r\n"
return send_citrix_xml_request(host, port, xmldata)
end
--- Parses the response from the request_capabilities request
-- @param response string with the XML response
-- @return table containing the server capabilities
--
function parse_capabilities_response(response)
local servers = {}
response = response:gsub("\r?\n","")
for s in response:gmatch("<CapabilityId.->([^\<]+)</CapabilityId>") do
table.insert(servers, s)
end
return servers
end
--- Tries to validate user credentials against the XML service
--
-- Consult the NFuse.DTD for a complete list of supported parameters
-- This function implements all the supported parameters described in:
-- Version 5.0 (draft 1) 24 January 2008
--
--
-- @param host string the host which is to be queried
-- @param port number the port number of the XML service
-- @param params table with parameters
-- @return string HTTP response data
--
function request_validate_credentials(host, port, params )
local params = params or {}
local credentials = params['Credentials'] or {}
local xmldata = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
xmldata = xmldata .. "<!DOCTYPE NFuseProtocol SYSTEM \"NFuse.dtd\">\r\n"
xmldata = xmldata .. "<NFuseProtocol version=\"5.0\">"
xmldata = xmldata .. "<RequestValidateCredentials>"
xmldata = xmldata .. "<Credentials>"
if credentials['UserName'] then
xmldata = xmldata .. "<UserName>" .. credentials['UserName'] .. "</UserName>"
end
if credentials['Password'] then
xmldata = xmldata .. "<Password encoding=\"cleartext\">" .. credentials['Password'] .. "</Password>"
end
if credentials['Domain'] then
xmldata = xmldata .. "<Domain type=\"NT\">" .. credentials['Domain'] .. "</Domain>"
end
xmldata = xmldata .. "</Credentials>"
xmldata = xmldata .. "</RequestValidateCredentials>"
xmldata = xmldata .. "</NFuseProtocol>\r\n"
return send_citrix_xml_request(host, port, xmldata)
end
--- Parses the response from request_validate_credentials
-- @param response string with the XML response
-- @return table containing the results
--
function parse_validate_credentials_response(response)
local tblResult = {}
response = response:gsub("\r?\n","")
tblResult['DaysUntilPasswordExpiry'] = response:match("<DaysUntilPasswordExpiry>(.+)</DaysUntilPasswordExpiry>")
tblResult['ShowPasswordExpiryWarning'] = response:match("<ShowPasswordExpiryWarning>(.+)</ShowPasswordExpiryWarning>")
tblResult['ErrorId'] = response:match("<ErrorId>(.+)</ErrorId>")
return tblResult
end
--- Sends a request to reconnect session data
--
-- Consult the NFuse.DTD for a complete list of supported parameters
-- This function does NOT implement all the supported parameters
----
-- @param host string the host which is to be queried
-- @param port number the port number of the XML service
-- @param params table with parameters
--
function request_reconnect_session_data(host, port, params)
local params = params or {}
local Credentials = params.Credentials or {}
params.ServerType = params.ServerType or {}
params.ClientType = params.ClientType or {}
local xmldata = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
xmldata = xmldata .. "<!DOCTYPE NFuseProtocol SYSTEM \"NFuse.dtd\">\r\n"
xmldata = xmldata .. "<NFuseProtocol version=\"5.0\">"
xmldata = xmldata .. "<RequestReconnectSessionData>"
xmldata = xmldata .. "<Credentials>"
if Credentials.UserName then
xmldata = xmldata .. "<UserName>" .. Credentials.UserName .. "</UserName>"
end
if Credentials.Password then
xmldata = xmldata .. "<Password encoding=\"cleartext\">" .. Credentials.Password .. "</Password>"
end
if Credentials.Domain then
xmldata = xmldata .. "<Domain type=\"NT\">" .. Credentials.Domain .. "</Domain>"
end
xmldata = xmldata .. "</Credentials>"
if params.ClientName then
xmldata = xmldata .. "<ClientName>" .. params.ClientName .. "</ClientName>"
end
if params.DeviceId then
xmldata = xmldata .. "<DeviceId>" .. params.DeviceId .. "</DeviceId>"
end
for _, srvtype in pairs(params.ServerType) do
xmldata = xmldata .. "<ServerType>" .. srvtype .. "</ServerType>"
end
for _, clitype in pairs(params.ClientType) do
xmldata = xmldata .. "<ClientType>" .. clitype .. "</ClientType>"
end
xmldata = xmldata .. "</RequestReconnectSessionData>"
xmldata = xmldata .. "</NFuseProtocol>\r\n"
return send_citrix_xml_request(host, port, xmldata)
end

View File

@@ -0,0 +1,160 @@
description = [[ Attempts to guess valid credentials for the Citrix PN Web Agent XML Service.
The XML service authenticates against the local Windows server or the Active Directory.
CAUTION: This script makes no attempt of preventing account lockout.
If the password list contains more passwords than the lockout-threshold
accounts WILL be locked.
]]
---
-- @usage
-- nmap --script=citrix-brute-xml --script-args=userdb=<userdb>,passdb=<passdb>,ntdomain=<domain> -p 80,443,8080 <host>
--
-- @output
-- PORT STATE SERVICE REASON
-- 8080/tcp open http-proxy syn-ack
-- | citrix-brute-xml:
-- | Joe:password => Must change password at next logon
-- | Luke:summer => Login was successful
-- |_ Jane:secret => Account is disabled
--
---
-- Version 0.2
-- Created 11/30/2009 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 12/02/2009 - v0.2 - Use stdnse.format_ouput for output
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive", "auth"}
require 'unpwdb'
require 'shortport'
require 'citrixxml'
portrule = shortport.portnumber({8080,80,443}, "tcp")
--- Verifies if the credentials (username, password and domain) are valid
--
-- @param host string, the ip against which to perform
-- @param port number, the port number of the XML service
-- @param username string, the username to authenticate as
-- @param password string, the password to authenticate with
-- @param domain string, the Windows domain to authenticate against
--
-- @return success, message
--
function verify_password( host, port, username, password, domain )
local response = citrixxml.request_validate_credentials(host, port, {Credentials={Domain=domain, Password=password, UserName=username}})
local cred_status = citrixxml.parse_validate_credentials_response(response)
local account = {}
account.username = username
account.password = password
account.domain = domain
if cred_status.ErrorId then
if cred_status.ErrorId == "must-change-credentials" then
account.valid = true
account.message = "Must change password at next logon"
elseif cred_status.ErrorId == "account-disabled" then
account.valid = true
account.message = "Account is disabled"
elseif cred_status.ErrorId == "account-locked-out" then
account.valid = false
account.message = "Account Locked Out"
elseif cred_status.ErrorId == "failed-credentials" then
account.valid = false
account.message = "Incorrect Password"
elseif cred_status.ErrorId == "unspecified" then
account.valid = false
account.message = "Unspecified"
else
print("UNKNOWN response: " .. response)
account.valid = false
account.message = "failed"
end
else
account.message = "Login was successful"
account.valid = true
end
return account
end
--- Formats the result from the table of valid accounts
--
-- @param accounts table containing accounts (tables)
-- @return string containing the result
function create_result_from_table(accounts)
local result = ""
for _, account in ipairs(accounts) do
result = result .. " " .. account.username .. ":" .. account.password .. " => " .. account.message .. "\n"
end
return " \n" .. result
end
action = function(host, port)
local status, nextUser, nextPass
local username, password
local args = nmap.registry.args
local ntdomain = args.ntdomain
local valid_accounts = {}
if not ntdomain then
return "FAILED: No domain specified (use ntdomain argument)"
end
status, nextUser = unpwdb.usernames()
if not status then
return
end
status, nextPass = unpwdb.passwords()
if not status then
return
end
username = nextUser()
-- iterate over userlist
while username do
password = nextPass()
-- iterate over passwordlist
while password do
local result = "Trying " .. username .. "/" .. password .. " "
local account = verify_password(host.ip, port.number, username, password, ntdomain)
if account.valid then
table.insert(valid_accounts, account)
if account.valid then
stdnse.print_debug(1, "Trying %s/%s => Login Correct, Info: %s", username, password, account.message)
else
stdnse.print_debug(1, "Trying %s/%s => Login Correct", username, password)
end
else
stdnse.print_debug(1, "Trying %s/%s => Login Failed, Reason: %s", username, password, account.message)
end
password = nextPass()
end
nextPass("reset")
username = nextUser()
end
return create_result_from_table(valid_accounts)
end

View File

@@ -0,0 +1,150 @@
description = [[
Extracts a list of applications, acls and settings from Citrix XML service
The script returns the shorter, comma separated output per default.
Running nmap with the verbose flag (-v) triggers the detailed output.
]]
---
-- @usage
-- nmap --script=citrix-enum-apps-xml -p 80,443,8080 <host>
--
-- @output
-- PORT STATE SERVICE
-- 8080/tcp open http-proxy
-- | citrix-enum-apps-xml:
-- | Application: Notepad
-- | Disabled: false
-- | Desktop: false
-- | On Desktop: false
-- | Encryption: basic
-- | In start menu: false
-- | Publisher: labb1farm
-- | SSL: false
-- | Remote Access: false
-- | Users: Anonymous
-- | Application: iexplorer
-- | Disabled: false
-- | Desktop: false
-- | On Desktop: false
-- | Encryption: basic
-- | In start menu: false
-- | Publisher: labb1farm
-- | SSL: false
-- | Remote Access: false
-- | Users: Anonymous
-- | Application: registry editor
-- | Disabled: false
-- | Desktop: false
-- | On Desktop: false
-- | Encryption: basic
-- | In start menu: false
-- | Publisher: labb1farm
-- | SSL: false
-- | Remote Access: false
-- | Users: WIN-B4RL0SUCJ29\Joe
-- |_ Groups: WIN-B4RL0SUCJ29\HR, *CITRIX_BUILTIN*\*CITRIX_ADMINISTRATORS*
--
--
-- PORT STATE SERVICE
-- 8080/tcp open http-proxy
-- | citrix-enum-apps-xml:
-- | Application: Notepad; Users: Anonymous
-- | Application: iexplorer; Users: Anonymous
-- |_ Application: registry editor; Users: WIN-B4RL0SUCJ29\Joe; Groups: WIN-B4RL0SUCJ29\HR, *CITRIX_BUILTIN*\*CITRIX_ADMINISTRATORS*
--
---
-- Version 0.2
-- Created 11/26/2009 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 12/02/2009 - v0.2 - Use stdnse.format_ouput for output
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
require "comm"
require 'shortport'
require 'citrixxml'
portrule = shortport.portnumber({8080,80,443}, "tcp")
--- Creates a table which is suitable for use with stdnse.format_output
--
-- @param appdata table with results from parse_appdata_response
-- @param mode string short or long, see usage above
-- @return table suitable for stdnse.format_output
function format_output(appdata, mode)
local result = {}
local setting_titles = { {appisdisabled="Disabled"}, {appisdesktop="Desktop"}, {AppOnDesktop="On Desktop"},
{Encryption="Encryption"}, {AppInStartmenu="In start menu"},
{PublisherName="Publisher"}, {SSLEnabled="SSL"}, {RemoteAccessEnabled="Remote Access"} }
if mode == "short" then
for app_name, AppData in ipairs(appdata) do
local line = "Application: " .. AppData.FName
if AppData.AccessList then
if AppData.AccessList.User then
line = line .. "; Users: " .. stdnse.strjoin(", ", AppData.AccessList.User)
end
if AppData.AccessList.Group then
line = line .. "; Groups: " .. stdnse.strjoin(", ", AppData.AccessList.Group)
end
table.insert(result, line)
end
end
else
for app_name, AppData in ipairs(appdata) do
local result_part = {}
result_part.name = "Application: " .. AppData.FName
local settings = AppData.Settings
for _, setting_pairs in ipairs(setting_titles) do
for setting_key, setting_title in pairs(setting_pairs) do
local setting_value = settings[setting_key] and settings[setting_key] or ""
table.insert(result_part, setting_title .. ": " .. setting_value )
end
end
if AppData.AccessList then
if AppData.AccessList.User then
table.insert(result_part, "Users: " .. stdnse.strjoin(", ", AppData.AccessList.User) )
end
if AppData.AccessList.Group then
table.insert(result_part, "Groups: " .. stdnse.strjoin(", ", AppData.AccessList.Group) )
end
table.insert(result, result_part)
end
end
end
return result
end
action = function(host,port)
local response = citrixxml.request_appdata(host.ip, port.number, {ServerAddress="",attr={addresstype="dot"},DesiredDetails={"all","access-list"} })
local appdata = citrixxml.parse_appdata_response(response)
local response = format_output(appdata, (nmap.verbosity() > 1 and "long" or "short"))
return stdnse.format_output(true, response)
end

View File

@@ -0,0 +1,157 @@
description = [[
Extract published applications from the ICA Browser service
]]
---
-- @usage sudo ./nmap -sU --script=citrix-enum-apps -p 1604 <host>
--
-- @output
-- PORT STATE SERVICE
-- 1604/udp open unknown
-- 1604/udp open unknown
-- | citrix-enum-apps:
-- | Notepad
-- | iexplorer
-- |_ registry editor
--
-- Version 0.2
-- Created 11/24/2009 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 11/25/2009 - v0.2 - fixed multiple packet response bug
author = "Patrik Karlsson <patrik@cqure.net>"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery","intrusive"}
require "comm"
require "shortport"
require "stdnse"
require "bin"
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_pa_response(response)
local pos, packet_len = bin.unpack("SS", response)
local app_name
local pa_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, app_name = bin.unpack("z", response:sub(offset))
offset = offset + pos - 1
table.insert(pa_list, app_name)
end
return pa_list
end
action = function(host, port)
local packet, counter
local query = {}
local pa_list = {}
--
-- Packets were intercepted from the Citrix Program Neighborhood client
-- They are used to query a server for it's list of servers
--
-- 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(
0x20, 0x00, -- Length: 32
0x01, 0x36, 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, 0x00, 0x00
)
query[2] = 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, 0x21, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
)
counter = 0
local socket = nmap.new_socket()
socket:set_timeout(5000)
try = nmap.new_try(function() socket:close() end)
try( socket:connect(host.ip, port.number, port.protocol) )
-- 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
pa_list = process_pa_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_pa_response( packet )
for _,v in pairs(tmp_table) do
table.insert(pa_list, v)
end
end
-- set port to open
if #pa_list>0 then
nmap.set_port_state(host, port, "open")
end
socket:close()
return stdnse.format_output(true, pa_list)
end

View File

@@ -0,0 +1,45 @@
description = [[ Extracts the name of the server farm and member severs from Citrix XML service
]]
---
-- @usage
-- nmap --script=citrix-enum-servers-xml -p 80,443,8080 <host>
--
-- @output
-- PORT STATE SERVICE REASON
-- 8080/tcp open http-proxy syn-ack
-- | citrix-enum-servers-xml:
-- | CITRIX-SRV01
-- |_ CITRIX-SRV01
--
---
-- Version 0.2
-- Created 11/26/2009 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 12/02/2009 - v0.2 - Use stdnse.format_ouput for output
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
require "comm"
require 'shortport'
require 'citrixxml'
portrule = shortport.portnumber({8080,80,443}, "tcp")
action = function(host, port)
local xmldata = citrixxml.request_server_data(host.ip, port.number)
local servers = citrixxml.parse_server_data_response(xmldata)
local response = {}
for _, srv in ipairs(servers) do
table.insert(response, srv)
end
return stdnse.format_output(true, response)
end

View File

@@ -0,0 +1,141 @@
description = [[
Extract 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 <patrik@cqure.net>
-- Revised 11/26/2009 - v0.2 - minor packet documentation
author = "Patrik Karlsson <patrik@cqure.net>"
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.ip, port.number, port.protocol))
-- 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

View File

@@ -2,6 +2,11 @@ Entry { filename = "asn-query.nse", categories = { "discovery", "external", "saf
Entry { filename = "auth-owners.nse", categories = { "default", "safe", } }
Entry { filename = "auth-spoof.nse", categories = { "malware", "safe", } }
Entry { filename = "banner.nse", categories = { "discovery", "safe", } }
Entry { filename = "citrix-brute-xml.nse", categories = { "auth", "intrusive", } }
Entry { filename = "citrix-enum-apps-xml.nse", categories = { "discovery", "safe", } }
Entry { filename = "citrix-enum-apps.nse", categories = { "discovery", "intrusive", } }
Entry { filename = "citrix-enum-servers-xml.nse", categories = { "discovery", "safe", } }
Entry { filename = "citrix-enum-servers.nse", categories = { "discovery", "safe", } }
Entry { filename = "daytime.nse", categories = { "discovery", "safe", } }
Entry { filename = "db2-info.nse", categories = { "discovery", "safe", "version", } }
Entry { filename = "dhcp-discover.nse", categories = { "default", "discovery", "intrusive", } }