Adding Search Functionality to our Script
So far, our script builds the required LDAP path.
Now we can build in search functionality.
To do this, we will use two .NET classes that are located in the
System.DirectoryServices namespace,
more specifically the DirectoryEntry and DirectorySearcher classes.
Let’s implement these two classes into our script.
We have added the $direntry variable,
which is encapsulating our obtained LDAP path.
The $dirsearcher variable contains the $direntry variable
and uses the information as the SearchRoot, pointing to the top of the
hierarchy where DirectorySearcher will run the FindAll() method.
Since we start the search at the top and aren’t filtering the results,
it will generate a lot of output.
Let’s run the script:
This script does indeed generate a lot of text.
In fact, we are receiving all objects in the entire domain.
This does at least prove that the script is working as expected.
Filtering the output is rather simple, and there are several ways to do so.
One way is to set up a filter that will sift through the samAccountType attribute,
which is an attribute applied to all user, computer, and group objects.
The official documentation reveals different values
of the samAccountType attribute,
but we’ll start with 0x30000000 (decimal 805306368),
which will enumerate all users in the domain
To implement the filter in our script,
we can simply add the filter to the $dirsearcher.filter:
Running our script displays all user objects in the domain:
This is great information to have, but we need to develop it a little further.
When enumerating AD,
we are very interested in the attributes of each object,
which are stored in the Properties field.
Knowing this, we can store the results we receive from our search in a new variable.
We’ll iterate through each object
and print each property on its own line via a nested loop.
This complete script will search through AD and filter the results
based on the samAccountType of our choosing,
then place the results into the new $result variable.
It will then further filter the results based on two foreach loops.
The first loop will extract the objects stored in $result
and place them into the $obj variable.
The second loop will extract all the properties for each object
and store the information in the $prop variable.
The script will then print $prop and present the output in the terminal.
While the Write-Host command is not required for the script to function,
it does print a line between each object.
This helps make the output somewhat easier to read.
The script will output lots of information,
which can be overwhelming depending on the existing number of domain users.
Let’s up scroll and review jeffadmin’s attributes:
We can filter based on any property of any object type.
In this example, we’ll make two changes.
First, we’ll change the filter to use the name property
to only show information for jeffadmin.
Additionally, we’ll add .memberof to the $prop variable to only display the groups
jeffadmin is a member of:
Let’s run the script:
This confirms that jeffadmin is indeed a member of the Domain Admins group.
We can use this script to enumerate any object available to us in AD.
However, in the current state, this would require us to make further edits
to the script itself based on what we wish to enumerate.
Instead, we can make the script more flexible,
allowing us to add the required parameters via the command line.
For example, we could have the script accept the samAccountType
we wish to enumerate as a command line argument.
There are many ways we can accomplish this.
One way is to simply encapsulate the current functionality
of the script into an actual function.
Let’s review an example.
At the very top, we declare the function itself with the name of our choosing,
in this case LDAPSearch.
It then dynamically obtains the required LDAP path connection string
and adds it to the $DirectoryEntry variable.
Afterwards, the DirectoryEntry and our $LDAPQuery parameter
are fed into the DirectorySearcher.
Finally, the search is run and the output is added into an array,
which is displayed in our terminal depending on our needs.
To use the function, let’s import it to memory:
Within PowerShell, we can now use the LDAPSearch command,
our declared function name, to obtain information from AD.
To repeat parts of the user enumeration we did earlier,
we can again filter on the specific samAccountType:
We can also search directly for an Object Class,
which is a component of AD that defines the object type.
Let’s use objectClass=group in this case to list all the groups in the domain:
Our script enumerates more groups than net.exe
including Print Operators, IIS_IUSRS, and others.
This is because it enumerates all AD objects including Domain Local groups,
not just global groups.
In order to print properties and attributes for objects,
we’ll need to implement the loops we discussed earlier.
For now, let’s do this directly from the PowerShell command prompt.
To enumerate every group available in the domain and also display the user members,
we can pipe the output into a new variable
and use a foreach loop that will print each property for a group.
This allows us to select specific attributes we are interested in.
For example, let’s focus on the CN and member attributes:
Even though this environment is somewhat small,
we still received a lot of output.
Let’s focus on the three groups we noticed earlier in our enumeration with net.exe:
According to our search, we have expanded the properties for each object,
in this case the group objects,
and we printed the member attribute for each group.
This output reveals something unexpected.
Earlier when we enumerated the Sales Department group with net.exe,
we only found two users in it: pete and stephanie.
In this case however, it appears that Development Department is also a member.
Since the output can be somewhat difficult to read,
let’s once again search for the groups,
but this time specify the Sales Department in the query
and pipe it into a variable in our PowerShell command line:
Now that we only have one object in our variable,
we can simply print the member attribute directly:
The Development Department is indeed a member of the Sales Department group.
This is something we missed earlier with net.exe.
This is a group within a group, known as a nested group
Nested groups are relatively common in AD and scales well,
allowing flexibility and dynamic membership
customization of even the largest AD implementations.
The net.exe tool missed this because it only lists user objects, not group objects
In addition, net.exe can not display specific attributes.
This emphasizes the benefit of custom tools.
Now that we know the Development Department is a member
of the Sales Department, let’s enumerate it:
Based on the output, we have another case of a nested group
since Management Department is a member of Development Department.
Let’s check this group as well:
Finally, after searching through multiple groups, it appears we found the end.
According to the output,
jen is the sole member of the Management Department group.
Although we saw jen as a member of the Management Department group earlier,
we obtained additional information about the group memberships
in this case by enumerating the groups oneby-one.
This concludes the journey with our PowerShell script that invokes .NET
classes to run queries against AD via LDAP.
As we have verified, this approach is much more powerful than running tools
such as net.exe and provides a wealth of enumeration options.
With a basic understanding of LDAP and how we can use it to communicate with AD
using PowerShell, we’ll shift our focus in the next section
to a pre-developed script that will speed up our process.