diff --git a/CHANGELOG b/CHANGELOG index a0b6393e3..60465b434 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,12 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Applied patch from Chris Woodbury that adds the following additional + information to the output of smb-os-discovery: + + Forest name + + FQDN + + NetBIOS computer name + + NetBIOS domain name + o [Ncat] Ncat now supports IPV6 addresses by default without the -6 flag. Additionally ncat listens on both :: and localhost when passed -l, or any other listening mode unless a specific listening address is diff --git a/nselib/smb.lua b/nselib/smb.lua index 38e71e013..b533ada5a 100644 --- a/nselib/smb.lua +++ b/nselib/smb.lua @@ -1,13 +1,16 @@ --- --- Implements functionality related to Server Message Block (SMB, also known --- as CIFS) traffic, which is a Windows protocol. +-- Implements functionality related to Server Message Block (SMB, an extension +-- of CIFS) traffic, which is a Windows protocol. -- -- SMB traffic is normally sent to/from ports 139 or 445 of Windows systems. Other systems -- implement SMB as well, including Samba and a lot of embedded devices. Some of them implement -- it properly and many of them not. Although the protocol has been documented decently -- well by Samba and others, many 3rd party implementations are broken or make assumptions. -- Even Samba's and Windows' implementations aren't completely compatiable. As a result, --- creating an implementation that accepts everything is a bit of a minefield. +-- creating an implementation that accepts everything is a bit of a minefield. Microsoft's +-- extensive documentation is available at the following URLs: +-- * SMB: http://msdn.microsoft.com/en-us/library/cc246231(v=prot.13).aspx +-- * CIFS: http://msdn.microsoft.com/en-us/library/ee442092(v=prot.13).aspx -- -- Where possible, this implementation, since it's intended for scanning, will attempt to -- accept any invalid implementations it can, and fail gracefully if it can't. This has @@ -554,7 +557,7 @@ function start_netbios(host, port, name) return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [1]" end - -- Check for a position session response (0x82) + -- Check for a positive session response (0x82) if result == 0x82 then stdnse.print_debug(3, "SMB: Successfully established NetBIOS session with server name %s", name) return true, socket @@ -932,7 +935,7 @@ end -- * 'timezone' The server's timezone, in hours from UTC -- * 'timezone_str' The server's timezone, as a string -- * 'server_challenge' A random string used for challenge/response --- * 'domain' The server's primary domain +-- * 'domain' The server's primary domain or workgroup -- * 'server' The server's name function negotiate_protocol(smb, overrides) local header, parameters, data @@ -1328,7 +1331,7 @@ local function start_session_extended(smb, log_errors, overrides) status_name = get_status_name(status) -- Only parse the parameters if it's ok or if we're going to keep going - if(status_name == "NT_STATUS_OK" or status_name == "NT_STATUS_MORE_PROCESSING_REQUIRED") then + if(status_name == "NT_STATUS_SUCCESS" or status_name == "NT_STATUS_MORE_PROCESSING_REQUIRED") then -- Parse the parameters pos, andx_command, andx_reserved, andx_offset, action, security_blob_length = bin.unpack(" 0 ) then + local length = domain_length + local pos = domain_offset + 1 -- +1 to convert to Lua's 1-based indexes + local target_realm + pos, target_realm = bin.unpack( string.format( "A%d", length ), security_blob, pos ) + ntlm_challenge[ "target_realm" ] = from_unicode( target_realm ) + end + + -- Parse the TargetInfo data (Wireshark calls this the "Address List") + if ( target_info_length > 0 ) then + + -- Definition of AvId values (IDs for AV_PAIR (attribute-value pair) structures), + -- as definied by the NTLM Authentication Protocol specification [MS-NLMP]. + local NTLM_AV_ID_VALUES = { + MsvAvEOL = 0x0, + MsvAvNbComputerName = 0x1, + MsvAvNbDomainName = 0x2, + MsvAvDnsComputerName = 0x3, + MsvAvDnsDomainName = 0x4, + MsvAvDnsTreeName = 0x5, + MsvAvFlags = 0x6, + MsvAvTimestamp = 0x7, + MsvAvRestrictions = 0x8, + MsvAvTargetName = 0x9, + MsvAvChannelBindings = 0xA, + } + -- Friendlier names for AvId values, to be used as keys in the results table + -- e.g. ntlm_challenge[ "dns_computer_name" ] -> "host.test.local" + local NTLM_AV_ID_NAMES = { + [NTLM_AV_ID_VALUES.MsvAvNbComputerName] = "netbios_computer_name", + [NTLM_AV_ID_VALUES.MsvAvNbDomainName] = "netbios_domain_name", + [NTLM_AV_ID_VALUES.MsvAvDnsComputerName] = "fqdn", + [NTLM_AV_ID_VALUES.MsvAvDnsDomainName] = "dns_domain_name", + [NTLM_AV_ID_VALUES.MsvAvDnsTreeName] = "dns_forest_name", + [NTLM_AV_ID_VALUES.MsvAvTimestamp] = "timestamp", + } + + + local length = target_info_length + local pos = target_info_offset + 1 -- +1 to convert to Lua's 1-based indexes + local target_info + pos, target_info = bin.unpack( string.format( "A%d", length ), security_blob, pos ) + + pos = 1 -- reset pos to 1, since we'll be working out of just the target_info + repeat + local value, av_id, av_len + pos, av_id, av_len = bin.unpack( "= #target_info ) + end + + return ntlm_challenge +end + ---Create an 8-byte message signature that's sent with all SMB packets. -- --@param mac_key The key used for authentication. It's the concatination of the session key and the diff --git a/scripts/smb-brute.nse b/scripts/smb-brute.nse index 761565491..f1a12ba0a 100644 --- a/scripts/smb-brute.nse +++ b/scripts/smb-brute.nse @@ -542,7 +542,7 @@ local function initialize(host) -- Get the OS (identifying windows versions tells us which hash to use) result, os = smb.get_os(host) - if(result == false) then + if(result == false or os['os'] == nil) then hostinfo['os'] = "" else hostinfo['os'] = os['os'] diff --git a/scripts/smb-os-discovery.nse b/scripts/smb-os-discovery.nse index 578f999a0..05320c1d5 100644 --- a/scripts/smb-os-discovery.nse +++ b/scripts/smb-os-discovery.nse @@ -1,10 +1,23 @@ description = [[ -Attempts to determine the operating system, computer name, domain, and current +Attempts to determine the operating system, computer name, domain, workgroup, and current time over the SMB protocol (ports 445 or 139). This is done by starting a session with the anonymous account (or with a proper user account, if one is given; it likely doesn't make a difference); in response to a session starting, the server will send back all this -information. +information. + +The following fields may be included in the output, depending on the +cirumstances (e.g. the workgroup name is mutually exclusive with domain and forest +names) and the information available: +* OS +* Computer name +* Domain name +* Forest name +* FQDN +* NetBIOS computer name +* NetBIOS domain name +* Workgroup +* System time Some systems, like Samba, will blank out their name (and only send their domain). Other systems (like embedded printers) will simply leave out the information. Other @@ -18,7 +31,8 @@ servers that are being poorly maintained (for more information/random thoughts o using the time, see http://www.skullsecurity.org/blog/?p=76. Although the standard smb* script arguments can be used, -they likely won't change the outcome in any meaningful way. +they likely won't change the outcome in any meaningful way. However, smbnoguest +will speed up the script on targets that do not allow guest access. ]] --- @@ -28,10 +42,15 @@ they likely won't change the outcome in any meaningful way. -- --@output -- Host script results: --- | smb-os-discovery: --- | | OS: Windows 2000 (Windows 2000 LAN Manager) --- | | Name: WORKGROUP\RON-WIN2K-TEST --- |_ |_ System time: 2009-11-09 14:33:39 UTC-6 +-- | smb-os-discovery: +-- | OS: Windows Server (R) 2008 Standard 6001 Service Pack 1 (Windows Server (R) 2008 Standard 6.0) +-- | Computer name: Sql2008 +-- | Domain name: lab.test.local +-- | Forest name: test.local +-- | FQDN: Sql2008.lab.test.local +-- | NetBIOS computer name: SQL2008 +-- | NetBIOS domain name: LAB +-- |_ System time: 2011-04-20 13:34:06 UTC-5 ----------------------------------------------------------------------- author = "Ron Bowes" @@ -62,6 +81,16 @@ function get_windows_version(os) end +function add_to_output(output_table, label, value, value_if_nil) + if (value == nil and value_if_nil ~= nil) then + value = value_if_nil + end + + if (value ~= nil) then + table.insert(output_table, string.format("%s: %s", label, value) ) + end +end + action = function(host) local response = {} local status, result = smb.get_os(host) @@ -69,10 +98,44 @@ action = function(host) if(status == false) then return stdnse.format_output(false, result) end - - table.insert(response, string.format("OS: %s (%s)", get_windows_version(result['os']), result['lanmanager'])) - table.insert(response, string.format("Name: %s\\%s", result['domain'], result['server'])) - table.insert(response, string.format("System time: %s %s", result['date'], result['timezone_str'])) + + local hostname_dns, is_domain_member, os_string, time_string + if (result[ "fqdn" ]) then + -- Pull the first part of the FQDN as the computer name + hostname_dns = string.match( result[ "fqdn" ], "^([^.]+)%.?" ) + + if (result[ "domain_dns" ]) then + -- If the computer name doesn't match the domain name, the target is a domain member + is_domain_member = ( result[ "fqdn" ] ~= result[ "domain_dns" ] ) + end + end + + if (result['os'] and result['lanmanager']) then + os_string = string.format( "%s (%s)", get_windows_version( result['os'] ), result['lanmanager'] ) + end + if (result['date'] and result['timezone_str']) then + time_string = string.format("%s %s", result['date'], result['timezone_str']) + end + + + add_to_output( response, "OS", os_string, "Unknown" ) + add_to_output( response, "Computer name", hostname_dns ) + + if ( is_domain_member ) then + add_to_output( response, "Domain name", result[ "domain_dns" ] ) + add_to_output( response, "Forest name", result[ "forest_dns" ] ) + add_to_output( response, "FQDN", result[ "fqdn" ] ) + end + + add_to_output( response, "NetBIOS computer name", result[ "server" ] ) + + if ( is_domain_member ) then + add_to_output( response, "NetBIOS domain name", result[ "domain" ] ) + else + add_to_output( response, "Workgroup", result[ "workgroup" ], result[ "domain" ] ) + end + + add_to_output( response, "System time", time_string, "Unknown" ) return stdnse.format_output(true, response) end