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/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 e9c2a3f..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,6 +300,7 @@ public static async Task EnumerateAsync(string[] args) }; + startSelection: using (var httpClient = new HttpClient(httpClientHandler)) { var gitHubDict = new Dictionary() { }; @@ -315,6 +314,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,13 +323,26 @@ 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) { 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); @@ -352,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); 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 @@ - +