Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions winPEAS/winPEASexe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ The goal of this project is to search for possible **Privilege Escalation Paths*

New in this version:
- Detect potential GPO abuse by flagging writable SYSVOL paths for GPOs applied to the current host and by highlighting membership in the "Group Policy Creator Owners" group.
- Highlight disconnected high-privilege RDP sessions so you can plan LSASS/token theft when admins leave idle sessions (Ink Dragon-style escalation).


It should take only a **few seconds** to execute almost all the checks and **some seconds/minutes during the lasts checks searching for known filenames** that could contain passwords (the time depened on the number of files in your home folder). By default only **some** filenames that could contain credentials are searched, you can use the **searchall** parameter to search all the list (this could will add some minutes).
Expand Down
54 changes: 51 additions & 3 deletions winPEAS/winPEASexe/winPEAS/Checks/UserInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,63 @@ void PrintRdpSessions()
try
{
Beaprint.MainPrint("RDP Sessions");
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/credentials-mgmt/rdp-sessions", "Disconnected high-privilege RDP sessions keep reusable tokens inside LSASS.");
List<Dictionary<string, string>> rdp_sessions = UserInfoHelper.GetRDPSessions();
if (rdp_sessions.Count > 0)
{
string format = " {0,-10}{1,-15}{2,-15}{3,-25}{4,-10}{5}";
string header = string.Format(format, "SessID", "pSessionName", "pUserName", "pDomainName", "State", "SourceIP");
string format = " {0,-8}{1,-15}{2,-20}{3,-22}{4,-15}{5,-18}{6,-10}";
string header = string.Format(format, "SessID", "Session", "User", "Domain", "State", "SourceIP", "HighPriv");
Beaprint.GrayPrint(header);
var colors = ColorsU();
List<Dictionary<string, string>> flaggedSessions = new List<Dictionary<string, string>>();
foreach (Dictionary<string, string> rdpSes in rdp_sessions)
{
Beaprint.AnsiPrint(string.Format(format, rdpSes["SessionID"], rdpSes["pSessionName"], rdpSes["pUserName"], rdpSes["pDomainName"], rdpSes["State"], rdpSes["SourceIP"]), ColorsU());
rdpSes.TryGetValue("SessionID", out string sessionId);
rdpSes.TryGetValue("pSessionName", out string sessionName);
rdpSes.TryGetValue("pUserName", out string userName);
rdpSes.TryGetValue("pDomainName", out string domainName);
rdpSes.TryGetValue("State", out string state);
rdpSes.TryGetValue("SourceIP", out string sourceIp);

sessionId = sessionId ?? string.Empty;
sessionName = sessionName ?? string.Empty;
userName = userName ?? string.Empty;
domainName = domainName ?? string.Empty;
state = state ?? string.Empty;
sourceIp = sourceIp ?? string.Empty;

bool isHighPriv = UserInfoHelper.IsHighPrivilegeAccount(userName, domainName);
string highPrivLabel = isHighPriv ? "Yes" : "No";
rdpSes["HighPriv"] = highPrivLabel;

if (isHighPriv && string.Equals(state, "Disconnected", StringComparison.OrdinalIgnoreCase))
{
flaggedSessions.Add(rdpSes);
}

Beaprint.AnsiPrint(string.Format(format, sessionId, sessionName, userName, domainName, state, sourceIp, highPrivLabel), colors);
}

if (flaggedSessions.Count > 0)
{
Beaprint.BadPrint(" [!] Disconnected high-privilege RDP sessions detected. Their credentials/tokens stay in LSASS until the user signs out.");
foreach (Dictionary<string, string> session in flaggedSessions)
{
session.TryGetValue("pDomainName", out string flaggedDomain);
session.TryGetValue("pUserName", out string flaggedUser);
session.TryGetValue("SessionID", out string flaggedSessionId);
session.TryGetValue("SourceIP", out string flaggedIp);

flaggedDomain = flaggedDomain ?? string.Empty;
flaggedUser = flaggedUser ?? string.Empty;
flaggedSessionId = flaggedSessionId ?? string.Empty;
flaggedIp = flaggedIp ?? string.Empty;

string userDisplay = string.Format("{0}\\{1}", flaggedDomain, flaggedUser).Trim('\\');
string source = string.IsNullOrEmpty(flaggedIp) ? "local" : flaggedIp;
Beaprint.BadPrint(string.Format(" -> Session {0} ({1}) from {2}", flaggedSessionId, userDisplay, source));
}
Beaprint.LinkPrint("https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/credentials-mgmt/rdp-sessions", "Dump LSASS / steal tokens (e.g., comsvcs.dll, LsaLogonSessions, custom SSPs) to reuse those privileges.");
}
}
else
Expand Down
73 changes: 73 additions & 0 deletions winPEAS/winPEASexe/winPEAS/Info/UserInfo/UserInfoHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ namespace winPEAS.Info.UserInfo
{
class UserInfoHelper
{
private static readonly Dictionary<string, bool> _highPrivAccountCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private static readonly string[] _highPrivGroupIndicators = new string[]
{
"administrators",
"domain admins",
"enterprise admins",
"schema admins",
"server operators",
"account operators",
"backup operators",
"dnsadmins",
"hyper-v administrators"
};

// https://stackoverflow.com/questions/5247798/get-list-of-local-computer-usernames-in-windows


Expand Down Expand Up @@ -91,6 +105,65 @@ public static PrincipalContext GetPrincipalContext()
return oPrincipalContext;
}

public static bool IsHighPrivilegeAccount(string userName, string domain)
{
if (string.IsNullOrWhiteSpace(userName))
{
return false;
}

string cacheKey = ($"{domain}\\{userName}").Trim('\\');
if (_highPrivAccountCache.TryGetValue(cacheKey, out bool cached))
{
return cached;
}

bool isHighPriv = false;
try
{
string resolvedDomain = string.IsNullOrWhiteSpace(domain) ? Checks.Checks.CurrentUserDomainName : domain;
List<string> groups = User.GetUserGroups(userName, resolvedDomain);
foreach (string group in groups)
{
if (IsHighPrivilegeGroup(group))
{
isHighPriv = true;
break;
}
}
}
catch (Exception ex)
{
Beaprint.GrayPrint(string.Format(" [-] Unable to resolve groups for {0}\\{1}: {2}", domain, userName, ex.Message));
}

if (!isHighPriv)
{
isHighPriv = string.Equals(userName, "administrator", StringComparison.OrdinalIgnoreCase) || userName.StartsWith("admin", StringComparison.OrdinalIgnoreCase);
}

_highPrivAccountCache[cacheKey] = isHighPriv;
return isHighPriv;
}

private static bool IsHighPrivilegeGroup(string groupName)
{
if (string.IsNullOrWhiteSpace(groupName))
{
return false;
}

foreach (string indicator in _highPrivGroupIndicators)
{
if (groupName.IndexOf(indicator, StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
}

return false;
}

//From Seatbelt
public enum WTS_CONNECTSTATE_CLASS
{
Expand Down
Loading