diff --git a/CHANGELOG b/CHANGELOG index 9cb0d34ef..9110efd2a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- + +o [NSE] Added ldap-novell-getpass, a script that provides support for + retrieving Universal Passwords in plain-text from Novell eDirectory. + o [ZenMmap] Fixed issue with ports closed in newer scan not being removed from the ports list [Colin Rice] diff --git a/scripts/ldap-novell-getpass.nse b/scripts/ldap-novell-getpass.nse new file mode 100644 index 000000000..760804e06 --- /dev/null +++ b/scripts/ldap-novell-getpass.nse @@ -0,0 +1,133 @@ +description = [[ +Attempts to retrieve the Novell Universal Password for a user. +]] + +--- +-- Universal Password enables advanced password policies, including extended +-- characters in passwords, synchronization of passwords from eDirectory to +-- other systems, and a single password for all access to eDirectory. +-- +-- In case the password policy permits administrators to retrieve user +-- passwords ("Allow admin to retrieve passwords" is set in the password +-- policy) this script can retrieve the password. +-- +-- @args ldap-novell-getpass.account The name of the account to retrieve the +-- password for +-- @args ldap-novell-getpass.username The LDAP username to use when connecting +-- to the server +-- @args ldap-novell-getpass.password The LDAP password to use when connecting +-- to the server +-- +-- @usage +-- nmap -p 636 --script ldap-novell-getpass --script-args \ +-- 'ldap-novell-getpass.username="CN=admin,O=cqure", \ +-- ldap-novell-getpass.password=pass1234, \ +-- ldap-novell-getpass.account="CN=paka,OU=hr,O=cqure"' +-- +-- @output +-- PORT STATE SERVICE REASON +-- 636/tcp open ldapssl syn-ack +-- | ldap-novell-getpass: +-- | Account: CN=patrik,OU=security,O=cqure +-- |_ Password: foobar +-- + +-- Version 0.1 +-- Created 05/11/2010 - v0.1 - created by Patrik Karlsson + + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} + +require "ldap" +require 'shortport' +require 'comm' + +portrule = shortport.port_or_service({389,636}, {"ldap","ldapssl"}) + +function action(host,port) + + local username = stdnse.get_script_args("ldap-novell-getpass.username") + local password = stdnse.get_script_args("ldap-novell-getpass.password") or "" + local account = stdnse.get_script_args("ldap-novell-getpass.account") + + if ( not(username) ) then + return "\n ERROR: No username was supplied (ldap-novell-getpass.username)" + end + if ( not(account) ) then + return "\n ERROR: No account was supplied (ldap-novell-getpass.account)" + else + -- do some basic account validation + if ( not(account:match("^[Cc][Nn]=.*,") ) ) then + return "\n ERROR: The account argument should be specified as:\n" .. + " \"CN=name,OU=orgunit,O=org\"" + end + end + + -- In order to discover what protocol to use (SSL/TCP) we need to send a + -- few bytes to the server. An anonymous bind should do it + local anon_bind = bin.pack("H", "300c020101600702010304008000" ) + local socket, _, opt = comm.tryssl( host, port, anon_bind, nil ) + if ( not(socket) ) then + return "\n ERROR: Failed to connect to LDAP server" + end + + local status, errmsg = ldap.bindRequest( socket, { + version = 3, + username = username, + password = password + } + ) + + if ( not(status) ) then return errmsg end + + -- Start encoding the NMAS Get Password Request + local NMASLDAP_GET_PASSWORD_REQUEST = "2.16.840.1.113719.1.39.42.100.13" + local NMASLDAP_GET_PASSWORD_RESPONSE = "2.16.840.1.113719.1.39.42.100.14" + -- Add a trailing zero to the account name + local data = ldap.encode( account .. '\0' ) + + -- The following section could do with more documentation + -- It's based on packet dumps from the getpass utility available from Novell Cool Solutions + -- encode the account name as a sequence + data = ldap.encode( { _ldaptype = '30', bin.pack("H", "020101") .. data } ) + data = ldap.encode( { _ldaptype = '81', data } ) + data = ldap.encode( { _ldaptype = '80', NMASLDAP_GET_PASSWORD_REQUEST } ) .. data + data = ldap.encode( { _ldaptype = '77', data } ) + + -- encode the whole extended request as a sequence + data = ldap.encode( { _ldaptype = '30', bin.pack("H", "020102") .. data } ) + + status = socket:send(data) + if ( not(status) ) then return "ERROR: Failed to send request" end + + status, data = socket:receive() + if ( not(status) ) then return data end + socket:close() + + local _, response = ldap.decode(data) + + -- make sure the result code was a success + local rescode = ( #response >= 2 ) and response[2] + local respname = ( #response >= 5 ) and response[5] + + if ( rescode ~= 0 ) then + local errmsg = ( #response >= 4 ) and response[4] or "An unknown error occured" + return "\n ERROR: " .. errmsg + end + + -- make sure we get a NMAS Get Password Response back from the server + if ( respname ~= NMASLDAP_GET_PASSWORD_RESPONSE ) then return end + + local universal_pw = ( #response >= 6 and #response[6] >= 3 ) and response[6][3] + + if ( universal_pw ) then + local output = {} + table.insert(output, ("Account: %s"):format(account)) + table.insert(output, ("Password: %s"):format(universal_pw)) + return stdnse.format_output(true, output) + else + return "\n ERROR: No password was found" + end +end \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index 8ec3124ff..a5ed856db 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -90,6 +90,7 @@ Entry { filename = "iscsi-brute.nse", categories = { "auth", "intrusive", } } Entry { filename = "iscsi-info.nse", categories = { "discovery", } } Entry { filename = "jdwp-version.nse", categories = { "version", } } Entry { filename = "ldap-brute.nse", categories = { "auth", "intrusive", } } +Entry { filename = "ldap-novell-getpass.nse", categories = { "discovery", "safe", } } Entry { filename = "ldap-rootdse.nse", categories = { "discovery", "safe", } } Entry { filename = "ldap-search.nse", categories = { "discovery", "safe", } } Entry { filename = "lexmark-config.nse", categories = { "discovery", "safe", } }