Lucene search

K
nmapPatrik KarlssonNMAP:LDAP-ROOTDSE.NSE
HistoryFeb 21, 2010 - 8:52 a.m.

ldap-rootdse NSE Script

2010-02-2108:52:44
Patrik Karlsson
nmap.org
332

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

0.973 High

EPSS

Percentile

99.8%

Retrieves the LDAP root DSA-specific Entry (DSE)

Example Usage

nmap -p 389 --script ldap-rootdse <host>

Script Output

PORT    STATE SERVICE
389/tcp open  ldap
| ldap-rootdse:
|     currentTime: 20100112092616.0Z
|     subschemaSubentry: CN=Aggregate,CN=Schema,CN=Configuration,DC=cqure,DC=net
|     dsServiceName: CN=NTDS Settings,CN=LDAPTEST001,CN=Servers,CN=Default-First-Site,CN=Sites,CN=Configuration,DC=cqure,DC=net
|     namingContexts: DC=cqure,DC=net
|     namingContexts: CN=Configuration,DC=cqure,DC=net
|     namingContexts: CN=Schema,CN=Configuration,DC=cqure,DC=net
|     namingContexts: DC=DomainDnsZones,DC=cqure,DC=net
|     namingContexts: DC=ForestDnsZones,DC=cqure,DC=net
|     namingContexts: DC=TAPI3Directory,DC=cqure,DC=net
|     defaultNamingContext: DC=cqure,DC=net
|     schemaNamingContext: CN=Schema,CN=Configuration,DC=cqure,DC=net
|     configurationNamingContext: CN=Configuration,DC=cqure,DC=net
|     rootDomainNamingContext: DC=cqure,DC=net
|     supportedControl: 1.2.840.113556.1.4.319
|     .
|     .
|     supportedControl: 1.2.840.113556.1.4.1948
|     supportedLDAPVersion: 3
|     supportedLDAPVersion: 2
|     supportedLDAPPolicies: MaxPoolThreads
|     supportedLDAPPolicies: MaxDatagramRecv
|     supportedLDAPPolicies: MaxReceiveBuffer
|     supportedLDAPPolicies: InitRecvTimeout
|     supportedLDAPPolicies: MaxConnections
|     supportedLDAPPolicies: MaxConnIdleTime
|     supportedLDAPPolicies: MaxPageSize
|     supportedLDAPPolicies: MaxQueryDuration
|     supportedLDAPPolicies: MaxTempTableSize
|     supportedLDAPPolicies: MaxResultSetSize
|     supportedLDAPPolicies: MaxNotificationPerConn
|     supportedLDAPPolicies: MaxValRange
|     highestCommittedUSN: 126991
|     supportedSASLMechanisms: GSSAPI
|     supportedSASLMechanisms: GSS-SPNEGO
|     supportedSASLMechanisms: EXTERNAL
|     supportedSASLMechanisms: DIGEST-MD5
|     dnsHostName: EDUSRV011.cqure.local
|     ldapServiceName: cqure.net:[email protected]
|     serverName: CN=EDUSRV011,CN=Servers,CN=Default-First-Site,CN=Sites,CN=Configuration,DC=cqure,DC=net
|     supportedCapabilities: 1.2.840.113556.1.4.800
|     supportedCapabilities: 1.2.840.113556.1.4.1670
|     supportedCapabilities: 1.2.840.113556.1.4.1791
|     isSynchronized: TRUE
|     isGlobalCatalogReady: TRUE
|     domainFunctionality: 0
|     forestFunctionality: 0
|_    domainControllerFunctionality: 2


The root DSE object may contain a number of different attributes as described in RFC 2251 section 3.4:
* namingContexts: naming contexts held in the server
* subschemaSubentry: subschema entries (or subentries) known by this server
* altServer: alternative servers in case this one is later unavailable.
* supportedExtension: list of supported extended operations.
* supportedControl: list of supported controls.
* supportedSASLMechanisms: list of supported SASL security features.
* supportedLDAPVersion: LDAP versions implemented by the server.

The above example, which contains a lot more information is from Windows 2003 accessible without authentication.
The same request against OpenLDAP will result in significantly less information.

The ldap-search script queries the root DSE for the namingContexts and/or defaultNamingContexts, which it sets as base
if no base object was specified

Credit goes out to Martin Swende who provided me with the initial code that got me started writing this.

Requires


local comm = require "comm"
local ldap = require "ldap"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"

description = [[
Retrieves the LDAP root DSA-specific Entry (DSE)
]]

---
--
-- @usage
-- nmap -p 389 --script ldap-rootdse <host>
--
-- @output
-- PORT    STATE SERVICE
-- 389/tcp open  ldap
-- | ldap-rootdse:
-- |     currentTime: 20100112092616.0Z
-- |     subschemaSubentry: CN=Aggregate,CN=Schema,CN=Configuration,DC=cqure,DC=net
-- |     dsServiceName: CN=NTDS Settings,CN=LDAPTEST001,CN=Servers,CN=Default-First-Site,CN=Sites,CN=Configuration,DC=cqure,DC=net
-- |     namingContexts: DC=cqure,DC=net
-- |     namingContexts: CN=Configuration,DC=cqure,DC=net
-- |     namingContexts: CN=Schema,CN=Configuration,DC=cqure,DC=net
-- |     namingContexts: DC=DomainDnsZones,DC=cqure,DC=net
-- |     namingContexts: DC=ForestDnsZones,DC=cqure,DC=net
-- |     namingContexts: DC=TAPI3Directory,DC=cqure,DC=net
-- |     defaultNamingContext: DC=cqure,DC=net
-- |     schemaNamingContext: CN=Schema,CN=Configuration,DC=cqure,DC=net
-- |     configurationNamingContext: CN=Configuration,DC=cqure,DC=net
-- |     rootDomainNamingContext: DC=cqure,DC=net
-- |     supportedControl: 1.2.840.113556.1.4.319
-- |     .
-- |     .
-- |     supportedControl: 1.2.840.113556.1.4.1948
-- |     supportedLDAPVersion: 3
-- |     supportedLDAPVersion: 2
-- |     supportedLDAPPolicies: MaxPoolThreads
-- |     supportedLDAPPolicies: MaxDatagramRecv
-- |     supportedLDAPPolicies: MaxReceiveBuffer
-- |     supportedLDAPPolicies: InitRecvTimeout
-- |     supportedLDAPPolicies: MaxConnections
-- |     supportedLDAPPolicies: MaxConnIdleTime
-- |     supportedLDAPPolicies: MaxPageSize
-- |     supportedLDAPPolicies: MaxQueryDuration
-- |     supportedLDAPPolicies: MaxTempTableSize
-- |     supportedLDAPPolicies: MaxResultSetSize
-- |     supportedLDAPPolicies: MaxNotificationPerConn
-- |     supportedLDAPPolicies: MaxValRange
-- |     highestCommittedUSN: 126991
-- |     supportedSASLMechanisms: GSSAPI
-- |     supportedSASLMechanisms: GSS-SPNEGO
-- |     supportedSASLMechanisms: EXTERNAL
-- |     supportedSASLMechanisms: DIGEST-MD5
-- |     dnsHostName: EDUSRV011.cqure.local
-- |     ldapServiceName: cqure.net:[email protected]
-- |     serverName: CN=EDUSRV011,CN=Servers,CN=Default-First-Site,CN=Sites,CN=Configuration,DC=cqure,DC=net
-- |     supportedCapabilities: 1.2.840.113556.1.4.800
-- |     supportedCapabilities: 1.2.840.113556.1.4.1670
-- |     supportedCapabilities: 1.2.840.113556.1.4.1791
-- |     isSynchronized: TRUE
-- |     isGlobalCatalogReady: TRUE
-- |     domainFunctionality: 0
-- |     forestFunctionality: 0
-- |_    domainControllerFunctionality: 2
--
--
-- The root DSE object may contain a number of different attributes as described in RFC 2251 section 3.4:
-- * namingContexts: naming contexts held in the server
-- * subschemaSubentry: subschema entries (or subentries) known by this server
-- * altServer: alternative servers in case this one is later unavailable.
-- * supportedExtension: list of supported extended operations.
-- * supportedControl: list of supported controls.
-- * supportedSASLMechanisms: list of supported SASL security features.
-- * supportedLDAPVersion: LDAP versions implemented by the server.
--
-- The above example, which contains a lot more information is from Windows 2003 accessible without authentication.
-- The same request against OpenLDAP will result in significantly less information.
--
-- The ldap-search script queries the root DSE for the namingContexts and/or defaultNamingContexts, which it sets as base
-- if no base object was specified
--
-- Credit goes out to Martin Swende who provided me with the initial code that got me started writing this.
--

-- Version 0.3
-- Created 01/12/2010 - v0.1 - created by Patrik Karlsson <[email protected]>
-- Revised 01/20/2010 - v0.2 - added SSL support
-- Revised 04/09/2016 - v0.3 - added support for LDAP over UDP - Tom Sellers

author = "Patrik Karlsson"
copyright = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
dependencies = {"ldap-brute"}

-- Map domainControllerFunctionality to OS - https://msdn.microsoft.com/en-us/library/cc223272.aspx
-- Tested to be valid even when Active Directory functional level is lower than target ADC's OS version
DC_FUNCT_ID = {}
DC_FUNCT_ID["0"] = "Windows 2000"
DC_FUNCT_ID["2"] = "Windows 2003"
DC_FUNCT_ID["3"] = "Windows 2008"
DC_FUNCT_ID["4"] = "Windows 2008 R2"
DC_FUNCT_ID["5"] = "Windows 2012"
DC_FUNCT_ID["6"] = "Windows 2012 R2"

portrule = shortport.port_or_service({389,636}, {"ldap","ldapssl"},{'tcp','udp'})

function action(host,port)
  local status, searchResEntries, req, result, opt

  if port.protocol == 'tcp' then

    local socket = nmap.new_socket()

    -- 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 ldap_anonymous_bind = "\x30\x0c\x02\x01\x01\x60\x07\x02\x01\x03\x04\x00\x80\x00"
    local _
    socket, _, opt = comm.tryssl( host, port, ldap_anonymous_bind, nil )

    if not socket then
      return
    end

    -- We close and re-open the socket so that the anonymous bind does not distract us
    socket:close()
    status = socket:connect(host, port, opt)
    socket:set_timeout(10000)

    -- Searching for an empty argument list against LDAP on W2K3 returns all attributes
    -- This is not the case for OpenLDAP, so we do a search for an empty attribute list
    -- Then we compare the results against some known and expected returned attributes
    req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default }
    status, searchResEntries = ldap.searchRequest( socket, req )

    if not status then
      socket:close()
      return stdnse.format_output(false, searchResEntries)
    end

    -- Check if we were served all the results or not?
    if not ldap.extractAttribute( searchResEntries, "namingContexts" ) and
        not ldap.extractAttribute( searchResEntries, "supportedLDAPVersion" ) then

      -- The namingContexts was not there, try to query all attributes instead
      -- Attributes extracted from Windows 2003 and complemented from RFC
      local attribs = {"_domainControllerFunctionality","configurationNamingContext","currentTime","defaultNamingContext",
        "dnsHostName","domainFunctionality","dsServiceName","forestFunctionality","highestCommittedUSN",
        "isGlobalCatalogReady","isSynchronized","ldap-get-baseobject","ldapServiceName","namingContexts",
        "rootDomainNamingContext","schemaNamingContext","serverName","subschemaSubentry",
        "supportedCapabilities","supportedControl","supportedLDAPPolicies","supportedLDAPVersion",
        "supportedSASLMechanisms", "altServer", "supportedExtension"}

      req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = attribs }
      status, searchResEntries = ldap.searchRequest( socket, req )
    end

    socket:close()
  else
    -- Port protocol is UDP, indicating that this is an Active Directory Controller LDAP service
    req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default}
    status, searchResEntries = ldap.udpSearchRequest( host, port, req )
  end

  if not status or not searchResEntries then return stdnse.format_output(false, searchResEntries) end
  result = ldap.searchResultToTable( searchResEntries )

  -- if taken a way and ldap returns a single result, it ain't shown....
  result.name = "LDAP Results"
  local scriptResult = stdnse.format_output(true, result )

  -- Start extracting target information
  -- The following works on Windows AD LDAP as well as VMware's LDAP, VMware uses lower case cn vs AD ucase CN
  local serverName = scriptResult:match("serverName: [cC][nN]=([^,]+),[cC][nN]=Servers,[cC][nN]=")
  if serverName then port.version.hostname = serverName end

  -- Check to see if this is Active Directory vs some other product or ADAM
  -- https://msdn.microsoft.com/en-us/library/cc223359.aspx
  if string.match(scriptResult,"1.2.840.113556.1.4.800") then
    port.version.product = 'Microsoft Windows Active Directory LDAP'
    port.version.name_confidence = 10

    -- Determine Windows version
    if not port.version.ostype or port.version.ostype == 'Windows' then
      local DC_Func = string.match(scriptResult,"domainControllerFunctionality: (%d)")
      if DC_FUNCT_ID[DC_Func] then
        port.version.ostype = DC_FUNCT_ID[DC_Func]
      else
        port.version.ostype = 'Windows'
        stdnse.debug(1,"Unmatched OS lookup for domainControllerFunctionality: %d", DC_Func)
      end
    end

    local siteName = string.match(scriptResult,"serverName: CN=[^,]+,CN=Servers,CN=([^,]+),CN=Sites,")
    local domainName = string.match(scriptResult,"rootDomainNamingContext: ([^\n]*)")
    domainName = string.gsub(domainName,",DC=",".")
    domainName = string.gsub(domainName,"DC=","")
    if domainName and siteName then
      port.version.extrainfo = string.format("Domain: %s, Site: %s", domainName, siteName)
    end
  end

  -- Set port information
  port.version.name = "ldap"
  nmap.set_port_version(host, port, "hardmatched")
  nmap.set_port_state(host, port, "open")

  return scriptResult
end

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

0.973 High

EPSS

Percentile

99.8%

Related for NMAP:LDAP-ROOTDSE.NSE