├── LICENSE ├── README.md └── scan.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fox-IT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2019-1040 scanner 2 | 3 | Checks for CVE-2019-1040 vulnerability over SMB. 4 | The script will establish a connection to the target host(s) and send 5 | an invalid NTLM authentication. If this is accepted, the host is vulnerable to 6 | CVE-2019-1040 and you can execute the [MIC Remove attack](https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/) with ntlmrelayx. 7 | 8 | Note that this does not generate failed login attempts as the login information itself is valid, it is just the NTLM message integrity code that is absent, which is why the authentication is refused without increasing the badpwdcount. 9 | 10 | # Usage 11 | The script requires a recent impacket version. Should work with both python 2 and 3 (Python 3 requires you to use impacket from git). 12 | 13 | ``` 14 | [*] CVE-2019-1040 scanner by @_dirkjan / Fox-IT - Based on impacket by SecureAuth 15 | usage: scan.py [-h] [-target-file file] [-port [destination port]] 16 | [-hashes LMHASH:NTHASH] 17 | target 18 | 19 | CVE-2019-1040 scanner - Connects over SMB and attempts to authenticate with 20 | invalid NTLM packets. If accepted, target is vulnerable to MIC remove attack 21 | 22 | positional arguments: 23 | target [[domain/]username[:password]@] 24 | 25 | optional arguments: 26 | -h, --help show this help message and exit 27 | 28 | connection: 29 | -target-file file Use the targets in the specified file instead of the 30 | one on the command line (you must still specify 31 | something as target name) 32 | -port [destination port] 33 | Destination port to connect to SMB Server 34 | 35 | authentication: 36 | -hashes LMHASH:NTHASH 37 | NTLM hashes, format is LMHASH:NTHASH 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #################### 3 | # 4 | # Copyright (c) 2019 Dirk-jan Mollema / Fox-IT (@_dirkjan) 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | # Checks for CVE-2019-1040 vulnerability over SMB. 25 | # The script will establish a connection to the target host(s) and send 26 | # an invalid NTLM authentication. If this is accepted, the host is vulnerable to 27 | # CVE-2019-1040 and you can execute the MIC Remove attack with ntlmrelayx. 28 | # 29 | # See https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/ 30 | # for more info. 31 | # 32 | # Author: 33 | # Dirk-jan Mollema (@_dirkjan) 34 | # dlive (@D1iv3) 35 | # 36 | #################### 37 | import sys 38 | import logging 39 | import argparse 40 | import codecs 41 | import calendar 42 | import struct 43 | import time 44 | import random 45 | import string 46 | from impacket import version 47 | from impacket.examples.logger import ImpacketFormatter 48 | from impacket.smbconnection import SMBConnection, SessionError 49 | from impacket.smb3structs import * 50 | from impacket import ntlm 51 | from impacket.ntlm import AV_PAIRS, NTLMSSP_AV_TIME, NTLMSSP_AV_FLAGS, NTOWFv2, NTLMSSP_AV_TARGET_NAME, NTLMSSP_AV_HOSTNAME,USE_NTLMv2, hmac_md5 52 | 53 | def parse_creds(target): 54 | creds, remote_name = target.rsplit('@', 1) 55 | if ':' in creds: 56 | colon_split = creds.split(':', 1) # dom/user, pass 57 | password = colon_split[1] 58 | creds = colon_split[0] 59 | else: 60 | password = '' 61 | 62 | if '/' in creds: 63 | slash_split = creds.split("/", 1) 64 | dom = slash_split[0].strip() 65 | user = slash_split[1].strip() 66 | else: 67 | dom = '' 68 | user = creds 69 | 70 | return dom, user, password, remote_name 71 | 72 | 73 | def mod_getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash = '', use_ntlmv2 = USE_NTLMv2): 74 | 75 | # Safety check in case somebody sent password = None.. That's not allowed. Setting it to '' and hope for the best. 76 | if password is None: 77 | password = '' 78 | 79 | # Let's do some encoding checks before moving on. Kind of dirty, but found effective when dealing with 80 | # international characters. 81 | import sys 82 | encoding = sys.getfilesystemencoding() 83 | if encoding is not None: 84 | try: 85 | user.encode('utf-16le') 86 | except: 87 | user = user.decode(encoding) 88 | try: 89 | password.encode('utf-16le') 90 | except: 91 | password = password.decode(encoding) 92 | try: 93 | domain.encode('utf-16le') 94 | except: 95 | domain = user.decode(encoding) 96 | 97 | ntlmChallenge = ntlm.NTLMAuthChallenge(type2) 98 | 99 | # Let's start with the original flags sent in the type1 message 100 | responseFlags = type1['flags'] 101 | 102 | # Token received and parsed. Depending on the authentication 103 | # method we will create a valid ChallengeResponse 104 | ntlmChallengeResponse = ntlm.NTLMAuthChallengeResponse(user, password, ntlmChallenge['challenge']) 105 | 106 | clientChallenge = ntlm.b("".join([random.choice(string.digits+string.ascii_letters) for _ in range(8)])) 107 | 108 | serverName = ntlmChallenge['TargetInfoFields'] 109 | 110 | ntResponse, lmResponse, sessionBaseKey = ntlm.computeResponse(ntlmChallenge['flags'], ntlmChallenge['challenge'], 111 | clientChallenge, serverName, domain, user, password, 112 | lmhash, nthash, use_ntlmv2) 113 | 114 | # Let's check the return flags 115 | if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) == 0: 116 | # No extended session security, taking it out 117 | responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY 118 | if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_128 ) == 0: 119 | # No support for 128 key len, taking it out 120 | responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_128 121 | if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH) == 0: 122 | # No key exchange supported, taking it out 123 | responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH 124 | 125 | # drop the mic need to unset these flags 126 | if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_SEAL) != 0: 127 | responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SEAL 128 | if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_SIGN) != 0: 129 | responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SIGN 130 | if (ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN) != 0: 131 | responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN 132 | 133 | 134 | keyExchangeKey = ntlm.KXKEY(ntlmChallenge['flags'], sessionBaseKey, lmResponse, ntlmChallenge['challenge'], password, 135 | lmhash, nthash, use_ntlmv2) 136 | 137 | # Special case for anonymous login 138 | if user == '' and password == '' and lmhash == '' and nthash == '': 139 | keyExchangeKey = b'\x00'*16 140 | 141 | if ntlmChallenge['flags'] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH: 142 | exportedSessionKey = ntlm.b("".join([random.choice(string.digits+string.ascii_letters) for _ in range(16)])) 143 | encryptedRandomSessionKey = ntlm.generateEncryptedSessionKey(keyExchangeKey, exportedSessionKey) 144 | else: 145 | encryptedRandomSessionKey = None 146 | exportedSessionKey = keyExchangeKey 147 | 148 | ntlmChallengeResponse['flags'] = responseFlags 149 | ntlmChallengeResponse['domain_name'] = domain.encode('utf-16le') 150 | ntlmChallengeResponse['host_name'] = type1.getWorkstation().encode('utf-16le') 151 | if lmResponse == '': 152 | ntlmChallengeResponse['lanman'] = b'\x00' 153 | else: 154 | ntlmChallengeResponse['lanman'] = lmResponse 155 | ntlmChallengeResponse['ntlm'] = ntResponse 156 | if encryptedRandomSessionKey is not None: 157 | ntlmChallengeResponse['session_key'] = encryptedRandomSessionKey 158 | 159 | return ntlmChallengeResponse, exportedSessionKey 160 | 161 | 162 | # Slightly modified version of impackets computeResponseNTLMv2 163 | def mod_computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash='', 164 | nthash='', use_ntlmv2=USE_NTLMv2, check=False): 165 | 166 | responseServerVersion = b'\x01' 167 | hiResponseServerVersion = b'\x01' 168 | responseKeyNT = NTOWFv2(user, password, domain, nthash) 169 | 170 | av_pairs = AV_PAIRS(serverName) 171 | av_pairs[NTLMSSP_AV_TARGET_NAME] = 'cifs/'.encode('utf-16le') + av_pairs[NTLMSSP_AV_HOSTNAME][1] 172 | if av_pairs[NTLMSSP_AV_TIME] is not None: 173 | aTime = av_pairs[NTLMSSP_AV_TIME][1] 174 | else: 175 | aTime = struct.pack('