Python Scripting Basics

This project marks a transition from shell-based automation to the use of a high-level, general-purpose programming language for complex system tasks. By completing this lab, you'll have demonstrated the ability to leverage Python's design philosophy—emphasizing code readability and modularity—to build scripts that are more scalable and maintainable than traditional shell scripts.



Cybrary is a well established and free IT training platform with several intuitive labs to explore

A paid subscription with more advanced labs is available as well outside the scope of this platform

Head to https://www.cybrary.it to create a free account for learning available on their platform

Head to Python Scripting Basics to complete it yourself or you can perform it on your homelab below


Quick Links:



   1. Python Overview

   2. Install Nix OS

   3. Python Basics w/ REPL

   4. Python Scripting w/ VS Code

   5. Functions in Python

   6. Misconfigure an Active Directory Environment

   7. Join Linux Workstation to Active Directory Domain

   8. Map AD Attack Surface Using Python

   9. Remediate Active Directory Vulnerabilities


Requirements:


 • Windows PC w/ Internet Connection

 • USB Flash Drives w/ at least 64GB Capacity

 • 2 Unused PCs w/ at least 4GB of Memory


1. Python Overview


Python is a general-purpose programming language that was invented by Guido Van Rossum in the late 1980s.

While developing Python, Can Rossum was havily inspired by the ABC programming language. Unlike many other

programming languages of the era, ABC e,phasized readability and simplicity, and was intended for teaching

and prototyping, rather than actual systems programming. As a result, Python is also characterized by its

readability and simplicity, and today it is widely regarded as one of the most used programming lnaguages





Python Features

Python is an interpreted high-level programming language. Code is created using a human-readable syntax

and then converted into machine code, line by line, on the fly. There is no need to formally compile and

link code as required for languages like C or C++. Python Shares many of the same core concepts as other

popular multi-paradigm programming languages like Java or C++. While this lab is not intended to be a full

comprehensive introduction to programming, there's simply too much to discuss, we will briefly review some

of the core concepts used heavily in the upcomming lesson as a baseline and a refresher on the subject


 • Variables - Variables are containers (reserved memory locations) for storing data, like strings and

integers. For example, x = 1 stores the integer 1 in the container x.


 • Types - All data stored in memory has a type. Standard data types include numbers,strings, lists,

tuples, and dictionaries. For example, if we code name = "John", the variable name would be type string.

If we code age = 20, the variable age is type integer. While you can assign a variable type - for example,

x = int(10) or name = str("John") - this is seldom necessary, as Python can typically guess the type of

the variable for you


 • Operators - Operators are used to perform actions on variables. The most commonly used operators are

arithmetic (add, subtract, divide, multiply, etc.), assignment operators (x = "cow", y = 1.234, etc.),

comparison operators (equals, not equals, greater than, less than), and membership operators (is x in y)

 • Functions - A function is a named block of code from which you can pass data to get data returned

Once a function is created, it can be used, or "called", as often as needed.


 • Methods - Methods are special types of functions associated with an object. You can think of a method

as a built-in function. For example when you create a string (name = "John"), the new string object has

built-in functions to make all the letters uppercase ("JOHN"), or lowercase ("john"), or even the reverse

case ("jOHN").


 • Loops - Loops repeat a task until some criteria are met. For example, open a file and read each line

of text or iterate through all data stored in a list.


 • If / Else Conditions - If / Else statements allow a program to make decisions based on the result of

a test. For example, you may want to add 1 to variable x until x is 10, or you may want to loop through a

list of names until the name "John" is found.



Practical Applications

Python is remarkably popular. Let's briefly review some of the most common applications which use Python


 • Web Developement - Python offers many frameworks for web developement, including Flask and Django

These frameworks allow for rapid yet stable web application developement. Python also has many powerful

libraries like Scrapy and BeautifulSoup for web crawling.


 • Data Science - Alongside R, Python is the go-to language for data science. Python has a number of

libraries for numerical computations (Tensorflow, NumPy), data analysis (Pandas), data visualizations

(Matplotlib), and image processing (SciPy).


 • AI / Machine Learning - Python excels at managing and manipulating massive data sets, making Python

a key tool for machine learning (ML), large language models (LLS) and Artifician Intelligence (AI) research

Most libraries noted above are also used for ML/LLM/AI (for example, NumPy, SciPy, Pandas, and Tensorflow)

There are also ML-specific libraries like Scikit Learn. Scikit Learn leverages tools like NumPy and SciPy

to support supervised and unsupervised learning.


 • Cybersecurity - Offensive and defensive security practitioners use Python in every phase of the

cybersecurity lifecycle. With Python, you can scan networks, scrape web pages, create fuzzers and payloads

, decode packets, conduct targeted vulnerability assessments, and automate reconnaissance.



Modes of Operation

Python has two modes of operation: script and interactive. The interactive shell is called REPL (Read Eval

Print Loop). Each line of code is Read, then Evaluated, and any return vaules are Printed. In script mode,

a complete program is written in an editor and then passed to the Python interpreter in it entirety. You

will have an opportunity to practice with both in the upcoming lab. In the next lesson, you will practice

using some of the fundamental Python concepts discussed above. Following this, we will switch gears and

practice writing several Python scripts using Visual Basic Studio code editor, but first we need our OS


2. Install Nix OS


Nix OS is a Linux Distribution built on top of the Nix package manager. Unlike traditional distros like

Debian or Arch, NixOS is declarative and immutable, meaning many program package versions can co-exist

Because Nix OS isolates dependencies, it's ideal for testing software which requires specific packages




Because of these and many other features, Nix OS has now established itself as the ideal flavor of Linux

for software developers. Nix provides developers with a complete and consistent developement environment


Download Nix OS Iso: latest-nixos-graphical-x86_64-linux.iso

Download Rufus Disk Imaging Software: Rufus Official Download


Insert USB Flash Drive, run rufus.exe, select target drive, select the Nix OS Image file, then click start




Remove the USB Flash Drive and Insert into unused PC. Start PC and press the hotboot key at the startup:




Once our live operating system boots we will be taken to the GNOME pre-installation desktop environment




Click the installer window > Next > Next > Next and fill in the password and the username as itlabcenter




Now hit Next > Budgie > Next > then check the box to Allow unfree software > Next. Now we must partition




Use the dropdown at the top to select your drive and check Erase Disk > Swap (no Hibernate), and hit Next




At the summary, click Install. This will begin the installation which may take some time to fully complete




Once complete check the box to restart now and hit Done. Our system will now boot to Desktop Environment




With our Operating System installed and booted up to the desktop environment, we are ready to begin coding


3. Python Basics w/ REPL


In the first part of this lab, we will practive using several of the key Python concepts convered in the

overview section. We will begin by launching REPL - the python3 interactive shell. Hit the none dots in

the lower-left had corner of the desktop and select Terminal. This will launch our Terminal Application




We will work primarily from this terminal for the remainder of this lab on Python so keep this window open

Run the following command from the Nix OS Terminal to edit the package manager build configuration file


[itlabcenter@nixos:~]$ sudo nano /etc/nixos/configuration.nix


Now scroll down to the following section and add python3. Hit CTRL+O to save changes and CTRL+X to exit


environment.systemPackages = with pkgs; [

    python3

];


Run the following command from the Nix OS Terminal to apply the configuration and rebuild the packages


[itlabcenter@nixos:~]$ sudo nixos-rebuild switch


Run the following command from the Nix OS Terminal to launch the python3 interactive shell application


[itlabcenter@nixos:~]$ python3


Resulting Output:




Run the Python Statements from the REPL Terminal to assign a value to a variable and print the variable


>>> my_integer = 1

>>> my_integer


Resulting Output:




You should see that REPL prints the current value of my_integer to the screen. Note that variable=integer

and variable = integer are the same in Python. However, variable = integer is preferred and easier to read

Execute the following statements from the REPL Terminal to perform arithmetic operations on our variable


>>> my_integer * 10

>>> my_integer + my_integer

>>> my_integer ** 3


Resulting Output:




Note that in the above operations the value of my_integer does not change, only the statements output does

Execute the following statesments from the REPL Terminal to reassign our variables value and print it out


>>> my_integer = my_integer * 10

>>> my_integer


Resulting Output:




Execute the following statement from the REPL Terminal to use Python's type() function on our variable


>>> type(my_integer)


Resulting Output:




The type() function reports what a variable is. We see that my_integer is, in fact, "class int", integer

Execute the following statements from the REPL Terminal to update the value of my_integer and print it


>>> my_integer = 1.234

>>> my_integer


Resulting Output:




Execute the following statement from the REPL Terminal to use Python's type() function on our variable


>>> type(my_integer)


Resulting Output:




Notice that the type has changed to float. You may recall that integers are whole numbers, no decimals

A float is a floating point number, meaning it had decimals. The lesson here is that Python is dynamically

typed. Variables are assigned their type at runtime. We do not need to assign variable types in advance

Execute the following statements from the REPL Terminal to compare our variable to two different values


>>> my_integer == 1

>>> my_integer == 1.234


Resulting Output:




Because 1 does not equal 1.234, Python reports back False. When we compare the variable to 1.234 its True

As discussed in the previous lesson, the symbols we use on variables (+, **, =, ==, etc.) are referred to

as operators. Python operators are used for arithmetic, assignment, comparison and membership functions


Arithmetic Meaning Example
+ Add x + y
- Subtract x - y
* Multiply x * y
/ Divide x / y
** Exponent x ** y
Comparison Meaning Example
== Equals x == y
!= Not Equals x != y
> Greater Than x > y
< Less Than x < y














Assignment Meaning Example
= Assign x = y
+= Increment x += 1
-= Decrement x -= 1
Membership Meaning Example
in Contains if x in y:
not in Does not Contain if x not in y:









Execute the following statements in the REPL Terminal to create a string and print the strings value out


>>> my_string = "Happy Puppy"

>>> my_string


Resulting Output:




In Python, a string is a character list. Each letter is placed into a special container called a list. We

can access every element of a list by calling its index number. Here is an example of the my_string list


0 1 2 3 4 5 6 7 8 9 10
H a p p y P u p p y

In my_string above, there are 11 elements starting at index 0. Execute the following statement from REPL


>>> my_string[0]


Resulting Output:




Execute the following statements from the REPL Terminal to print multiple of this strings index values


>>> my_string[1]

>>> my_string[4]

>>> my_string[10]


Resulting Output:




Execute the following statement from the REPL Terminal, notice you get an error since there is no Index 11


>>> my_string[11]


Resulting Output:




When working with strings and other lists, it's good to find the length of the string to avoid errors like

the one above. Python provides a function for this. Execute the following statement from the REPL Terminal


>>> len(my_string)


Resulting Output:




Remember, 11 means 0 to 10! Because strings are lists in Python, they can be chopped into other elements.

Python provides a neat tool for chipping strings called slicing. Execute the following statement from REPL


>>> my_string[0:5:1]


Resulting Output:




The statement we executed above intructs Python to slice the my_string variable using these parameters:


 • Start at Index 0

 • Advance to element 4 (slices are always index number -1)

 • Advance one element at a time


Typically this is written as my_string[0:5], there is no need to specify the advance number if you want to

the default of 1. Execute the following statements from the REPL Terminal to experiment with more slices


>>> my_string[1:5]

>>> my_string[6:11]


Resulting Output:




Did the second slice confuse you? There is no 11th element! Why did we not get an error? When slicing, we

have to reference what we want plus one. Thus, if we want to see the 10th element, we must ask for the 11th

There is likely a reasonable explanation for this silliness, but sometimes you just have to accept the made

rules, even arbitrary ones, to get a program to do what you want. Execute the following statements from REPL


>>> new_string = "I am a happy sailor."

>>> new_string.find("sailor")

>>> new_string[13:19]


Resulting Output:




The .find() method tells us where the start of "sailor" is in the string. Press CTRL+L to clear the screen

Then execute the following statements from the REPL Terminal to create and concatenate strings together


>>> str1 = "the"

>>> str2 = "cow"

>>> str3 = "says"

>>> str4 = "moo"

>>> str5 = " "

>>> str6 = str1 + str5 + str2 + str5 + str3 + str5 + str4

>>> str6


Resulting Output:




Execute the following statements from the REPL Terminal to create and add string numeral variables together


>>> var1 = "1"

>>> var2 = "2"

>>> var3 = "3"

>>> var4 = var1 + var2 + var3

>>> var4


Resulting Output:




Did you expect var4 to be 6? What type of variable is var1, var2, and var3? Are we dealing with addition

(integers) or concatenation (strings)? Because the variables are characters (assign as "1" rather than 1)

Python will concatenate them, not add them. Now execute the following statements from the REPL Terminal


>>> str1 = "Pepper"

>>> str2 = "pepper"

>>> str1 == str2


Resulting Output:




In Python, you can compare strings like you can numbers. Are the two strings above equal? When comparing

strings, know that Python is case-sensitive. Now execute the following statement from the REPL Terminal


>>> str1.lower() == str2.lower()


Resulting Output:




In the example above, we access the .lower() method of the string object to make str1 and str2 lowercase

for the comparison operator. Note that the value of str1 does not change when we access a method. If we

wanted to change str1, we would use str1 = str1.lower(). We have already explored lists, also known as

when we discussed strings. Let's learn more about lists and explore the program login known as a loop

Execute the following statements from the REPL Terminal to create a list and print out the total length


>>> my_list = ["cat","dog","cow","pig","bunny","horse"]

>>> my_list

>>> my_list[0]

>>> my_list[1]

>>> len(my_list)


Resulting Output:




How many elements are in my_list according to len()? Next, let's loop through my_list using a for loop

Don;t confuse a string (list of characters) with a list of strings. In a list of strings, each string is

an element. In a string, each character is an element. Run the following statement from the REPL Terminal


>>> for animal in my_list:


Resulting Output:




Notice the change in prompt. REPL is waiting for more instructions. Indent two spaces, then run the below


...  print(animal)

...


Press Enter twice. Pressing Enter at ... tells REPL we are done typing commands and to execute the code




In the example above, the for loop creates an animal variable, then steps through each element of the list

and places that element into animal (one at a time). The loop then prints animal to the screen each element

Execute the following statements from the REPL Terminal to incorporate conditionals into our looping logic


>>> car_makers = ["Ford","Chevy","BMW","Volkswagen","KIA","Volvo"]

>>> for car_maker in car_makers:

...  if car_maker == "BMW":

...    print("Better make way!")

...  else:

...    print("Here comes a " + car_maker)

...


Resulting Output:




Note there are two spaces before if and else and four spaces before the print statements. Using the proper

indentation is critical in Python. Refer to the screenshot above to validate your input. The code above

introduces a new logic element: the if / else condition. We test the current value of car_maker against

the string "BMW". IF there is a match, we print "Better make way!" ELSE, we concatenate "Here comes a"

with the current string in the variable car_maker. You will see this pattern repeatedly in programming:

"for apple in apples", "for cow in cows", "for people in list_of_people", etc. While "for x in y" would

work just as well, it conveys no meaning to the reader. It's always best to use meaningful variable names

Execute the statement exit() from the REPL Terminal to leave the interactive Python Terminal Application


4. Python Scripting w/ VS Code


While working with REPL interactively is great for testing syntax and quick prototyping, writing anything

beyond a few lines becomes cumbersome and error-prone. You may have already discovered how easy it is to

make a typo and have to start over. In the next part of this lab exercise, we will work with Microsoft's

Visual Studio Code (VS Code) integrated development environment (IDE) to make our lives so much easier




Execute the following command from the Nix OS Terminal to edit the package manager build configuration



[itlabcenter@nixos:~]$ sudo nano /etc/nixos/configuration.nix


Noe add the following line anywhere inside the main bracket to enable packages with non-free licenses


nixpkgs.config.allowUnfree = true;


Now scroll down to the following section and add vscode. Hit CTRL+O to save changes and CTRL+X to exit


environment.systemPackages = with pkgs; [

    vscode

];


Run the following command from the Nix OS Terminal to apply the configuration and rebuild the packages


[itlabcenter@nixos:~]$ sudo nixos-rebuild switch


Execute the following command from the Nix OS Terminal to launch the Visual Basic Studio Code Editor app


[itlabcenter@nixos:~]$ code


Resulting Output:




From the VS Code menu bar, select File > New File... name our new file names.py > Built-in > Create File

You will receive a notification at the bottom-right hand corner prompting you to install Python, click it




Now head back to our names.py file tab and type out the following script, be sure to include indentation


names = ["Jane","Sally","June","Mary","Karen","Liz","Tara","Maddy","Lynne"]


name_requested = input("What name can I find for you: ")


for name in names:

  if name == name_requested:

    print("I have found " + name + " in my list.")


In the names.py script, we see a list called names with names in it. We then see something new: the input()

function. The input() function will prompt the user for input and then place that input into a variable.

Finally, we see the now familiar for loop and if condition, used to test is name_requested matches any of

the names in the list. In the upper-right hand corner, click the Run icon (triangle) to test the program




A new terminal window will open in the bottom half of VS Code. Type Karen and press Enter at the prompt

Click the Run icon to re-run the script. This time, type in Lauren and press Enter at the prompt for name




You should see that nothing happens. There's nothing in the script to handle a situation when a name is

not found. Let's change that. Alter the script by adding the following line of code to the bottom of it


print("Sadly I did not find " + name_requested + ".")


Press CTRL+S to save, then click the Run icon to re-run the script. Once again, type Lauren and hit Enter




That seems okay, but let's try the script with a name in the list. Click Run and type Mary and hit Enter




Our script is giving us conflicting messages. There are several possible solutions, but the simplest would

be to end the script is a name is found. To implement this change add exit() to the if condition like below


names = ["Jane","Sally","June","Mary","Karen","Liz","Tara","Maddy","Lynne"]


name_requested = input("What name can I find for you: ")


for name in names:

  if name == name_requested:

    print("I have found " + name + " in my list.")

    exit()


print("Sadly I did not find " + name_requested + ".")


Press CTRL+S to save, then use the run icon to re-run the script. Try using both the Karen and Lauren names




Great, but what if the user is lazy and does not want to capitalize the first letter in a name? Try karen




Recall that we can use the .lower() method on strings to make them all lowercase for testing. Alter the

script by adding the .lower() method to the name variables. The retest it with the names karen and lauren


names = ["Jane","Sally","June","Mary","Karen","Liz","Tara","Maddy","Lynne"]


name_requested = input("What name can I find for you: ")


for name in names:

  if name.lower() == name_requested.lower():

    print("I have found " + name + " in my list.")

    exit()


print("Sadly I did not find " + name_requested + ".")


Resulting Output:




What if a program could add a name to the list of names? Happily, lists are mutable, meaning they can be

altered. Add the following code to the bottom of the name.py script, then run the script and add Lauren

When prompted, type Y and enter to add Lauren to the list. Then run the script again to test the new name


add_name = input("Would you like to add " + name_requested + " to the list of names? (Y/N)")


if add_name.lower() == "y":

  names.append(name_requested)

  print(name_requested + " has been added to my list")

else:

  print("Ok. I will not add " + name_requested + ".")

  exit()


Resulting Output:




What's going on? Is our code even adding Lauren to the list? Let's confirm if Python is indeed appending

Lauren to the names list. Add the following code to the if condition, right above the else line and test


  print(names)


Resulting Output:




Lauren is being added to the names list, but if we re-run the script, we still find no Lauren in the list




This is because the names list gets re-written every time the program runs. If we wanted our list to be

stateful (to remember things), we must store the names in the list outside the script. One solution is to

use a file. Fortunately, it is very easy to read and write files using Python. In the next steps, we will

alter the script to read a text file with our names and then write any changes to a file before exiting

Run the following commands from the Nix OS Terminal to create a names.txt file for Python to use for names


[itlabcenter@nixos:~]$ echo "Jane" > names.txt

[itlabcenter@nixos:~]$ echo "Sally" >> names.txt

[itlabcenter@nixos:~]$ echo "June" >> names.txt

[itlabcenter@nixos:~]$ echo "Mary" >> names.txt

[itlabcenter@nixos:~]$ echo "Karen" >> names.txt

[itlabcenter@nixos:~]$ echo "Liz" >> names.txt

[itlabcenter@nixos:~]$ echo "Tara" >> names.txt

[itlabcenter@nixos:~]$ echo "Maddy" >> names.txt

[itlabcenter@nixos:~]$ echo "Lynne" >> names.txt


Now return to your VS Code window and at the top of the script, replace the names list with the following


names = []

with open('names.txt','r') as file:

  for line in file:

    line = line.strip()

    names.append(line)


This code snippet creates an empty list called names. The next line of code opens names.txt for reading

and gives us a handle to the open text document called file. Then, a for loop extracts text from the file

handle line by line. Each line is stripped (the newline character is removed) and appended to the names

list. Using "with open" is an efficient way to enumerate a file, as the operation closes the open file

Now that we have our array read from a file, add the following code to the bottom of our names.py script


file = open("names.txt","a")

file.write(name_requested + "\n")

file.close()

Before the script ends, we open names.txt for appending. We then write name_requested plus one new-line

character to the file handle. Lastly, we need to close the open file because we used open() vs with open()

With this in mind, here is an example of what our last lines of code would look like using the with open()


with open(names.txt,"a") as file:

  file.write(name_requested + "/n")


Note that we exit() the program if the value of ass_name is anything other than Y or y. Thus, the script

will end without writing to the file if the user does not answer Y or y when they're prompted to add name

With these several modifications we have made to correct our login for saving names, here is what we have




Press CTRL+S to save, then re-run the script. Try adding Lauren to the names list, then re-run to test it




As a scripter and developer, adding comments to make your program understandable to others (and your future

self) is always a good idea! in Python, there are two types of comments, here are examples of each type:


"""

This is a

multiline

comment

"""


#This is a single line comment


Comments do not need to describe every line of code in detail. Instead, they should give a general overview

of what a given code block does, or is suppose to do. Comments are helpful but can be too much if overused


5. Functions in Python


The names.py program we just worked on flows procedurally from top to bottom. This is perfectly fine for

small programs and quick prototyping. However, Python programs typically leverage functions. Functions are

reusable blocks of code that perform a specific task. Functions help to organize code, making it much more

readable and efficient. Once you write a function it can be called as many times as you need it in a script

Like in any program language, functions are defined with a specific syntax structure in Python as follows:


 • def: Keyword used to define a function.

 • function name: Identifier for the function.

 • parameters: Input values (arguments) the function accepts (optional).

 • docstring: A special type of comment used to explain the function (optional but recommended).

 • return: Keyword used to return a value from the function (optional).


Let's practice implementing functions into our scripts. In VSCode clie File > New File > Python File Python

Enter in the following statements into our new VS Code Python Script to define a function and call on it


def capitalize (text):

  """ Returns a word in all caps """

  text = text.upper()

  return(text)


string = "rubber ducky"

print( capitalize(string) )

print( capitalize("jimmy") )

print( capitalize("abc123") )


Press CTRL+S to save the new code as functions.py. Click the Run icon to run the script, here's the output




Let's break all this down... First, the def command is used to define a function called "capitalize". We

see that the new capitalize function takes in one parameter called text. There is an optional docstring

(comment) telling us what this function does. Next, we run the .upper() method to capitalize any letters

found in the test parameter. Next, we return the value of text. Finally, we call the capitalize function

by name inside of each print command, passing different things (a variable, a string, and a string with

numbers in it) into the function to see what it will print. We can see that our script is fully functional

Now that we have covered the basic priciples of scripting in Python, lets move onto using the language in

a more practical, Security focused sense, by incorporating it into infrastructure. Let's build it up now


6. Misconfigure an Active Directory Environment


Python contains many useful modules designated for integrating with enterprise environments and frameworks

One of these is the ldap3 library, which allows Python scripts to integrate with Active Direectory Setups

Here we will step beyond the basic REPL statements and VS Code scripts to perform real-world Security work

We will start by setting up our Active Directory environment using Windows Server as our Domain Controller


Download Windows Server 2025 Disk Image (ISO): Windows Server 2025 ISO

Download Rufus Disk Imaging Software: Rufus Official Download


Insert USB Flash Drive, run rufus.exe, select target drive, select our Windows Server 2025 ISO, hit start:




Use the rufus popup menu to customize the Windows Server 2025 installation and disable data collection:




Remove USB Flash Drive and Insert into unused PC. Start the PC and press the hotboot key at the startup:




Navigate through the installer and select the Windows Server Standard Evaluation Desktop Experience option




Now that we have our Server operating, the next step is to promote this server to an AD domain controller

On the taskbar, click the Windows Start icon, then click the Server Manager to open the Windows Manager:




In the Server Manager, click the Add Roles and Features link to open the Add Roles and Features Wizard

Hit Next > Next > Next > Active Directory Domain Services > Add Features > Next > Next > Next > Install




Now hit Promote this Server to a Domain Controller > Add a new forest > Root Domain Name: itlab.local




Hit Next, then enter the root password and hit Next > Next > Next > Next > Next > and finally Install

This installation may take some time, a new Domain Controller can take up to 5 minutes to fully boot up

Once our domain controller has booted up, we can move on to creating the vulnerabilities. In this project

we will use Python scripts to create an attack surface map of our active directory environment. For this

to work we need security misconfigurations. Simply installing the role isn't enough; out of the box, AD

is relatively secure. To make our Python Attack Surface Mapper actually find vulnerabilities, we must

deliberately plant the misconfigurations after promotion. From the taskbar open PowerShell ISE as admin




Type out the following script in the top panel of the PowerShell ISE Utility, then hit the green RUN arrow


Import-Module ActiveDirectory

$Domain = "DC=itlab,DC=local"

$TargetOU = "OU=Lab-Assets,$Domain"

$DefaultPass = ConvertTo-SecureString "Winter2026Lab!" -AsPlainText -Force

Write-Host "[*] Initializing Attack Surface Deployment..." -ForegroundColor Cyan


# 1. Create the Lab Asset Container

try {

    New-ADOrganizationalUnit -Name "Lab-Assets" -Path $Domain -ErrorAction Stop

    Write-Host "[+] Created OU: Lab-Assets" -ForegroundColor Green

} catch {

    Write-Host "[!] OU Lab-Assets already exists, proceeding..." -ForegroundColor Yellow

}


# 2. Deploy AS-REP Roasting Target

$User1 = "Vulnerable-SVC1"

New-ADUSer -Name $User1 -SamAccountName $User1 -Path $TargetOU -AccountPassword $DefaultPass -Enabled $true

Set-ADAccountControl -Identity $User1 -DoesNotRequirePreAuth $true

Write-Host "[+] Seeded: AS-REP Roasting Target ($User1)" -ForegroundColor Green


# 3. Deploy Kerberoasting Target

$User2 = "SQL-ServiceAccount"

New-ADUser -Name $User2 -SamAccountName $User2 -Path $TargetOU -AccountPassword $DefaultPass -Enabled $true

Set-ADUser -Identity $User2 -ServicePrincipalNames @{Add="MSSQLSvc/db01.itlab.local:1433"}

Write-Host "[+] Seeded: Kerberoasting Target ($User2)" -ForegroundColor Green


# 4. Deploy Reversible Encryption Target

$User3 = "Legacy-App-User"

New-ADUser -Name $User3 -SamAccountName $User3 -Path $TargetOU -AccountPassword $DefaultPass -Enabled $true

Set-ADUser -Identity $User3 -AllowReversiblePasswordEncryption $true

Write-Host "[+] Seeded: Reversible Encryption Enabled ($User3)" -ForegroundColor Green


# 5. Deploy Password Never Expires Target

$User4 = "Svc-BackupExec"

New-ADUser -Name $User4 -SamAccountName $User4 -Path $TargetOU -AccountPassword $DefaultPass -Enabled $true

Set-ADUser -Identity $User4 -PasswordNeverExpires $true

Write-Host "[+] Seeded: Password Never Expires ($User4)" -ForegroundColor Green


# 6. Deploy Password Not Required Target

$User5 = "Vendor-Temp"

New-ADUser -Name $User5 -SamAccountName $User5 -Path $TargetOU -PasswordNotRequired $true

Enable-ADAccount -Identity $User5

Write-Host "[+] Seeded: Password Not Required ($User5)" -ForegroundColor Green


# 7. Deploy Unconstrained Delegation Target (Computer Object)

$Comp1 = "SRV-WRB-LEGACY"

New-ADComputer -Name $Comp1 -SamAccountName $Comp1 -Path $TargetOU

Set-ADComputer -Identity $Comp1 -TrustedForDelegation $true

Write-Host "[+] Seeded: Unconstrained Delegation Target ($Comp1)" -ForegroundColor Green


Write-Host "`n[*] Vulnerability Deployment Complete. The domain is now vulnerable." -ForegroundColor Cyan


Resulting Output:




This PowerShell script introduces vulnerabilities into our Active Directory environment, details below



AS-REP Roasting (DoesNotRequirePreAuth)

Normally, Kerberos requires a user to encrypt a timestamp with their password hash to request an initial

ticket (TGT). If pre-authentication is disabled, anyone can request auhtetication data for that user. The

Domain Controller will happily reply with a message encrypted with the user's password hash. The attacker

then saves this offline and cracks it at their leisure.



Kerberoasting (Service Principal Names on User Accounts)

Service Principal Names (SPNs) link a service to a specific logon account. Any authenticated domain user

can request a service ticket (TGS) for any SPN in the domain. The Domain Controller encrypts part of this

ticket using the password hash of the account running the service. The attacker extracts this ticket from

memory and cracks it offline.



Reversible Password Encryption (AllowReversiblePasswordEncryption)

Active Direectory normally stores passwords as one-way NTLM hashes. This legacy setting forces AD to store

passwords in a format that is easily decrypted back to pure plaintext. If an attacker performs a DCSync

attack or dumps the ntds.dit database, they don't even have to crack the hash, they just read the plain

test password.



Unconstrained Delegation (TrustedForDelegation)

Delegation allows a server to impersonate a user to access other network resources. The term Unconstrained

Delegation means the server is trusted to impersonate the user to any service. If an attacker compromises

this server and tricks a Domain Admin into authentication to it (often via forced authentication attacks

like the "Printer Bug"), the server caches the Admin's TGT, and the attacker instantly owns the entire

domain.



Password Never Expires

A staple of poorly managed service accounts. If an attacker sompromises and account where the password has

not changed since 2015, they establish long-term persistence that survives standard IT security audits.



Password Not Required (PasswordNotRequired)

This flag bypasses domain password complexity requirements entirely, allowing the account to have a blank

password. It is a massive backdoor for trivial unauthorized access.


This perfectly simulates real-world scenarios where a lazy administrator bypassed the domains password

or account creation policies to make things easier on themselves, while putting the orgs security at risk


7. Join Linux Workstation to Active Directory Domain


While not strictly necessary, in many cases you will be running your assessments from internal endpoints

Here we will simulate that environment by joining our Nix OS / Linux workstation to the Active Directory

To bridge the gap between Windows identity structures and Linux security subsystems, we use three tools


   1. Kerberos (krb5): Handles ticket-based authentication

   2. adcli: A specialized low-level binary that creates the computer object inside Active Directory and

   handles the initial secure handshakes

   3. SSSD (System Security Services Daemon): The real workhorse. It sits between local Linux system apps

   (like PAM for logins and Sudo for provilege escalation) and remote identity providers (Active Directory)


There are many moving pieces here, and they all interact with one another seamlessly, here is an example


               ┌─────────────────────────────┐
               │      ~~~ ~                  │
               │    ~(     )  REMOTE SERVER  │
               │   (________)                │
               └─────────────────────────────┘
                              ↑
                              │
                              │
┌─────────────────────────────┴─────────────────────────────┐   ┌────────────────┐
│                      BACKEND SERVICE                      │   │     TOOLS      │
│  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐  │   │ ┌────────────┐ │
│  │   DOMAIN ID   │  │  DOMAIN AUTH  │  │      ...      │  │   │ │   sssctl   │ │
│  └───────────────┘  └───────────────┘  └───────────────┘  │   │ └────────────┘ │
│                                                           │   │                │
└─────────────────────────────────────────┬─────────────────┘   │ ┌────────────┐ │
                  ↑           ↑           │                     │ │ sss_cache  │ │
┌───────┐                                 Cache   _______    │ └────────────┘ │
│       │ Control                         Write  (_______)   │                │
Monitor─────────┤    Private│DBUS       └────────→(_______)   │ ┌────────────┐ │
│       │ Process                       ┌────────→(_______)   │ │sss_override│ │
└───────┘                                 Cache  (_______)   │ └────────────┘ │
                  │           │           │  Read               │                │
                  ↓           │           │                     │ ┌────────────┐ │
┌─────────────────────────────────────────┴─────────────────┐   │ │  sss_seed  │ │
│                         RESPONDER                         │   │ └────────────┘ │
│  ┌────────┐ ┌────────┐ ┌─────────┐ ┌────────┐ ┌────────┐  │   │                │
│  │  NSS   │ │  SUDO  │ │   PAM   │ │ AUTOFS │ │  ....  │  │   │ ┌────────────┐ │
│  └────────┘ └────────┘ └─────────┘ └────────┘ └────────┘  │   │ │    ....    │ │
│      ↑           ↑          ↑          ↑           ↑      │   │ └────────────┘ │
└──────┼───────────┼──────────┼──────────┼───────────┼──────┘   │                │
       │           │          │          │           │          │                │
       │           │ SSSD Wire│Protocol  │           │          │                │
       │           │          │          │           │          │                │
┌──────┼───────────┼──────────┴──────────┼───────────┼──────┐   │                │
│      │           │  CLIENT LIBRARIES   │           │      │   │                │
│  ┌───┴────┐ ┌────┴───┐ ┌─────────┐ ┌───┴────┐ ┌────┴───┐  │   │                │
│  │  nss   │ │  sudo  │ │   pam   │ │ autofs │ │  ....  │  │   │                │
│  └────────┘ └────────┘ └─────────┘ └────────┘ └────────┘  │   │                │
│      ↑           ↑          ↑          ↑           ↑      │   │                │
└──────┼───────────┼──────────┼──────────┼───────────┼──────┘   │                │
       │           │          │          │           │          │                │
       │           │     Library API     │           │          │                │
       │           │          │          │           │          │                │
┌──────┼───────────┼──────────┴──────────┼───────────┼──────┐   │                │
│      │           │ CLIENT APPLICATIONS │           │      │   │                │
│  ┌───┴────┐ ┌────┴───┐ ┌─────────┐ ┌───┴────┐ ┌────┴───┐  │   │                │
│  │ glibc  │ │  sudo  │ │   pam   │ │automnt │ │  ....  │  │   │                │
│  └────────┘ └────────┘ └─────────┘ └────────┘ └────────┘  │   │                │
│                                                           │   │                │
└───────────────────────────────────────────────────────────┘   └────────────────┘

As you can see in the architecture layout above, SSSD acts as a centralized caching responder. When you

type a command or try to authenticate a domain user, your local system libraries query SSSD via the wire

protocol, which then queries the Active Directory backend. Because Nix OS is completely declarative, we

don't configure files like /etc/sssd/sssd.conf or /etc/krb5.conf manually. If you edit them directly, Nix

will simply wipe them out on the next rebuild. Instead, we declare our configuration file in a Nix Module

Run the following command from the Nix OS Terminal to open and edit a new configuration file using VS Code


[itlabcenter@nixos:~]$ sudo code --no-sandbox --user-data-dir /home /etc/nixos/active-directory.nix


Type out the following Nix configuration file as shown below. Use the CTRL+S shortcut to save afterwards


# /etc/nixos/active-directory.nix

{ config, pkgs, ... }:


let

    domainLower = "itlab.local";

    domainUpper = "ITLAB.LOCAL";

in {

    # 1. Install the enterprise identity packages

    environment.systemPackages = with pkgs; [

        adcli

        krb5

        sssd

        samba4Full

        bashInteractive

        bind

        openldap

    ];


    environment.shells = [ pkgs.bashInteractive ];


    # 2. Configure Name Resolution via your DC

    services.resolved.enable = true;


    # 3. Configure Kerberos

    security.krb5 = {

        enable = true;

        settings = {

            libdefaults = {

                default_realm = domainUpper;

                rdns = false;

                dns_canonicalized_hostname = false;

            };

        };

    };


    security.pam.services.sssd.enable = true;


    # 4. Configure SSSD to translate Active Directory to Linux

    services.sssd = {

        enable = true;

        config = ''

            [sssd]

            domains = ${domainLower}

            config_file_version = 2

            services = nss, pam, sudo


            [pam]

            offline_credentials_expiration = 30


            [domain/${domainLower}]

            id_provider = ad

            auth_provider = ad

            chpass_provider = ad

            access_provider = ad


            cache_credentials = true

            ldap_id_mapping = true # Maps Windows SIDs to consistent Linux UIDs automatically


            # Nix OS uses a unique filesystem structurel override the default login shell

            override_shell = "${pkgs.bashInteractive}/bin/bash"

        '';

    };


    # 5. Map the AD "Domain Admins" Group to Local Linux Sudo

    security.sudo = {

        enable = true;

        extraConfig = ''

            # SSSD preserves spaces by default; escape the space with a backslash

            %Domain\ Admins ALL=(ALL:ALL) ALL

        '';

    };


    # 6. Auto-create home directories for domain users upon their first login

    security.pam.services.login.makeHomeDir = true;

    security.pam.services.su.makeHomeDir = true;

}


Open your primary /etc/nixos/configuration.nix file and add your new module to the pre-existing imports:


imports = [

    ./hardware-configuration.nix

    ./active-directory.nix

];


Run the following commands from the Servers Administrator PowerShell to find the servers hostname and IP


PS C:\Windows\System32> ipconfig

PS C:\Windows\System32> hostname


Resulting Output:




Open your primary /etc/nixos/configuration.nix file and add the following network DNS declaration code


networking.nameservers = [ "192.168.0.220" ]; # Replace with your actual DC IP Address


networking.hosts = {

    "192.168.0.220" = [

        "win-hb56o5eo34g.itlab.local"

        "win-hb56o5eo34g"

        "itlab.local"

    ];

};


services.resolved = {

    enable = true;

    extraConfig = ''

        DNSStubListener=no

        Domains=itlab.local

    '';

};

Run the following command from the Nix OS Terminal to rebuild the system by compiling installed packages


[itlabcenter@nixos:~]$ sudo nixos-rebuild switch


Resulting Output:




SSSD might fail to start immediately during this initial rebuild. This is completely expected. SSSD can't

start cleanly until the machine is physically joined to the domain and a local keytab file can be located

Now use adcli to securely connect to your Windows Domain Controller, create a computer object account in

the Active Directory database, and securely exchange key materials. You will need to use a domain account

that has rights to join machines to the domain. Run the following command from the Nix OS Terminal below


[itlabcenter@nixos:~]$ sudo adcli join --domain=itlab.local --user=administrator


Type the password for your AD Administrator when prompted. If it suceeds, it will exit silently without

errors and automatically generate your secure authentication file at /etc/krb5.keytab. Enable the Service


[itlabcenter@nixos:~]$ sudo systemctl restart sssd


Resulting Output:




Run the following command from the Nix OS Terminal to test our Active Directory Command Line Permissions


[itlabcenter@nixos:~]$ su - administrator@itlab.local

[administrator@nixos:~]$ ldapsearch -Y GSSAPI -H ldap://itlab.local -b 'DC=itlab,DC=local' '(objectClass=user)' sAMAccountName 2>/dev/null | grep 'sAMAccountName:'

[administrator@nixos:~]$ exit


Resulting Output:




We now have access to the Active Directory user account information from our NIX OS host, time to scan it


8. Map AD Attack Surface Using Python


Let's apply our newly developed Python skills towards real Security Operations work. We will develope a

proactive threat hunting to which can be used to enumerate vulnerabilities and service misconfigurations

within an Active Directory environment. Let's build this script section by section so you can fully see

the mechanics behind it. Since we are on NixOS and utilizing Kerberos, we need to ensure Python has the

correct libraries to speak LDAP (Lightweight Directory Access Protocol) and read your ticket cache. You

will need the ldap3 library and the gssapi wrapper. Adjust your /etc/configuration.nix packages block:


environment.systemPackages = with pkgs; [

    (python3.withPackages(ps: with ps; [

        ldap3

        gssapi

        cryptography

    ]))

    vscode

    krb5

];


Run the following command from the Nix OS Terminal to rebuild the system by compiling installed packages


[itlabcenter@nixos:~]$ sudo nixos-rebuild switch


Execute the following command from the Nix OS Terminal to launch the Visual Basic Studio Code Editor app


[itlabcenter@nixos:~]$ code


From the VS Code menu bar, select File > New File... name our new file mapper.py > Built-in > Create File





Active Directory Attack Surface Mapper Script

Here is the complete script. It is designed to be modular, so we can easily plug in the LDAP filters for

the specific vulnerabilities we engineered. We have pre-loaded it with the LDAP syntax for the critical

six vulnerabilities we have planted, but additional filters can be substituted for other vulnerabilities

Type out the following Python script shown below into your VS Code Editor, then press CTRL+S to save it


import sys

import ssl

from ldap3 import Server, Connection, SASL, GSSAPI, ALL, Tls


# --- Configuration ---

# Replace with the true FQDN of your Domain Controller

DC_FQDN = 'win-hb56o5eo34g.itlab.local'

SEARCH_BASE = 'DC=itlab,DC=local'


# --- The Vulnerability Signatures (LDAP Filters) ---

# We use bitwise LDAP queries to check the userAccountControl attribute

VULNERABILITIES = {

    "AS-REP Roasting (Pre-Auth Not Required)": "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))",

    "Kerberoasting (User Accounts with SPNs)": "(&(objectCategory=person)(objectClass=user)(servicePrincipalName=*)(!(sAMAccountName=krbtgt)))",

    "Passwords Set to Never Expire": "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536)),"

    "Unconstrained Delegation Enabled": "(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=524288))",

    "Password Not Required (Blank Passwords Allowed)": "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=32))",

    "Passwords Stored in Reversible Encryption": "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=128))"

}


def connect_to_ad():

    """Establishes a secure connection to AD using the existing Kerberos ticket."""

    print(f"[*] Attempting GSSAPI connection to {DC_FQDN}...")

    try:

        # Define the server, requesting all schema info

        tls_config = Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2)

        server = Server(DC_FQDN, port=636, use_ssl=True, tls=tls_config, get_info=ALL)


        # Connect using the Kerberos ticket cache (SASL/GSSAPI)

        conn = Connection(server, authentication=SASL, sasl_mechanism=GSSAPI)


        if not conn.bind():

            print("[-] Bind failed. Do you have a valid Kerberos ticket? Run 'kinit'.")

            print(conn.result)

            sys.exit(1)


        print("[+] Successfully bound to Active Directory via Kerberos!\n")

        return conn

    except Exception as e:

        print(f"[-] Connection error: {e}")

        sys.exit(1)


def hunt_vulnerabilities(conn):

    """Iterates through our defined signatures and queries the domain."""

    print("==========================================")

    print("   ACTIVE DIRECTORY ATTACK SURFACE MAP    ")

    print("==========================================\n")


    for vuln_name, ldap_filter in VULNERABILITIES.items():

        print(f"[*] Hunting for: {vuln_name}")


        # Execute the Search

        conn.search(

            search_base=SEARCH_BASE,

            search_filter=ldap_filter,

            attributes=['sAMAccountName', 'description']

        )


        # Parse and Display Results

        if conn.entries:

            print(f"[!] Found {len(conn.entries)} vulnerable account(s):")

            for entry in conn.entries:

                username = entry.sAMAccountName.value

                desc = entry.description.value if entry.description else "No description"

                print(f"    -> {username} (Desc: {desc})")

        else:

            print("[+] Secure: No accounts found matching this signature.")

        print("-" * 40)


if __name__ == "__main__":

    # Ensure we have a valid connection before hunting

    ad_connection = connect_to_ad()

    hunt_vulnerabilities(ad_connection)

    ad_connection.unbind()


Run the following command from the NixOS Terminal to request an authentication ticket from our Kerberos


[itlabcenter@nixos:~]$ kinit administrator


In order for our Domain Controller to accept a connection over port 636 we need to setup a certificate

Run the following commands from the Domain Controllers PowerShell to configure a certificate for LDAPS


PS C:\Windows\System32> Install-WindowsFeature Adcs-Cert-Authority -IncludeManagementTools

PS C:\Windows\System32> Add-ADGroupMember -Identity "Enterprise Admins" -Members "ITLab_Center"

PS C:\Windows\System32> shutdown /r

PS C:\Windows\System32> Install-AdcsCertificationAuthority -CAType EnterpriseRootCa -CryptoProviderName "RSA#Microsoft Software Key Storage Provider" -KeyLength 2048 -HashAlgorithmName SHA256 -ValidityPeriod Years -ValidityPeriodUnits 5 -Force

PS C:\Windows\System32> shutdown /r

Run the following command from the NixOS Terminal to execute our Python script to hunt vulnerabilities


[itlabcenter@nixos:~]$ python3 mapper.py


Resulting Output:




To deeply understand what this script is doing, let's break it down into the core mechanisms it's using:



The GSSAPI Bind (Zero-Touch Authentication)

conn = Connection(server, authentication=SASL, sasl_mechanism=GSSAPI)

This is the payoff for all your NixOS configuration. Instead of hardcoding a username and password using

plaintext (which is a massive security flaw in its own right), this tells the ldap3 library to reach out

to the underlying OS, grab the Kerberos Ticket-Granting-Ticket (TGT) you generated with kinit, and present

it to the Domain Controller. It is silent, secure, and enterprise-grade



The LDAP Bitwise Filters

VULNERABILITIES = {

    "AS-REP Roasting (Pre-Auth Not Required)": "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))",

    "Kerberoasting (User Accounts with SPNs)": "(&(objectCategory=person)(objectClass=user)(servicePrincipalName=*)(!(sAMAccountName=krbtgt)))",

    "Passwords Set to Never Expire": "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536)),"

    "Unconstrained Delegation Enabled": "(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=524288))",

    "Password Not Required (Blank Passwords Allowed)": "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=32))",

    "Passwords Stored in Reversible Encryption": "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=128))"

}

Active Directory stores a massive amount of security data in a single integer attribute referred to as

userAccountControl (UAC). To find misconfigurations, we can't just look for a word; we have to use bitwise

matching (that crazy 1.2.840.113556.1.4.803 string is the LDAP Object Identifier for bitwise AND operator)

The flags (:=4194304, :=65536, etc.) isolate the bit flag for the vulnerabilities we are hunting for



Execution and Parsing

conn.search(

    search_base=SEARCH_BASE,

    search_filter=ldap_filter,

    attributes=['sAMAccountName', 'description']

)

The conn_search() method sends the filter to the DC. The data comes back as a list of entry objects. We

then iterate through conn.entries and extract the sAMAccountName (the login name) and the description

(attackers frequently find passwords lazily left in the description field).


9. Remediate Active Directory Vulnerabilities


The primary purpose of any vulnerability scanning activities is to improve security posture. Here we will

do so by remediating the vulnerabilities our Python script has uncovered, Open the PowerShell ISE as admin

Execute the following script from PowerShell ISE to remediate the vulnerabilities our mapper discovered


Import-Module ActiveDirectory

Write-Host "[*] Starting Active Directory Attack Surface Remediation..." -ForegroundColor Cyan


# 1. AS-REP Roasting (Require Pre-Authentication)

Write-Host "`n[*] Detecting & Fixing AS-REP Roasting Vulnerabilities..."

$asrepUsers = Get-ADUser -Filter 'DoesNotRequirePreAuth -eq $true'

ForEach ($user in $asrepUsers) {

    Set-ADAccountControl -Identity $user.sAMAccountName -DoesNotRequirePreAuth $false

    Write-Host "    -> Secured $($user.sAMAccountName): Pre-Authentication Enforced." -ForegroundColor Green

}


2. Passwords Set to Never Expire

Write-Host "`n[*] Detecting & Fixing Accounts with Non-Expiring Passwords..."

$neverExpire = Get-ADUser -LDAPFilter "(userAccountControl:1.2.840.113556.1.4.803:=65536)"

ForEach ($user in $neverExpire) {

    Set-ADUser -Identity $user.sAMAccountName -PasswordNeverExpires $false

    Write-Host "    -> Secured $($user.sAMAccountName): Expiration Policy Restored." -ForegroundColor Green

}


# 3. Unconstrained Delegation

Write-Host "`n[*] Detecting & Fixing Unconstrained Delegation on Computers..."

$unconstrained = Get-ADComputer -LDAPFilter "(userAccountControl:1.2.840.113556.1.4.803:=524288)"

ForEach ($comp in $unconstrained) {

    Set-ADComputer -Identity $comp.sAMAccountName -TrustedForDelegation $false

    Write-Host "    -> Secured $($comp.sAMAccountName): Delegation Disabled." -ForegroundColor Green

}


# 4. Password Not Required (Blank Passwords)

Write-Host "`n[*] Detecting & Fixing Accounts Allowing Blank Passwords..."

$blankPass = Get-ADUser -LDAPFilter "(userAccountControl:1.2.840:113556.1.4.803:=32)"

$temppass = ConvertTo-SecureString "T3MP_pa55w0rd!" -AsPlainText -Force

ForEach ($user in $blankPass) {

    Set-ADAccountPassword -Identity $user.sAMAccountName -NewPassword $temppass -Reset:$true

    Set-ADUser -Identity $user.sAMAccountName -PasswordNotRequired $false

    Write-Host "    -> Secured $($user.sAMAccountName): Password Requirement Enforced." -ForegroundColor Green

}


# 5. Reversible Encryption (Requires Password Reset)

Write-Host "`n[*] Detecting & Fixing Reversible Encryption..."

$reversible = Get-ADUser -LDAPFilter "(userAccountControl:1.2.840.113556.1.4.803:=128)"

$temppass = ConvertTo-SecureString "T3MP_pa55w0rd!" -AsPlainText -Force

ForEach ($user in $reversible) {

    Set-ADUser -Identity $user.sAMAccountName -AllowReversiblePasswordEncryption $false

    Set-ADAccountPassword -Identity $user.sAMAccountName -NewPassword $temppass -Reset:$true

    Write-Host "    -> Secured $($user.sAMAccountName): Reversible Encryption Disabled and NTDS Hash Overwritten." -ForegroundColor Green

}


# 6. Kerberoasting Mitigation

Write-Host "`n[*] Detecting & Fixing Kerberoastable Service Accounts..."

$kerberoastable = Get-ADUser -LDAPFilter "(&(servicePrincipalName=*)(!(sAMAccountName=krbtgt)))"

ForEach ($user in $kerberoastable) {

    $complexPass = ConvertTo-SecureString "SvcAcct!$([guid]::NewGuid().ToString())" -AsPlainText -Force

    Set-ADAccountPassword -Identity $user.sAMAccountName -NewPassword $complexPass -Reset:$true

    Set-ADUser -Identity $user.sAMAccountName -Clear ServicePrincipalName

    Write-Host "    -> Secured $($user.sAMAccountName): Injected 36+ Character Randomized Password." -ForegroundColor Green

}


Write-Host "`n[+] Remediation Sweep Complete." -ForegroundColor Cyan


Resulting Output:




Run the following command from the NixOS Terminal to execute our Python script to validate remediation


[itlabcenter@nixos:~]$ python3 mapper.py


Resulting Output:




We can see that after our remediation efforts, the vulnerabilities are no longer detected on our system

Congratulations! You have completed this guided lab on Python Scripting. You have briefly covered a lot

of Python: integers, strings, operators, slicing, lists, for loops, if/else causes, and even our own file

handling. I hope this lab inspires you to learn more Python syntax and start writing your own tools in

Python. Writing tools and automating tasks are the best ways to learn Python and grow as a professional