local comm = require "comm" local ldap = require "ldap" local shortport = require "shortport" local stdnse = require "stdnse" local table = require "table" description = [[ Attempts to retrieve the Novell Universal Password for a user. You must already have (and include in script arguments) the username and password for an eDirectory server administrative account. ]] --- -- 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 https://nmap.org/book/man-legal.html" categories = {"discovery", "safe"} portrule = shortport.port_or_service({389,636}, {"ldap","ldapssl"}) local function fail (err) return stdnse.format_output(false, err) end 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 fail("No username was supplied (ldap-novell-getpass.username)") end if ( not(account) ) then return fail("No account was supplied (ldap-novell-getpass.account)") else -- do some basic account validation if ( not(account:match("^[Cc][Nn]=.*,") ) ) then return fail("The account argument should be specified as: \"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 = stdnse.fromhex( "300c020101600702010304008000" ) local socket, _, opt = comm.tryssl( host, port, anon_bind, nil ) if ( not(socket) ) then return fail("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', stdnse.fromhex( "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', stdnse.fromhex( "020102") .. data } ) status = socket:send(data) if ( not(status) ) then return fail("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 occurred" return fail(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 fail("No password was found") end end