Skip to content

Commit fc71ef8

Browse files
authored
Merge pull request Pennyw0rth#783 from Dfte/qwinsta_grep
Allows --qwinsta to filter for a specific user
2 parents 7ab386f + b7d8eeb commit fc71ef8

2 files changed

Lines changed: 45 additions & 28 deletions

File tree

nxc/protocols/smb.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ def is_host_dc(self):
688688
from impacket.dcerpc.v5 import nrpc, epm
689689

690690
self.logger.debug("Performing authentication attempts...")
691-
691+
692692
# First check if port 135 is open
693693
if self._is_port_open(135):
694694
self.logger.debug("Port 135 is open, attempting MSRPC connection...")
@@ -964,7 +964,7 @@ def enumerate_sessions_info(self, sessions):
964964
sessions[SessionId]["DisconnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DisconnectTime"]
965965
sessions[SessionId]["LogonTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LogonTime"]
966966
sessions[SessionId]["LastInputTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LastInputTime"]
967-
967+
968968
try:
969969
with TSTS.RCMPublic(self.conn, self.host, self.kerberos) as rcm:
970970
for SessionId in sessions:
@@ -1011,33 +1011,32 @@ def qwinsta(self):
10111011
"WTS_SESSIONSTATE_LOCK": "Locked",
10121012
"WTS_SESSIONSTATE_UNLOCK": "Unlocked",
10131013
}
1014+
10141015
sessions = self.get_session_list()
1015-
if not len(sessions):
1016+
if not sessions:
10161017
return
1018+
10171019
self.enumerate_sessions_info(sessions)
10181020

1021+
# Calculate max lengths for formatting
10191022
maxSessionNameLen = max(len(sessions[i]["SessionName"]) + 1 for i in sessions)
1020-
maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME") + 1
1023+
maxSessionNameLen = max(maxSessionNameLen, len("SESSIONNAME") + 1)
10211024
maxUsernameLen = max(len(sessions[i]["Username"] + sessions[i]["Domain"]) + 1 for i in sessions) + 1
1022-
maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username") + 1
1025+
maxUsernameLen = max(maxUsernameLen, len("USERNAME") + 1)
10231026
maxIdLen = max(len(str(i)) for i in sessions)
1024-
maxIdLen = maxIdLen if len("ID") < maxIdLen else len("ID") + 1
1027+
maxIdLen = max(maxIdLen, len("ID") + 1)
10251028
maxStateLen = max(len(sessions[i]["state"]) + 1 for i in sessions)
1026-
maxStateLen = maxStateLen if len("STATE") < maxStateLen else len("STATE") + 1
1027-
maxRemoteIp = max(len(sessions[i]["RemoteIp"]) + 1 for i in sessions)
1028-
maxRemoteIp = maxRemoteIp if len("RemoteAddress") < maxRemoteIp else len("RemoteAddress") + 1
1029-
maxClientName = max(len(sessions[i]["ClientName"]) + 1 for i in sessions)
1030-
maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName") + 1
1031-
template = ("{SESSIONNAME: <%d} " # noqa: UP031
1032-
"{USERNAME: <%d} "
1033-
"{ID: <%d} "
1029+
maxStateLen = max(maxStateLen, len("STATE") + 1)
1030+
1031+
# Create the template for formatting
1032+
template = (f"{{SESSIONNAME: <{maxSessionNameLen}}} "
1033+
f"{{USERNAME: <{maxUsernameLen}}} "
1034+
f"{{ID: <{maxIdLen}}} "
10341035
"{IPv4: <16} "
1035-
"{STATE: <%d} "
1036+
f"{{STATE: <{maxStateLen}}} "
10361037
"{DSTATE: <9} "
10371038
"{CONNTIME: <20} "
1038-
"{DISCTIME: <20} ") % (maxSessionNameLen, maxUsernameLen, maxIdLen, maxStateLen)
1039-
1040-
result = []
1039+
"{DISCTIME: <20} ")
10411040
header = template.format(
10421041
SESSIONNAME="SESSIONNAME",
10431042
USERNAME="USERNAME",
@@ -1048,7 +1047,6 @@ def qwinsta(self):
10481047
CONNTIME="ConnectTime",
10491048
DISCTIME="DisconnectTime",
10501049
)
1051-
10521050
header2 = template.replace(" <", "=<").format(
10531051
SESSIONNAME="",
10541052
USERNAME="",
@@ -1059,30 +1057,49 @@ def qwinsta(self):
10591057
CONNTIME="",
10601058
DISCTIME="",
10611059
)
1062-
result.extend((header, header2))
1060+
result = [header, header2]
1061+
1062+
# Check if we need to filter for usernames
1063+
usernames = None
1064+
if self.args.qwinsta:
1065+
arg = self.args.qwinsta
1066+
if os.path.isfile(arg):
1067+
with open(arg) as f:
1068+
usernames = [line.strip().lower() for line in f if line.strip()]
1069+
else:
1070+
usernames = [arg.lower()]
10631071

10641072
for i in sessions:
1073+
username = sessions[i]["Username"]
1074+
domain = sessions[i]["Domain"]
1075+
user_full = f"{domain}\\{username}" if username else ""
1076+
1077+
# If usernames are provided, filter them
1078+
if usernames and username.lower() not in usernames:
1079+
continue
1080+
10651081
connectTime = sessions[i]["ConnectTime"]
10661082
connectTime = connectTime.strftime(r"%Y/%m/%d %H:%M:%S") if connectTime.year > 1601 else "None"
10671083

10681084
disconnectTime = sessions[i]["DisconnectTime"]
10691085
disconnectTime = disconnectTime.strftime(r"%Y/%m/%d %H:%M:%S") if disconnectTime.year > 1601 else "None"
1070-
userName = sessions[i]["Domain"] + "\\" + sessions[i]["Username"] if len(sessions[i]["Username"]) else ""
10711086

1072-
result.append(template.format(
1087+
row = template.format(
10731088
SESSIONNAME=sessions[i]["SessionName"],
1074-
USERNAME=userName,
1089+
USERNAME=user_full,
10751090
ID=i,
10761091
IPv4=sessions[i]["RemoteIp"],
10771092
STATE=sessions[i]["state"],
10781093
DSTATE=desktop_states[sessions[i]["flags"]],
10791094
CONNTIME=connectTime,
10801095
DISCTIME=disconnectTime,
1081-
))
1096+
)
1097+
result.append(row)
10821098

1083-
self.logger.success("Enumerated qwinsta sessions")
1084-
for row in result:
1085-
self.logger.highlight(row)
1099+
if len(result) > 2:
1100+
self.logger.success("Enumerated qwinsta sessions")
1101+
for row in result:
1102+
self.logger.highlight(row)
10861103

10871104
@requires_admin
10881105
def tasklist(self):

nxc/protocols/smb/proto_args.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def proto_args(parser, parents):
5353
mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="Enumerate local groups, if a group is specified then its members are Enumerated")
5454
mapping_enum_group.add_argument("--pass-pol", action="store_true", help="dump password policy")
5555
mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="Enumerate users by bruteforcing RIDs")
56-
mapping_enum_group.add_argument("--qwinsta", action="store_true", help="Enumerate RDP connections")
56+
mapping_enum_group.add_argument("--qwinsta", type=str, nargs="?", const="", help="Enumerate user sessions. If a username is given, filter for it; if a file is given, filter for listed usernames. If no value is given, list all.")
5757
mapping_enum_group.add_argument("--tasklist", type=str, nargs="?", const=True, help="Enumerate running processes and filter for the specified one if specified")
5858
mapping_enum_group.add_argument("--taskkill", type=str, help="Kills a specific PID or a proces name's PID's")
5959

0 commit comments

Comments
 (0)