summaryrefslogtreecommitdiffstats
path: root/scripts/citrix-brute-xml.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/citrix-brute-xml.nse')
-rw-r--r--scripts/citrix-brute-xml.nse163
1 files changed, 163 insertions, 0 deletions
diff --git a/scripts/citrix-brute-xml.nse b/scripts/citrix-brute-xml.nse
new file mode 100644
index 0000000..021fe9e
--- /dev/null
+++ b/scripts/citrix-brute-xml.nse
@@ -0,0 +1,163 @@
+local citrixxml = require "citrixxml"
+local nmap = require "nmap"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local table = require "table"
+local unpwdb = require "unpwdb"
+
+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=<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 https://nmap.org/book/man-legal.html"
+categories = {"intrusive", "brute"}
+
+
+portrule = shortport.portnumber({8080,80,443}, "tcp")
+
+--- Verifies if the credentials (username, password and domain) are valid
+--
+-- @param host string or host table against which to perform
+-- @param port number or port table 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
+ stdnse.debug1("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 i, account in ipairs(accounts) do
+ result[i] = ("\n %s:%s => %s"):format(account.username, account.password, account.message)
+ end
+
+ return table.concat(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, port, username, password, ntdomain)
+
+ if account.valid then
+
+ table.insert(valid_accounts, account)
+
+ if account.valid then
+ stdnse.debug1("Trying %s/%s => Login Correct, Info: %s", username, password, account.message)
+ else
+ stdnse.debug1("Trying %s/%s => Login Correct", username, password)
+ end
+ else
+ stdnse.debug1("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