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. 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=,passdb=,ntdomain= -p 80,443,8080 -- -- @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 -- 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