├── LICENSE ├── README.md └── raindance.ps1 /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, True Demon 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RainDance 2 | > A toolkit for enumerating and collecting information from Office 365 3 | 4 | ``` 5 | \ _( )_ \ \ _( )_ \ \ 6 | _( )_ _( )_ ( ) \ \ 7 | (_________) \ _( )_(_ _) 8 | \ \ \ (_________)________) \ \ \ 9 | \ \ \ \ \ \(__________) \ 10 | \ \ \ \ \ \ \ \ \ 11 | \ . \ \ \ \ \ \ \ 12 | \ \_O/ \ \ , \ \ \ \ 13 | \ \ \ \ / \ \O_ 14 | /\_ \ \_\ ,/\/ \ 15 | \ , \ \ \ / \ \ 16 | ___ \ __ /O\ \ \ \ 17 | | _ \ __ _(_)_ __ | _ \ __ _ _ __ ___ ___ 18 | | |_) / _ | | '_ \| | | |/ _ | '_ \ / __/ _ \ 19 | | _ < (_| | | | | | |_| | (_| | | | | (_| __/ 20 | |_| \_\__,_|_|_| |_|____/ \__,_|_| |_|\___\___| 21 | - Office 365 Info-Gathering Toolkit 22 | ``` 23 | 24 | ## Latest Updates 25 | Sorry for the delay in updates. I've been working on a cross-platform/python implementation to replace this, but it's slow going as I am doing some cert-chasing and swamped with other projects. There are some tentatively tested changes in dev, but they haven't been thoroughly put through the wringer yet. Mileage may vary. Thanks for the support :+1: 26 | 27 | ## Description 28 | 29 | Raindance uses built-in powershell modules, namely from the MSOnline & AzureAD powershell modules to log into Office 365 tenants with 30 | legitimate credentials and pulls out the list of users, their mailing groups and distros, roles/permissions, and identify administrators 31 | in the tenant. This tool is intended to be used as an attack tool to assist penetration testers in enumerating users and select targets 32 | for offensive engagements. 33 | 34 | ## BSides Talk 35 | I was given the opportunity to speak at BSides about my findings associated with my research into Office 365 that led to the development of Raindance. This video goes into some additional detail of how to use the tool effectively, along with plausible scenarios in which it can be useful. If you are interested in that talk, you can find it here.

36 | 37 | [![Raindance: Raining Recon from the Microsoft Cloud](https://i.ytimg.com/vi/VHPZ2YU351M/hqdefault.jpg?sqp=-oaymwEXCPYBEIoBSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCbfQmSWN0caw5mYY2FvoxLqNbnvg)](https://www.youtube.com/watch?v=VHPZ2YU351M) 38 | 39 | ## Features 40 | * Enumerates domain information within O365 41 | * Get the full list of users, including disabled accounts 42 | * Get a list of the mailing/distribution groups in the tenant 43 | * Identify administrative users and highlight Global Administrators (Company Admins) 44 | 45 | ### In the works 46 | * Support for Exchange Server & Office API login 47 | * Search and download emails (with administrator impersonation) 48 | * Automated password searcher (dig through mail & sharepoint for indicators of plaintext passwords) 49 | * Upload/Download files to/from Sharepoint 50 | * Malicious modification of Sharepoint/OneDrive files 51 | * Remote deployment over psexec 52 | 53 | ## Installation & Running 54 | Raindance runs like a powershell module, and does not require any installation. Simply clone it to a directory, and import as a 55 | powershell module to gain access to its functions. It is recommended to run as administrator the first time in order to enable 56 | it to install the necessary dependencies, or you may do so manually. 57 | 58 | ``` 59 | # Open a Powershell Command Window as administrator 60 | # Ensure you have all the necessary dependencies for PowerShell 61 | Set-ExecutionPolicy RemoteSigned 62 | 63 | # Install Microsoft Online Services (office 365) 64 | Install-Module MSOnline 65 | 66 | # Install AzureAD 67 | Install-Module AzureAD 68 | 69 | # Download & Run raindance 70 | git clone https://github.com/true-demon/raindance.git C:\Path\to\Raindance 71 | cd C:\Path\to\Raindance 72 | Import-Module .\raindance.ps1 73 | ``` 74 | 75 | ### Dependencies 76 | * Windows Only (for now): Microsoft has promised to (eventually) add Linux support for the library dependencies. 77 | * Powershell v5.0+: This is due to .NET dependencies 78 | * Library - MSOnline: Download using powershell `Install-Module msonline` 79 | * Library - AzureAD: Download using powershell `Install-Module AzureAD` 80 | 81 | ### Optional 82 | It is recommended to install [chocolatey](https://chocolatey.org/install "Chocolatey Installer") for windows to assist with installing Powershell packages 83 | 84 | -------------------------------------------------------------------------------- /raindance.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Intelligence Gathering tool for Office 365 & Microsoft Exchange 4 | 5 | .Description 6 | Authenticate to an O365/MSExchange user to perform intelligence 7 | gathering on the domain from the user-context using some snazzy, 8 | hopefully user-friendly cmdlets, or via the menu wizard. 9 | 10 | .Example 11 | # Print Help Message & Usage 12 | Rain-Help 13 | 14 | .Example 15 | # List available cmdlets 16 | Rain-Help 17 | 18 | #> 19 | 20 | function Get-Banner{ 21 | $BANNER= @" 22 | 23 | 24 | \ _( )_ \ \ _( )_ \ \ 25 | _( )_ _( )_ ( ) \ \ 26 | (_________) \ _( )_(_ _) 27 | \ \ \ (_________)________) \ \ \ 28 | \ \ \ \ \ \(__________) \ 29 | \ \ \ \ \ \ \ \ \ 30 | \ . \ \ \ \ \ \ \ 31 | \ \_O/ \ \ , \ \ \ \ 32 | \ \ \ \ / \ \O_ 33 | /\_ \ \_\ ,/\/ \ 34 | \ , \ \ \ / \ \ 35 | ___ \ __ /O\ \ \ \ 36 | | _ \ __ _(_)_ __ | _ \ __ _ _ __ ___ ___ 37 | | |_) / _ | | '_ \| | | |/ _ | '_ \ / __/ _ \ 38 | | _ < (_| | | | | | |_| | (_| | | | | (_| __/ 39 | |_| \_\__,_|_|_| |_|____/ \__,_|_| |_|\___\___| 40 | - Office 365 Info-Gathering Toolkit 41 | 42 | PRO-TIP: Use 'Rain-help' for a list of commands 43 | UBER PRO-TIP: Rain-Debug will list variables and additional cmdlets for more advanced users 44 | "@ 45 | 46 | Write-Output $BANNER 47 | } 48 | 49 | function Load-Modules { 50 | if ($PSVersionTable.PSVersion.Major -lt 5){ 51 | Write-Host("*********************************************") 52 | Write-Host("[-] Minimum PowerShell Version of 5.0 not met.") 53 | Exit 54 | } 55 | $OS=[System.Environment]::OSVersion.Platform 56 | if ($OS -like "*nix*"){ 57 | Write-Host("Microsoft currently does not support the necessary libraries (MSOnline & AzureAD) on Linux. n 58 | Sorry but you'll have to use Windows... :(") 59 | Read-Host -Prompt "Press enter to exit..." 60 | Exit-PSSession 61 | } 62 | 63 | if (!(Get-Module -ListAvailable -Name MSOnline)){ 64 | Write-Output("This tool requires MSOnline PS-module to be installed. Installing...") 65 | try { 66 | Install-Module MSOnline 67 | } 68 | catch 69 | { 70 | Write-Output("Error, unable to install MSOnline. Try running Powershell as administrator and executing the following...") 71 | Write-Output("Install-Module PackageManagement -Force") 72 | Write-Output("Install-Module MSOnline") 73 | Read-Host -prompt 'Hit "Enter" to exit...' 74 | Exit-PSSession 75 | } 76 | } 77 | if (!(Get-Module -ListAvailable -Name AzureAD)){ 78 | Write-Output("This tool requires AzureAD PS-Module to be installed. Installing...") 79 | try { 80 | Install-Module AzureAD 81 | } 82 | catch 83 | { 84 | Write-Output("Error, unable to install AzureAD. Try running Powershell as administrator and executing the following...") 85 | Write-Output("Install-Module AzureAD") 86 | Read-Host -prompt 'Hit "Enter" to exit...' 87 | Exit-PSSession 88 | } 89 | } 90 | Import-Module MSOnline 91 | Import-Module AzureAD 92 | } 93 | 94 | function check-import{ 95 | if(!(Get-Module -Name raindance)){ 96 | Write-Host "[!]Do not execute as a script! " 97 | Write-Host "-> Start this tool with 'Import-Module .\raindance.ps1'" 98 | Read-Host -prompt "Press enter to continue..." 99 | Exit-PSSession 100 | } 101 | } 102 | 103 | function Rain-Help{ 104 | $help_text = @" 105 | --------------_LOG IN_-------------- 106 | [*] Use this command first to begin your magical journey into the land of Office :D 107 | 108 | Rain-Login | Log into Office365, AzureAD & Exchange 109 | 110 | 111 | -----------_GET COMMANDS_----------- 112 | [*] These commands gather data and store it in memory to be recalled later by the show commands 113 | ==> Use the "-verbose" option to return ALL output, including empty/useless stuff 114 | ==> BE PATIENT! These commands take a while to complete. The more info, the longer it takes. 115 | ==> Wait a few minutes and hit space if the console appears to hang. It's just thinking ;) 116 | 117 | Rain-GetAll | Get all user, group, role, & admin information at once (Takes a long time) 118 | Rain-GetUsers | Get all user information (MUST BE RUN FIRST)" 119 | Rain-GetDevices| Get all active devices on the AzureAD Domain with currently logged in sessions 120 | Rain-GetRoles | Get all user roles (O365 permissions) details (MUST BE RUN SECOND) 121 | Rain-GetGroup | Get all distribution/security group details 122 | Rain-GetGroupMembers | Get all members of distribution groups (MUST HAVE RUN Rain-GetGroup) 123 | Rain-GetAdmins | Get all administrative users 124 | 125 | 126 | -----------_SHOW COMMANDS_----------- 127 | [*] These commands show the data you've gathered up. Very quick, very clean. :) 128 | 129 | Rain-Show | Show gathered information currently in memory. Options below. 130 | | 131 | |-> Users 132 | |-> Devices 133 | |-> Roles 134 | |-> Groups 135 | |-> GroupMembers 136 | |-> Admins 137 | |-> GlobalAdmins 138 | Example: 'Rain-Show Admins' 139 | 140 | -----------_DUMP COMMANDS_------------ 141 | [*] Dump currently gathered information to a csv file(s) or txt file. 142 | ==> DumpAll to txt format will dump all information to a SINGLE, formatted file. 143 | ==> DumpAll CSV will create 1 csv file per list (user.csv, admins.csv, etc) 144 | 145 | Rain-DumpAll | Dumps all gathered information to csv (default) or txt formatted files. (Files are auto-named) 146 | Rain-DumpUsers | Dumps all user information to csv (default) or txt formatted files 147 | Rain-DumpDevices | Dumps all gathered device sessions to csv (default) or txt formatted files. 148 | Rain-DumpRoles | Dumps all role information to csv (default) or txt formatted files 149 | Rain-DumpAdmins | Dumps all administrator (and global admins) to csv (default) or txt formatted files 150 | Rain-DumpGroups | Dumps all security & distribution groups to csv (default) or txt formatted files 151 | 152 | -----------_OTHER COMMANDS_------------ 153 | [*] Commands that divulge other information about the domain 154 | Rain-Company | Get information about the organization along with some policy information (alias for Get-MsolCompanyInformation) 155 | 156 | "@ 157 | Write-Host $help_text 158 | } 159 | 160 | function Rain-Debug{ 161 | $debug_text = @" 162 | ---------_VARIABLES AVAILABLE_--------- 163 | Feel free to manipulate these variables to get more verbose output once they have been instantiated. 164 | Please note, these are global variables, and must be re-declared any time values are re-assigned. 165 | 166 | O_USERS - Return output of users gathered by Rain-GetUsers 167 | O_ROLES - Return output of roles gathered by Rain-GetRoles 168 | O_GROUPS - Return output of mailing groups gathered by Rain-GetGroups 169 | O_ADMINS - Return output of administrators (not global admin) gathered by Rain-GetAdmins 170 | O_GADMINS - Return output of global administrators gathered by Rain-GetAdmins 171 | "@ 172 | } 173 | 174 | function Rain-Login{ 175 | <# 176 | .Description 177 | Log into MS Online (O365/Exchange) Services 178 | 179 | .Example 180 | Rain-Login -user john.doe@example.com 181 | Enter target password: ************** 182 | [+] Successfully Logged In 183 | 184 | #> 185 | [CmdletBinding()] 186 | param 187 | ( 188 | [Parameter(Mandatory=$False)] 189 | [string]$user 190 | ) 191 | Write-Host '****** Welcome to RainDance! ******' 192 | Write-Verbose 'Attempting to Authenticate...' 193 | if(!$user){ 194 | $user = Read-Host -Prompt "Enter your target username" 195 | } 196 | $password = Read-Host -Prompt "Enter target password" -AsSecureString 197 | $creds = New-Object -typename System.Management.Automation.PSCredential -argumentlist $user, $password 198 | $password = $NULL 199 | Write-Host "[*] ATTEMPTING TO GET PSSESSION WITH EXCHANGE" 200 | try{ 201 | $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $creds -Authentication Basic -AllowRedirection 202 | Import-PSSession $Session 203 | Write-Host "[+] Success! You have access to exchange online mail!" 204 | } 205 | catch [System.Exception]{ 206 | Write-Host "[-] Login to Exchange Online failed. Either the user does not have access or is not licensed for Outlook" 207 | } 208 | 209 | Write-Host "[*] ATTEMPTING O365 LOGIN!" 210 | try{ 211 | Connect-MsolService -Credential $creds -ErrorAction Stop 212 | $Global:LOGGED_IN_USER = $user 213 | Write-Host "[+] Successfully Logged In!" 214 | Write-Host "[*] Gathering general domain info..." 215 | $Global:O_DOMAINS = @(Get-MSOLDomain | Select-Object $_.Name) 216 | $Global:Licenses = Get-MsolSubscription 217 | $current_user_role = Get-MsolUserRole -UserPrincipalName $user 218 | if(!($current_user_role)){ 219 | Write-Host "[-] Your current user has no administrative permissions." 220 | } 221 | else{ 222 | Write-Host "[+] Your current user has the following administrative privileges!" 223 | Write-Output $current_user_role 224 | } 225 | Write-Host "[*] You currently have access to the following domains:" 226 | Write-Output $O_DOMAINS.Name 227 | Write-Host "[*] This company currently has the following products in use... n" 228 | Write-Output $Licenses.SkuPartNumber 229 | } 230 | catch [System.Exception] { 231 | Write-Host "O365 - Authentication Error!" 232 | Write-Host "2FA may be enforced, or powershell is disabled for this user." 233 | Write-Host "To be sure, use Connect-MsolService manually from powershell." 234 | Write-Host "If successful, set $Global:LOGGED_IN_USER = 'yourtarget@domain.com' to continue." 235 | } 236 | 237 | Write-Host "---------------------------------------------------------- n" 238 | 239 | try{ 240 | Write-Host "[*] ATTEMPTING AZURE ACTIVE DIRECTORY LOGIN!" 241 | Connect-AzureAD -Credential $creds -ErrorAction Continue 242 | Write-Host "[+] Successfully authenticated to Azure!" 243 | } 244 | catch { 245 | Write-Host "Unable to Log into Azure AD. Either it is not provisioned, or you do not have access." 246 | } 247 | 248 | return Get-Header 249 | } 250 | 251 | function check-login{ 252 | if ($LOGGED_IN_USER -eq $NULL){ 253 | Write-Host "[-] Woah cowboy. You gotta login first." 254 | return $False 255 | } 256 | else{ 257 | return $True 258 | } 259 | } 260 | 261 | function Rain-GetUsers(){ 262 | [CmdletBinding()] 263 | param 264 | ( 265 | [Parameter(Mandatory=$False)] 266 | [Switch]$All, 267 | [Int]$MaxResults 268 | ) 269 | 270 | if(!(check-login)){Break} 271 | Write-Host "[+] Gathering Usernames and details..." 272 | $users = @() 273 | Get-MSOLUser @psBoundParameters | ForEach-Object { 274 | if ($_.IsLicensed -eq $True){ 275 | $item = [Ordered]@{ 276 | Username=$_.UserPrincipalName 277 | Name=$_.DisplayName 278 | SignIn=$_.SignInName 279 | Department=$_.Department 280 | Title=$_.Title 281 | Phone=$_.PhoneNumber 282 | Mobile=$_.MobilePhone 283 | Office=$_.Office 284 | City=$_.City 285 | State=$_.State 286 | Location=$_.UsageLocation 287 | LastPasswordChange=$_.LastPasswordChangeTimestamp 288 | LastDirSync=$_.LastDirSyncTime 289 | ObjectId=$_.ObjectId 290 | } 291 | $users += New-Object PSObject -Property $item 292 | if ($users.length % 100 -eq 0){ 293 | Write-Host "[+] $($users.length) Users Collected" 294 | } 295 | if ($users.length -gt 1000){ 296 | Write-Host "[*] More than 1000 users have been found so far. This may take a while. Don't stop the script, even if it appears to hang" 297 | } 298 | } 299 | } 300 | 301 | $Global:O_USERS = $users 302 | Write-Host "[+] User collection complete. $($users.length) total users collected. Use 'Rain-Show Users' command to view data." 303 | if($verbose){ 304 | return $users 305 | } 306 | } 307 | 308 | 309 | function Rain-DumpUsers{ 310 | param( 311 | [Parameter(Mandatory=$True, ValueFromPipeline=$True, position=1)] 312 | [string]$outfile, 313 | 314 | [Parameter(Mandatory=$False,ValueFromPipeline=$True, position=2)] 315 | [ValidateSet("csv","txt")] 316 | [string]$format = 'csv' 317 | ) 318 | 319 | if($format -eq "csv"){ 320 | $O_USERS | Sort-Object -Property Username | Export-Csv -path $outfile -NoTypeInformation 321 | } 322 | 323 | if($format -eq "txt"){ 324 | $O_USERS | Sort-Object -Property Username | Format-Table > $outfile 325 | } 326 | } 327 | 328 | function Rain-GetRoles{ 329 | if(!(check-login)){Break} 330 | Write-Host "[+] Gathering User Roles & Members..." 331 | $roles = @() 332 | Get-MSOLRole | ForEach-Object { 333 | if($_.IsEnabled){ 334 | if($_.Name.contains("Admin")){ 335 | $item = [Ordered]@{ 336 | Name=$($_.Name) 337 | ObjectId=$($_.ObjectID) 338 | Description=$($_.Description) 339 | IsAdmin=$True 340 | } 341 | $roles += New-Object PSObject -Property $item 342 | } 343 | 344 | else{ 345 | $item = [Ordered]@{ 346 | Name=$($_.Name) 347 | ObjectId=$($_.ObjectID) 348 | Description=$($_.Description) 349 | IsAdmin=$False 350 | } 351 | $roles += New-Object PSObject -Property $item 352 | } 353 | } 354 | } 355 | if($O_ROLES.length -eq 0){ 356 | $Global:O_ROLES = $roles 357 | } 358 | Write-Host "[+] Role collection complete. Use 'Rain-Show Roles' command to view data." 359 | if($verbose){ 360 | return $roles 361 | } 362 | } 363 | 364 | function Rain-DumpRoles{ 365 | param( 366 | [Parameter(Mandatory=$True, ValueFromPipeline=$True, position=1)] 367 | [string]$outfile, 368 | 369 | [Parameter(Mandatory=$False,ValueFromPipeline=$True, position=2)] 370 | [ValidateSet("csv","txt")] 371 | [string]$format = 'csv' 372 | ) 373 | 374 | if($format -eq "csv"){ 375 | $O_ROLES | Sort-Object -Property Username | Export-Csv -path $outfile -NoTypeInformation 376 | } 377 | 378 | if($format -eq "txt"){ 379 | $O_ROLES | Sort-Object -Property Username | Format-Table > $outfile 380 | } 381 | } 382 | 383 | function Rain-GetAdmins(){ 384 | if(!(check-login)){Break} 385 | Write-Host "[+] Gathering Administrator Accounts..." 386 | $admins = @() 387 | $Global_admins = @() 388 | $admin_roles = $O_ROLES | Where-Object{$_.IsAdmin -eq $True} 389 | foreach($admin_role in $admin_roles){ 390 | if($admin_role.Name -eq "Company Administrator"){ 391 | $Global_admins += Get-MsolRoleMember -All -RoleObjectID $admin_role.ObjectId 392 | } 393 | else{ 394 | $admins += Get-MsolRoleMember -All -RoleObjectId $admin_role.ObjectId 395 | } 396 | Write-Host "================$($admin_role.Name)================" 397 | Write-Output $(Get-MSOLRoleMember -RoleObjectID $admin_role.ObjectId) 398 | } 399 | $Global:O_ADMINS = $admins 400 | $Global:O_GADMINS = $Global_admins 401 | Write-Host "[+] Admins collection Complete. Use 'Rain-Show Admins' or 'Rain-Show GlobalAdmins' command to view data." 402 | if($verbose){ 403 | return $admins 404 | return $Global_admins 405 | } 406 | } 407 | 408 | function Rain-DumpAdmins{ 409 | param( 410 | [Parameter(Mandatory=$True, ValueFromPipeline=$True, position=1)] 411 | [string]$outfile, 412 | 413 | [Parameter(Mandatory=$False,ValueFromPipeline=$True, position=2)] 414 | [ValidateSet("csv","txt")] 415 | [string]$format = 'csv' 416 | ) 417 | 418 | if($format -eq "csv"){ 419 | $O_ADMINS | Sort-Object -Property Username | Export-Csv -path $outfile -NoTypeInformation 420 | } 421 | 422 | if($format -eq "txt"){ 423 | $O_ADMINS | Sort-Object -Property Username | Format-Table > $outfile 424 | } 425 | } 426 | 427 | function Rain-GetGroup{ 428 | if(!(check-login)){Break} 429 | Write-Host "[+] Gathering Mailing & Distribution Groups..." 430 | $groups = Get-MsolGroup -All 431 | if($O_GROUPS.length -eq 0){ 432 | $Global:O_GROUPS = $groups 433 | } 434 | Write-Host "[+] Group collection Complete. Use 'Rain-Show Groups' command to view data." 435 | if($verbose){ 436 | return $groups 437 | } 438 | } 439 | 440 | function Rain-DumpGroups{ 441 | param( 442 | [Parameter(Mandatory=$True, ValueFromPipeline=$True, position=1)] 443 | [string]$outfile, 444 | 445 | [Parameter(Mandatory=$False,ValueFromPipeline=$True, position=2)] 446 | [ValidateSet("csv","txt")] 447 | [string]$format = 'csv' 448 | ) 449 | 450 | if($format -eq "csv"){ 451 | $O_GROUPS | Sort-Object -Property Username | Export-Csv -path $outfile -NoTypeInformation 452 | } 453 | 454 | if($format -eq "txt"){ 455 | $O_GROUPS | Sort-Object -Property Username | Format-Table > $outfile 456 | } 457 | } 458 | 459 | function Rain-GetGroupMembers{ 460 | if(!($O_Groups)){Write-Host "[-] Groups have not been gathered yet! Run Rain-GetGroup first."} 461 | else{ 462 | Write-Host "[+] Gathering Group Members..." 463 | $group_member_list = @() 464 | foreach($group in $O_GROUPS){ 465 | $g = Get-MsolGroupMember -GroupObjectId $group.ObjectId 466 | $group_info = [ordered]@{ 467 | GroupName=$($group.DisplayName) 468 | Users=$($g) 469 | } 470 | $group_member_list += New-Object PSObject -property $group_info 471 | } 472 | $Global:O_GROUPMEMBERS = $group_member_list 473 | Write-Host {"[+] Group Member collection Complete. Use 'Rain-Show GroupMembers' command to view data."} 474 | if($verbose){ 475 | return $group_member_list 476 | } 477 | } 478 | } 479 | 480 | function Rain-GetDevices{ 481 | if(!(check-login)){break} 482 | Write-Host "[+] Gathering active devices..." 483 | $device_list = @() 484 | $azure_devices = Get-AzureADDevice 485 | foreach($device in $azure_devices){ 486 | $owner = Get-AzureADDeviceRegisteredOwner -ObjectId $device.ObjectId 487 | $item = [Ordered]@{ 488 | Hostname=$($device.DisplayName) 489 | OS=$($device.DeviceOSType) 490 | Version=$($device.DeviceOSVersion) 491 | Trust=$($device.DeviceTrustType) 492 | ObjectId=$($device.ObjectId) 493 | Owner=$($owner.DisplayName) 494 | Username=$($owner.UserPrincipalName) 495 | } 496 | $device_list += New-Object PSObject -Property $item 497 | } 498 | $Global:O_DEVICES = $device_list 499 | if($verbose){ 500 | return $devices_list 501 | } 502 | } 503 | 504 | function Rain-DumpDevices{ 505 | param( 506 | [Parameter(Mandatory=$True, ValueFromPipeline=$True, position=1)] 507 | [string]$outfile, 508 | 509 | [Parameter(Mandatory=$False,ValueFromPipeline=$True, position=2)] 510 | [ValidateSet("csv","txt")] 511 | [string]$format = 'csv' 512 | ) 513 | 514 | if($format -eq "csv"){ 515 | $O_DEVICES | Sort-Object -Property Hostname | Export-Csv -path $outfile -NoTypeInformation 516 | } 517 | 518 | if($format -eq "txt"){ 519 | $O_DEVICES | Sort-Object -Property Hostname | Format-Table > $outfile 520 | } 521 | } 522 | 523 | function Rain-GetAll{ 524 | if(!(check-login)){Break} 525 | Write-Host("Gathering ALL of the Rain!") 526 | if($verbose){ 527 | Rain-GetUsers -verbose 528 | Rain-GetDevices -verbose 529 | Rain-GetRoles -verbose 530 | Rain-GetAdmins -verbose 531 | Rain-GetGroup -verbose 532 | Rain-GetGroupMembers -verbose 533 | } 534 | 535 | Rain-GetUsers 536 | Rain-GetDevices 537 | Rain-GetRoles 538 | Rain-GetAdmins 539 | Rain-GetGroup 540 | Rain-GetGroupMembers 541 | } 542 | 543 | function Rain-Show{ 544 | param( 545 | [Parameter (Mandatory=$True, ValueFromPipeline=$True, Position=1)] 546 | [ValidateSet("Users","Devices","Roles","Groups","GroupMembers","Admins","GlobalAdmins")] 547 | [string]$type, 548 | 549 | [string]$search 550 | ) 551 | if($type -eq "Users"){ 552 | if($O_USERS -eq $NULL){ 553 | Write-Host "No Users gathered yet..." 554 | Break 555 | } 556 | $O_USERS | Sort-Object -Property Username | Format-Table 557 | } 558 | elseif($type -eq "Roles"){ 559 | if($O_ROLES -eq $NULL){ 560 | Write-Host "No Roles gathered yet..." 561 | Break 562 | } 563 | $O_ROLES | Sort-Object -Property Username | Format-Table 564 | } 565 | elseif($type -eq "Groups"){ 566 | if($O_GROUPS -eq $NULL){ 567 | Write-Host "No Groups gathered yet..." 568 | Break 569 | } 570 | $O_GROUPS | Sort-Object -Property Username | Format-Table 571 | } 572 | elseif($type -eq "GroupMembers"){ 573 | if($O_GROUPMEMBERS -eq $NULL){ 574 | Write-Host "No Group Members gathered yet..." 575 | Break 576 | } 577 | $search = Read-Host -prompt "Enter group to list members of... [blank = ALL GROUPS]" 578 | if($search -eq $NULL){ 579 | $O_GROUPMEMBERS | Sort-Object -Property GroupName 580 | } 581 | else{ 582 | $selection = $O_GROUPMEMBERS | Select-Object * | Where-Object {$_.GroupName -like "*$($search)*"} 583 | foreach($group in $selection){ 584 | "==============$($group.GroupName)==============" 585 | $group.Users | Sort-Object -Property DisplayName | Format-Table 586 | } 587 | } 588 | } 589 | 590 | elseif($type -eq "Admins"){ 591 | if($O_ADMINS -eq $NULL){ 592 | Write-Host "No Admins gathered yet..." 593 | Break 594 | } 595 | $O_ADMINS | Sort-Object -Property Username | Format-Table 596 | } 597 | elseif($type -eq "GlobalAdmins"){ 598 | if($O_GADMINS -eq $NULL){ 599 | Write-Host "No Global Admins gathered yet..." 600 | Break 601 | } 602 | $O_GADMINS | Sort-Object -Property Username | Format-Table 603 | } 604 | elseif($type -eq "Devices"){ 605 | if($O_DEVICES -eq $NULL){ 606 | Write-Host "No devices have been gathered yet or no active sessions exist..." 607 | break 608 | } 609 | $O_DEVICES | Sort-Object -Property Hostname | Format-Table 610 | } 611 | else{ 612 | Write-Host "Use one of the following types: Users, Roles, Groups, Admins" 613 | } 614 | } 615 | 616 | function Rain-DumpAll{ 617 | [CmdletBinding()] 618 | param 619 | ( 620 | [Parameter(Mandatory=$True, ValueFromPipeline=$True, position=1, 621 | HelpMessage="Enter a path to drop files into")] 622 | [string]$path, 623 | [Parameter(Mandatory=$False, ValueFromPipeline=$True, position=2, 624 | HelpMessage="Format of output file (csv or txt)")] 625 | [string]$format = 'csv' 626 | ) 627 | 628 | if(!(test-path $path)){ 629 | New-Item -ItemType Directory -Force -Path $path | Out-Null 630 | } 631 | 632 | if($format -eq 'txt'){ 633 | $outfile = [io.path]::combine($path, "Rain_dump.txt") 634 | "========================+USERS+========================" > $outfile 635 | $O_USERS | Sort-Object -Property Username | Format-Table >> $outfile 636 | "=======================+DEVICES+=======================" >> $outfile 637 | $O_DEVICES | Sort-Object -Property Hostname | Format-Table >> $outfile 638 | "========================+ROLES+========================" >> $outfile 639 | $O_ROLES | Sort-Object -Property Username | Format-Table >> $outfile 640 | "========================+GROUPS+=======================" >> $outfile 641 | $O_GROUPS | Sort-Object -Property Username | Format-Table >> $outfile 642 | "====================+GLOBAL ADMINS+====================" >> $outfile 643 | $O_GADMINS | Sort-Object -Property Username | Format-Table >> $outfile 644 | 645 | } 646 | else{ 647 | $prefix = "Rain_" 648 | $userfile = $prefix + "users.csv" 649 | $rolefile = $prefix + "roles.csv" 650 | $groupfile = $prefix + "groups.csv" 651 | $adminfile = $prefix + "admins.csv" 652 | $fullpaths = @($userfile, $rolefile, $groupfile, $adminfile) 653 | foreach($i in $fullpaths){ 654 | $full = [io.path]::combine($path, $i) 655 | if($i -like "*user*"){Rain-DumpUsers $full $format} 656 | elseif($i -like "*role*"){Rain-DumpRoles $full $format} 657 | elseif($i -like "*group*"){Rain-DumpGroups $full $format} 658 | elseif($i -like "*admin*"){Rain-DumpAdmins $full $format} 659 | } 660 | } 661 | Write-Host {"[+] ---COMPLETE---"} 662 | } 663 | 664 | function Rain-Company{ 665 | if(!($COMPANY)){ 666 | $Global:COMPANY = Get-MsolCompanyInformation 667 | } 668 | return $COMPANY 669 | } 670 | 671 | # Shows logged in user and domain information 672 | function Get-Header{ 673 | Write-Host ('============================= Welcome to Rain Dance =============================') 674 | if($LOGGED_IN_USER){ 675 | Write-Output $("Current Domain: {0} | Alternate domains: {1}" -f $O_DOMAINS[0].Name, ($O_DOMAINS.Length - 1)) 676 | Rain-Company 677 | } 678 | else{ 679 | Write-Output "Not logged in. Run Rain-Login to get started." 680 | } 681 | } 682 | 683 | # Declare Main Variables as global (I'm sorry...powershell forces me to do horrible things :C ) 684 | function Rain-Main{ 685 | Load-Modules 686 | $Global:LOGGED_IN_USER = $NULL 687 | $Global:O_Groups = $NULL 688 | $Global:O_DOMAINS = @() 689 | $Global:O_ADMINS = @() 690 | $Global:O_GADMINS = @() 691 | $Global:O_USERS = @() 692 | $Global:O_ROLES = @() 693 | $Global:O_DEVICES = @() 694 | Get-Banner 695 | check-import 696 | Rain-Login 697 | Get-Header 698 | } 699 | 700 | Rain-Main 701 | --------------------------------------------------------------------------------