From 71bfe3e0be6725dfca32838ca1a7cca9d0c31911 Mon Sep 17 00:00:00 2001 From: Melvin Langvik Date: Tue, 11 Apr 2023 13:35:19 +0200 Subject: [PATCH 1/2] Added the email format j.smith@domain.com as requested in issue #25. Added error handling for email format selection Updated nuget packages --- README.md | 8 ++++---- .../TeamFiltration/Modules/Enumerate.cs | 16 +++++++++++++++- .../TeamFiltration/TeamFiltration.csproj | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 17d6b25..26f69be 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ See the [TeamFiltration](https://github.com/Flangvik/TeamFiltration/wiki/TeamFil This tool has been used internally since January 2021 and was publicly released in my talk "Taking a Dumb In The Cloud" during DefCON30. ## Download -[You can download the latest precompiled release for Linux, Windows and MacOSX X64 ](https://github.com/Flangvik/TeamFiltration/releases/latest) +[You can download the latest precompiled release for Linux, Windows and MacOS ](https://github.com/Flangvik/TeamFiltration/releases/latest) -**The releases are precompiled into a single application-dependent binary. The size go up, but you do not need DotNetCore or any other dependencies to run them.** +**The releases are precompiled into a single application-dependent binary. The size go up, but you do not need NET or any other dependencies to run them.** ## Usage @@ -40,7 +40,7 @@ This tool has been used internally since January 2021 and was publicly released └╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╝ ╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝╝ -[] TeamFiltration V3.5.1 PUBLIC, created by @Flangvik at @TrustedSec +[❤] TeamFiltration V3.5.1 PUBLIC, created by @Flangvik at @TrustedSec [+] Args parsed Usage: @@ -125,6 +125,6 @@ Usage: - [GitHub - KoenZomers/OneDriveAPI: API in .NET to communicate with OneDrive Personal and OneDrive for Business](https://github.com/KoenZomers/OneDriveAPI) - [Research into Undocumented Behavior of Azure AD Refresh Tokens ](https://github.com/secureworks/family-of-client-ids-research) - [WS API Gateway management tool for creating on the fly HTTP pass-through proxies for unique IP rotation](https://github.com/ustayready/fireprox) -- Credits to [Ryan] (https://twitter.com/detectdotdev) for validating and discussing my observations / questions! +- Credits to [Ryan](https://twitter.com/detectdotdev) for validating and discussing my observations / questions! - The entire [TrustedSec](https://TrustedSec.com) team for helping me polish this tool! diff --git a/TeamFiltration/TeamFiltration/Modules/Enumerate.cs b/TeamFiltration/TeamFiltration/Modules/Enumerate.cs index e9c2a3f..bc01012 100644 --- a/TeamFiltration/TeamFiltration/Modules/Enumerate.cs +++ b/TeamFiltration/TeamFiltration/Modules/Enumerate.cs @@ -302,6 +302,7 @@ public static async Task EnumerateAsync(string[] args) }; + startSelection: using (var httpClient = new HttpClient(httpClientHandler)) { var gitHubDict = new Dictionary() { }; @@ -315,6 +316,7 @@ public static async Task EnumerateAsync(string[] args) gitHubDict.Add(7, "https://raw.githubusercontent.com/Flangvik/statistically-likely-usernames/master/smith.txt"); gitHubDict.Add(8, "https://raw.githubusercontent.com/Flangvik/statistically-likely-usernames/master/smithj.txt"); gitHubDict.Add(9, "https://raw.githubusercontent.com/Flangvik/statistically-likely-usernames/master/john_smith.txt"); + gitHubDict.Add(10, "https://raw.githubusercontent.com/Flangvik/statistically-likely-usernames/master/j.smith.txt"); foreach (var usernameDict in gitHubDict) @@ -323,7 +325,19 @@ public static async Task EnumerateAsync(string[] args) } Console.WriteLine(); Console.Write("[?] Select an email format #> "); - var selection = Convert.ToInt32(Console.ReadLine()); + int selection = 0; + try + { + selection = Convert.ToInt32(Console.ReadLine()); + } + catch (Exception ex) + { + + Console.WriteLine("[!] Failed to parse input / selection, try again!"); + Console.WriteLine(""); + goto startSelection; + } + var userListReq = await httpClient.PollyGetAsync(gitHubDict.GetValueOrDefault(selection)); if (userListReq.IsSuccessStatusCode) { diff --git a/TeamFiltration/TeamFiltration/TeamFiltration.csproj b/TeamFiltration/TeamFiltration/TeamFiltration.csproj index bf2aaf1..0b67fc8 100644 --- a/TeamFiltration/TeamFiltration/TeamFiltration.csproj +++ b/TeamFiltration/TeamFiltration/TeamFiltration.csproj @@ -34,7 +34,7 @@ - + @@ -43,7 +43,7 @@ - + From 4d51079ec55324bba4b444b01fa60806dbac543b Mon Sep 17 00:00:00 2001 From: Melvin Langvik Date: Wed, 12 Apr 2023 16:56:15 +0200 Subject: [PATCH 2/2] Added the GetPresence check to Teams Account Enumeration mode, fetched and stores the OutOfOffice message in the database when found Changed the ValidAccount database structure to account for this changes Changed the CSV generator separator from , to ; --- .../TeamFiltration/Handlers/TeamsHandler.cs | 48 ++++++++++++++----- .../Models/TeamFiltration/ValidAccount.cs | 2 + .../Models/Teams/GetPresenceResp.cs | 40 ++++++++++++++++ .../TeamFiltration/Modules/Database.cs | 2 +- .../TeamFiltration/Modules/Enumerate.cs | 25 +++++----- 5 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 TeamFiltration/TeamFiltration/Models/Teams/GetPresenceResp.cs diff --git a/TeamFiltration/TeamFiltration/Handlers/TeamsHandler.cs b/TeamFiltration/TeamFiltration/Handlers/TeamsHandler.cs index fa82c2d..969d71f 100644 --- a/TeamFiltration/TeamFiltration/Handlers/TeamsHandler.cs +++ b/TeamFiltration/TeamFiltration/Handlers/TeamsHandler.cs @@ -50,7 +50,7 @@ public TeamsHandler(BearerTokenResp getBearToken, GlobalArgumentsHandler teamFil _teamsClient = new HttpClient(httpClientHandler); _teamsClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {getBearToken.access_token}"); - + _teamsClient.DefaultRequestHeaders.Add("User-Agent", teamFiltrationConfig.TeamFiltrationConfig.UserAgent); _teamsClient.DefaultRequestHeaders.Add("x-ms-client-caller", "x-ms-client-caller"); _teamsClient.DefaultRequestHeaders.Add("x-ms-client-version", "27/1.0.0.2021011237"); @@ -130,7 +130,7 @@ public async Task GetChatLogs(Conversation chatResp) public async Task GetThreads(string meetingId) { - + var getConversationUrl = $"https://{TeamsRegion}.ng.msg.teams.microsoft.com/v1/threads/{meetingId}?view=msnp24Equivalent"; var getConversationsReq = await _teamsClient.PollyGetAsync(getConversationUrl); var getConversationResp = await getConversationsReq.Content.ReadAsStringAsync(); @@ -141,7 +141,7 @@ public async Task GetThreads(string meetingId) public async Task GetConversations() { - + var getConversationUrl = $"https://{TeamsRegion}.ng.msg.teams.microsoft.com/v1/users/ME/conversations"; var getConversationsReq = await _teamsClient.PollyGetAsync(getConversationUrl); var getConversationResp = await getConversationsReq.Content.ReadAsStringAsync(); @@ -158,9 +158,9 @@ public async Task GetWorkingWithList(string tenantId) return workingWithDataResp; } - public async Task<(bool isValid, string objectId, TeamsExtSearchRep responseObject)> EnumUser(string username, string enumUserUrl) + public async Task<(bool isValid, string objectId, TeamsExtSearchRep responseObject, Outofofficenote Outofofficenote)> EnumUser(string username, string enumUserUrl) { - + Outofofficenote Outofofficenote = new Outofofficenote() { }; int failedCount = 0; failedResp: @@ -169,7 +169,6 @@ public async Task GetWorkingWithList(string tenantId) var enumUserReq = await _teamsClient.PollyGetAsync(enumUserUrl + $"{TeamsRegion}/beta/users/{username}/externalsearchv3"); - if (enumUserReq.IsSuccessStatusCode) { @@ -200,27 +199,54 @@ public async Task GetWorkingWithList(string tenantId) && responeObject.FirstOrDefault().userPrincipalName.ToLower().Equals(username.ToLower()) ) { - return (true, responeObject.FirstOrDefault().objectId, responeObject.FirstOrDefault()); + + //Check the user presence + HttpResponseMessage getUserPresence = await _teamsClient.PollyPostAsync( + $"https://presence.teams.microsoft.com/v1/presence/getpresence/", + + new StringContent( + "[{ \"mri\":\"" + responeObject.FirstOrDefault().mri + "\"}]" + , Encoding.UTF8 + , "application/json" + ) + ); + + var getPresenceObject = JsonConvert.DeserializeObject>(await getUserPresence.Content.ReadAsStringAsync()); + + try + { + if (getPresenceObject.FirstOrDefault()?.presence?.calendarData?.isOutOfOffice != null) + { + Outofofficenote = getPresenceObject.FirstOrDefault()?.presence?.calendarData.outOfOfficeNote; + } + } + catch (Exception ex) + { + + + } + + return (true, responeObject.FirstOrDefault().objectId, responeObject.FirstOrDefault(), Outofofficenote); } } } - return (false, "", null); + return (false, "", null, null); } else if (enumUserReq.StatusCode.Equals(HttpStatusCode.Forbidden)) { //If we get the forbidden error response, we can assume it's valid! - return (true, Guid.NewGuid().ToString(), null); + return (true, Guid.NewGuid().ToString(), null, null); } else if (enumUserReq.StatusCode.Equals(HttpStatusCode.InternalServerError)) { failedCount++; if (failedCount > 2) - return (false, "", null); + return (false, "", null, null); else goto failedResp; } - return (false, "", null); + return (false, "", null, null); } diff --git a/TeamFiltration/TeamFiltration/Models/TeamFiltration/ValidAccount.cs b/TeamFiltration/TeamFiltration/Models/TeamFiltration/ValidAccount.cs index 0163505..48d1991 100644 --- a/TeamFiltration/TeamFiltration/Models/TeamFiltration/ValidAccount.cs +++ b/TeamFiltration/TeamFiltration/Models/TeamFiltration/ValidAccount.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Text; +using TeamFiltration.Models.Teams; namespace TeamFiltration.Models.TeamFiltration { @@ -17,5 +18,6 @@ public ValidAccount() public string Username { get; set; } public string objectId { get; set; } public string DisplayName { get; set; } + public string OutOfOfficeMessage { get; set; } } } diff --git a/TeamFiltration/TeamFiltration/Models/Teams/GetPresenceResp.cs b/TeamFiltration/TeamFiltration/Models/Teams/GetPresenceResp.cs new file mode 100644 index 0000000..6a8ffb9 --- /dev/null +++ b/TeamFiltration/TeamFiltration/Models/Teams/GetPresenceResp.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TeamFiltration.Models.Teams +{ + public class GetPresenceResp + { + public string mri { get; set; } + public Presence presence { get; set; } + public bool etagMatch { get; set; } + public string etag { get; set; } + public int status { get; set; } + } + + public class Presence + { + public string sourceNetwork { get; set; } + public Calendardata calendarData { get; set; } + public object[] capabilities { get; set; } + public string availability { get; set; } + public string activity { get; set; } + public string deviceType { get; set; } + } + + public class Calendardata + { + public Outofofficenote outOfOfficeNote { get; set; } + public bool isOutOfOffice { get; set; } + } + + public class Outofofficenote + { + public string message { get; set; } + public DateTime publishTime { get; set; } + public DateTime expiry { get; set; } + } +} diff --git a/TeamFiltration/TeamFiltration/Modules/Database.cs b/TeamFiltration/TeamFiltration/Modules/Database.cs index 482309c..9f8b08a 100644 --- a/TeamFiltration/TeamFiltration/Modules/Database.cs +++ b/TeamFiltration/TeamFiltration/Modules/Database.cs @@ -125,7 +125,7 @@ public static void DatabaseStart(string[] args) //Format based on o if (selection.Contains("csv")) - formattedDataOut = ToCsv(",", dataOut); + formattedDataOut = ToCsv(";", dataOut); if (selection.Contains("json")) formattedDataOut = JsonConvert.SerializeObject(dataOut, Formatting.Indented); diff --git a/TeamFiltration/TeamFiltration/Modules/Enumerate.cs b/TeamFiltration/TeamFiltration/Modules/Enumerate.cs index bc01012..b4aa253 100644 --- a/TeamFiltration/TeamFiltration/Modules/Enumerate.cs +++ b/TeamFiltration/TeamFiltration/Modules/Enumerate.cs @@ -71,7 +71,10 @@ public static async Task ValidUserWrapperTeams(TeamsHandler teamsHandler, //check list and add if (!_teamsObjectIds.Contains(validUser.objectId)) { - _databaseHandle.WriteLog(new Log("ENUM", $"{username} valid!", "")); + if (validUser.Outofofficenote != null) + _databaseHandle.WriteLog(new Log("ENUM", $"{username} valid (OutOfOffice message found)!", "")); + else + _databaseHandle.WriteLog(new Log("ENUM", $"{username} valid!", "")); try { @@ -80,7 +83,8 @@ public static async Task ValidUserWrapperTeams(TeamsHandler teamsHandler, Username = username, Id = Helpers.Generic.StringToGUID(username).ToString(), objectId = validUser.objectId, - DisplayName = (validUser.responseObject != null) ? validUser.responseObject?.displayName : "" + DisplayName = (validUser.responseObject != null) ? validUser.responseObject?.displayName : "", + OutOfOfficeMessage = (validUser.Outofofficenote != null) ? validUser.Outofofficenote.message : "", } @@ -115,13 +119,7 @@ public static async Task ValidUserWrapperTeams(TeamsHandler teamsHandler, //LiteDB needs to fix their crap } } - /* - * There is a "bug" in litedb that makes it unable to handle more then 300/s transactions a second - else if (!validUser.isValid) - { - //User is not valid, let's note that down - _databaseHandle.WriteInvalidAcc(new ValidAccount() { Username = username, Id = Helpers.Generic.StringToGUID(username).ToString(), objectId = validUser.objectId }); - }*/ + return false; } catch (Exception ex) @@ -302,7 +300,7 @@ public static async Task EnumerateAsync(string[] args) }; - startSelection: + startSelection: using (var httpClient = new HttpClient(httpClientHandler)) { var gitHubDict = new Dictionary() { }; @@ -337,13 +335,14 @@ public static async Task EnumerateAsync(string[] args) Console.WriteLine(""); goto startSelection; } - + var userListReq = await httpClient.PollyGetAsync(gitHubDict.GetValueOrDefault(selection)); if (userListReq.IsSuccessStatusCode) { var userListContent = await userListReq.Content.ReadAsStringAsync(); userListData = (userListContent).Split("\n").Where(x => !string.IsNullOrEmpty(x)).Select(x => x + $"@{domain}").ToArray(); - }else + } + else { Console.WriteLine("[!] Failed to download statistically-likely-usernames from Github!"); Environment.Exit(0); @@ -366,7 +365,7 @@ public static async Task EnumerateAsync(string[] args) userListData = userListData.Except(currentValidAccounts).ToArray(); userListData = userListData.Except(currentInvalidAccounts).ToArray(); - if(userListData.Count() == 0) + if (userListData.Count() == 0) { _databaseHandle.WriteLog(new Log("ENUM", $"No valid accounts left after filters applied, exiting..", "")); Environment.Exit(0);