From bca60ba8de3d03847f6f0daad47ff763c752d4f6 Mon Sep 17 00:00:00 2001 From: tomsellers Date: Sat, 29 Oct 2011 10:18:52 +0000 Subject: [PATCH] Added support for LDAP substring searches to ldap.lua. These can now be performed alone or in conjunction with other LDAP query types. Added a new quick filter (qfilter) to ldap-search.nse that allows the user to specify, on the command line, an attribute and corresponding value to search the LDAP directory for. The use of the asterisk '*' as a wildcard is permitted in the value parameter. Updated asn1.lua with some minor notes on a hex value that was used. --- nselib/asn1.lua | 4 +++- nselib/ldap.lua | 42 +++++++++++++++++++++++++++++++++++++-- scripts/ldap-search.nse | 44 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/nselib/asn1.lua b/nselib/asn1.lua index c7563de2a..e96956b79 100644 --- a/nselib/asn1.lua +++ b/nselib/asn1.lua @@ -281,7 +281,9 @@ ASN1Encoder = { end, --- - -- Encodes an ASN1 sequence + -- Encodes an ASN1 sequence, the value of 30 below breaks down as + -- 0x30 = 00110000 = 00 1 10000 + -- hex binary Universal Constructed value Data Type = SEQUENCE (16) encodeSeq = function(self, seqData) return bin.pack('HAA' , '30', self.encodeLength(#seqData), seqData) end, diff --git a/nselib/ldap.lua b/nselib/ldap.lua index f5c30195f..442813074 100644 --- a/nselib/ldap.lua +++ b/nselib/ldap.lua @@ -6,12 +6,14 @@ -- -- Credit goes out to Martin Swende who provided me with the initial code that got me started writing this. -- --- Version 0.4 +-- Version 0.5 -- Created 01/12/2010 - v0.1 - Created by Patrik Karlsson -- Revised 01/28/2010 - v0.2 - Revised to fit better fit ASN.1 library -- Revised 02/02/2010 - v0.3 - Revised to fit OO ASN.1 Library -- Revised 09/05/2011 - v0.4 - Revised to include support for writing output to file, added decoding certain time -- formats +-- Revised 10/29/2011 - v0.5 - Added support for performing wildcard searches via the substring filter. +-- module("ldap", package.seeall) @@ -404,6 +406,14 @@ end -- @param filter table containing the filter to be created -- @return string containing the ASN1 byte sequence function createFilter( filter ) + + -- In the following code the values 0x80, 0x81, 0x82 when used in the context of an + -- LDAP substring FILTER mean: + -- 0x80 = 10000000 = 10 0 00000 = 0 - match beginning of target string + -- 0x81 = 10000001 = 10 0 00001 = 1 - match any location in target string + -- 0x82 = 10000010 = 10 0 00010 = 2 - match end of target string + -- hex binary Context Specific Primitive Sequence meaning + local asn1_type = asn1.BERtoInt( asn1.BERCLASS.ContextSpecific, true, filter.op ) local filter_str = "" @@ -413,7 +423,35 @@ function createFilter( filter ) end else local obj = encode( filter.obj ) - local val = encode( filter.val ) + local val = '' + if ( filter.op == FILTER['substrings'] ) then + + local tmptable = stdnse.strsplit('*', filter.val) + local tmp_result = '' + + if (#tmptable <= 1 ) then + tmp_result = bin.pack('HAA' , '81', string.char(#filter.val), filter.val) + else + for indexval, substr in ipairs(tmptable) do + if (indexval == 1) and (substr ~= '') then + tmp_result = bin.pack('HAA' , '80', string.char(#substr), substr) + end + + if (indexval ~= #tmptable) and (indexval ~= 1) and (substr ~= '') then + tmp_result = tmp_result .. bin.pack('HAA' , '81', string.char(#substr), substr) + end + + if (indexval == #tmptable) and (substr ~= '') then + tmp_result = tmp_result .. bin.pack('HAA' , '82', string.char(#substr), substr) + end + end + end + + + val = asn1.ASN1Encoder:encodeSeq( tmp_result ) + else + val = encode( filter.val ) + end filter_str = filter_str .. obj .. val end diff --git a/scripts/ldap-search.nse b/scripts/ldap-search.nse index 947130d03..44753caf8 100644 --- a/scripts/ldap-search.nse +++ b/scripts/ldap-search.nse @@ -11,7 +11,10 @@ anonymous bind will be used as a last attempt. -- @args ldap.username If set, the script will attempt to perform an LDAP bind using the username and password -- @args ldap.password If set, used together with the username to authenticate to the LDAP server -- @args ldap.qfilter If set, specifies a quick filter. The library does not support parsing real LDAP filters. --- The following values are valid for the filter parameter: computer, users or all. If no value is specified it defaults to all. +-- The following values are valid for the filter parameter: computer, users,custom or all. If no value is specified it defaults to all. +-- @args ldap.searchattrib When used with the 'custom' qfilter, this parameter works in conjunction with ldap.searchvalue to allow the user to specify a custom attribute and value as search criteria. +-- @args ldap.searchvalue When used with the 'custom' qfilter, this parameter works in conjunction with ldap.searchattrib to allow the user to specify a custom attribute and value as search criteria. +-- This parameter DOES PERMIT the use of the asterisk '*' as a wildcard. -- @args ldap.base If set, the script will use it as a base for the search. By default the defaultNamingContext is retrieved and used. -- If no defaultNamingContext is available the script iterates over the available namingContexts -- @args ldap.attrib If set, the search will include only the attributes specified. For a single attribute a string value can be used, if @@ -22,8 +25,11 @@ anonymous bind will be used as a last attempt. -- of .CSV as well as the hostname and port will automatically be added based on the output type selected. -- -- @usage --- nmap -p 389 --script ldap-search --script-args ldap.username="'cn=ldaptest,cn=users,dc=cqure,dc=net'",ldap.password=ldaptest, --- ldap.qfilter=users,ldap.attrib=sAMAccountName +-- nmap -p 389 --script ldap-search --script-args 'ldap.username="cn=ldaptest,cn=users,dc=cqure,dc=net",ldap.password=ldaptest, +-- ldap.qfilter=users,ldap.attrib=sAMAccountName' +-- +-- nmap -p 389 --script ldap-search --script-args 'ldap.username="cn=ldaptest,cn=users,dc=cqure,dc=net",ldap.password=ldaptest, +-- ldap.qfilter=custom,ldap.searchattrib="operatingSystem",ldap.searchvalue="Windows *Server*",ldap.attrib={operatingSystem,whencreated,OperatingSystemServicePack}' -- -- @output -- PORT STATE SERVICE REASON @@ -46,12 +52,27 @@ anonymous bind will be used as a last attempt. -- | sAMAccountName: VMABUSEXP008$ -- | dn: CN=ldaptest,CN=Users,DC=cqure,DC=net -- |_ sAMAccountName: ldaptest +-- +-- +-- PORT STATE SERVICE REASON +-- 389/tcp open ldap syn-ack +-- | ldap-search: +-- | Context: DC=cqure,DC=net; QFilter: custom; Attributes: operatingSystem,whencreated,OperatingSystemServicePack +-- | dn: CN=USDC01,OU=Domain Controllers,DC=cqure,DC=net +-- | whenCreated: 2010/08/27 17:30:16 UTC +-- | operatingSystem: Windows Server 2008 R2 Datacenter +-- | operatingSystemServicePack: Service Pack 1 +-- | dn: CN=TESTBOX,OU=Test Servers,DC=cqure,DC=net +-- | whenCreated: 2010/09/04 00:33:02 UTC +-- | operatingSystem: Windows Server 2008 R2 Standard +-- |_ operatingSystemServicePack: Service Pack 1 + -- Credit -- ------ -- o Martin Swende who provided me with the initial code that got me started writing this. --- Version 0.6 +-- Version 0.7 -- Created 01/12/2010 - v0.1 - created by Patrik Karlsson -- Revised 01/20/2010 - v0.2 - added SSL support -- Revised 01/26/2010 - v0.3 - Changed SSL support to comm.tryssl, prefixed arguments with ldap, changes in determination of namingContexts @@ -59,6 +80,8 @@ anonymous bind will be used as a last attempt. -- Capped output to 20 entries, use ldap.maxObjects to override -- Revised 07/16/2010 - v0.5 - Fixed bug with empty contexts, added objectClass person to qfilter users, add error msg for invalid credentials -- Revised 09/05/2011 - v0.6 - Added support for saving searches to a file via argument ldap.savesearch +-- Revised 10/29/2011 - v0.7 - Added support for custom searches and the ability to leverage LDAP substring search functionality added to LDAP.lua + author = "Patrik Karlsson" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" @@ -80,6 +103,8 @@ function action(host,port) local username = stdnse.get_script_args('ldap.username') local password = stdnse.get_script_args('ldap.password') local qfilter = stdnse.get_script_args('ldap.qfilter') + local searchAttrib = stdnse.get_script_args('ldap.searchattrib') + local searchValue = stdnse.get_script_args('ldap.searchvalue') local base = stdnse.get_script_args('ldap.base') local attribs = stdnse.get_script_args('ldap.attrib') local saveFile = stdnse.get_script_args('ldap.savesearch') @@ -167,6 +192,17 @@ function action(host,port) } elseif qfilter == "computers" or qfilter == "computer" then filter = { op=ldap.FILTER.equalityMatch, obj='objectClass', val='computer' } + + elseif qfilter == "custom" then + if searchAttrib == nil or searchValue == nil then + return "\n\nERROR: Please specify both ldap.searchAttrib and ldap.searchValue using using the custom qfilter." + end + if string.find(searchValue, '*') == nil then + filter = { op=ldap.FILTER.equalityMatch, obj=searchAttrib, val=searchValue } + else + filter = { op=ldap.FILTER.substrings, obj=searchAttrib, val=searchValue } + end + elseif qfilter == "all" or qfilter == nil then filter = nil -- { op=ldap.FILTER} else