Last active
April 13, 2021 16:42
-
-
Save akanieski/d1944c12070f274792327738d5e2620b to your computer and use it in GitHub Desktop.
A script used to find secrets in public gists and cross-reference them against GitHub and Azure DevOps
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.NOTES | |
Written by Andrew Kanieski under the GNU GPLv3 license found at https://www.gnu.org/licenses/ | |
.SYNOPSIS | |
Given a list of GitHub users to scan, this script will pull all their public | |
gists and search them for suspicious strings that look like Azure DevOps or GitHub | |
personal access tokens. If found it will cross reference them against GitHub and/or | |
a list of provided Azure DevOps organizations. | |
.PARAMETER InputFile | |
A CSV delimited list of GitHub users that will be used to drive the resulting | |
report. An example of the file format can be found below: | |
``` | |
"GitHubUsername","FullName","Email" | |
"akanieski", "Andrew Kanieski", "sample@andrewkanieski.com" | |
``` | |
.PARAMETER OutputFile | |
A CSV delimited result of the scan operation with the following format: | |
``` | |
"TokenType","GistUrl","GithubUsername","Email","FullName","CompromisedApi" | |
"AzureDevOps","https://api.github.com/gists/ba56f07875cc40ffba8920b16b511b80","akanieski","sample@andrewkanieski.com","Andrew Kanieski","https://dev.azure.com/ak-ups" | |
``` | |
.PARAMETER GithubUsername [Optional] | |
Github username used to access the GitHub API to pull gists. Providing this will | |
allow for a higher volume of requests to be made through the GitHub REST API. | |
.PARAMETER GithubToken [Optional] | |
Github personal access token used to access the GitHub API to pull gists. Providing | |
this will allow for a higher volume of requests to be made through the GitHub REST | |
API. | |
.PARAMETER AzureDevOpsUrls | |
A comma-seperated list of strings in the form of Azure DevOps organization or | |
collection urls. This should support Azure DevOps Services as well as Server. | |
#> | |
param ( | |
[string]$InputFile = "", | |
[string]$GithubUsername = "", | |
[string]$GithubToken = "", | |
[string]$OutputFile = "", | |
[string[]]$AzureDevOpsUrls = [string[]]@(), | |
[switch]$StrictMode | |
) | |
function Test-AzureDevOpsToken { | |
param ( | |
[string]$suspectedToken, | |
[string]$orgUrl | |
) | |
$encoded = [System.Convert]::ToBase64String([System.Text.ASCIIEncoding]::ASCII.GetBytes(":" + $suspectedToken)) | |
$result = try { | |
(Invoke-WebRequest -uri ($orgUrl + "/_apis/projects") -UseBasicParsing -Headers @{Authorization = "Basic " + $encoded}).BaseResponse | |
} catch [System.Net.WebException] { | |
Write-Verbose "An exception was caught: $($_.Exception.Message)" | |
$_.Exception.Response | |
} | |
if ([int]$result.StatusCode -eq 200) { | |
return $true; | |
} | |
return $false; | |
} | |
function Test-GithubToken { | |
param ( | |
[string]$suspectedToken | |
) | |
$encoded = [System.Convert]::ToBase64String([System.Text.UTF8Encoding]::UTF8.GetBytes("user:" + $suspectedToken)) | |
$result = try { (Invoke-WebRequest -uri "https://api.github.com/user" ` | |
-Headers @{Authorization = "Basic " + $encoded} ` | |
-UseBasicParsing).BaseResponse | |
} catch [System.Net.WebException] { | |
Write-Verbose "An exception was caught: $($_.Exception.Message)" | |
$_.Exception.Response | |
} | |
if ([int]$result.StatusCode -eq 200) { | |
return $true; | |
} | |
return $false; | |
} | |
$mappedUsers = Import-Csv -LiteralPath $InputFile | |
if ($GithubUsername -ne "" -and $GithubToken -ne "") { | |
$encodedToken = [System.Convert]::ToBase64String([System.Text.UTF8Encoding]::UTF8.GetBytes($GithubUsername + ":" + $GithubToken)) | |
$github_credentials = @{Authorization = "Basic " + $encodedToken} | |
} else { | |
$github_credentials = @{} | |
} | |
Write-Verbose $github_credentials | |
$compromised = [System.Collections.Generic.List[PSCustomObject]]::new() | |
$total_gists = 0 | |
$github_calls = 0 | |
foreach ($map in $mappedUsers) { | |
Write-Host "Scanning gists for [$($map.FullName)].." | |
$github_calls += 1 | |
$gists = (Invoke-RestMethod ` | |
-Headers $github_credentials ` | |
-Uri "https://api.github.com/users/$($map.GitHubUsername)/gists" ` | |
-UseBasicParsing) | |
Write-Verbose "Found $($gists.Count) gists for [$($map.FullName)]!" | |
foreach ($gist in $gists) { | |
$github_calls += 1 | |
$rawContent = Invoke-RestMethod -Uri $gist.url ` | |
-Headers $github_credentials ` | |
-UseBasicParsing ` | |
foreach ($file in $rawContent.files.psobject.Members ` | |
| where-object membertype -like 'noteproperty' ` | |
| Select-Object -Property "Name") { | |
$fileContent = $rawContent.files | Select-Object -ExpandProperty $file.Name | |
Write-Host "Scanning [$($file.Name)] .." | |
# Azure DevOps PATs | |
$matches = [regex]::Matches($fileContent.content, "[a-z0-9]{52}") | |
if ($AzureDevOpsUrls.Count -gt 0 -and $matches.Count -gt 0) { | |
Write-Host "Found $($matches.Count) suspected Azure DevOps tokens.." | |
$type = "AzureDevOps" | |
foreach ($suspectedToken in $Matches) { | |
$CompromisedApi = "Unknown" | |
foreach ($org in $AzureDevOpsUrls) { | |
Write-Host "Cross referencing against Azure DevOps [$org]" | |
if ($true -eq (Test-AzureDevOpsToken -SuspectedToken $suspectedToken.Value -OrgUrl $org)) { | |
$CompromisedApi = $org | |
Write-Host "Confirmed valid token - Added to report output" | |
break; | |
} | |
} | |
if (!$StrictMode -or $CompromisedApi -ne "Unknown") { | |
$row = [PsCustomObject]@{ | |
TokenType = $type | |
GistUrl = $rawContent.url | |
GithubUsername = $map.GitHubUsername | |
Email = $map.Email | |
FullName = $map.FullName | |
CompromisedApi = $CompromisedApi | |
} | |
$compromised += $row | |
} | |
} | |
} | |
# GitHub PATs | |
$matches = [regex]::Matches($fileContent.content, "ghp_[a-zA-Z0-9]{36}") | |
if ($matches.Count -gt 0) { | |
$type = "Github" | |
foreach ($suspectedToken in $Matches) { | |
$CompromisedApi = "Unknown" | |
Write-Host "Found $($matches.Count) suspected GitHub tokens.." | |
if ($true -eq (Test-GithubToken -SuspectedToken $suspectedToken.Value )) { | |
$CompromisedApi = "https://api.github.com/" | |
Write-Host "Confirmed valid token - Added to report output" | |
} | |
if (!$StrictMode -or $CompromisedApi -ne "Unknown") { | |
$row = [PsCustomObject]@{ | |
TokenType = $type | |
GistUrl = $rawContent.url | |
GithubUsername = $map.GitHubUsername | |
Email = $map.Email | |
FullName = $map.FullName | |
CompromisedApi = $CompromisedApi | |
} | |
$compromised += $row | |
} | |
} | |
} | |
} | |
$total_gists += 1 | |
} | |
} | |
Write-Host Done! | |
$compromised | Export-Csv -LiteralPath $OutputFile -NoTypeInformation | |
Write-Host | |
Write-Host | |
Write-Host "Final Report " -ForegroundColor Cyan | |
Write-Host "---------------------------------" | |
Write-Host | |
Write-Host " Report Parameters" -ForegroundColor Cyan | |
Write-Host " -------------------------------" | |
Write-Host " Total Github Users ...... $(([array]$mappedUsers).Count.ToString().PadLeft(4,' '))" | |
Write-Host " Total Gists Scanned ..... $($total_gists.ToString().PadLeft(4,' '))" | |
Write-Host " Github API calls ........ $($github_calls.ToString().PadLeft(4,' '))" | |
Write-Host " Azure DevOps Orgs ....... $($AzureDevOpsUrls.Count.ToString().PadLeft(4,' '))" | |
Write-Host " -------------------------------" | |
Write-Host | |
Write-Host | |
Write-Host " Suspected Secrets" -ForegroundColor Cyan | |
Write-Host " -------------------------------" | |
Write-Host " Azure DevOps Secrets ... $(([array]($compromised | where CompromisedApi -eq "Unknown" | where TokenType -eq "AzureDevOps")).Count.ToString().PadLeft(4,' '))" | |
Write-Host " GitHub Secrets ......... $(([array]($compromised | where CompromisedApi -eq "Unknown" | where TokenType -eq "Github")).Count.ToString().PadLeft(4,' '))" | |
Write-Host " -------------------------------" | |
Write-Host " Total $(([array]($compromised | where CompromisedApi -eq "Unknown")).Count.ToString().PadLeft(4,' '))" -ForegroundColor Yellow | |
Write-Host | |
Write-Host | |
Write-Host " Confirmed Secrets " -ForegroundColor Cyan | |
Write-Host " -------------------------------" | |
Write-Host " Azure DevOps Secrets ... $(([array]($compromised | where CompromisedApi -ne "Unknown" | where TokenType -eq "AzureDevOps")).Count.ToString().PadLeft(4,' '))" | |
Write-Host " GitHub Secrets ......... $(([array]($compromised | where CompromisedApi -ne "Unknown" | where TokenType -eq "Github")).Count.ToString().PadLeft(4,' '))" | |
Write-Host " -------------------------------" | |
Write-Host " Total $(([array]($compromised | where CompromisedApi -ne "Unknown")).Count.ToString().PadLeft(4,' '))" -ForegroundColor Magenta | |
Write-Host | |
Write-Host |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
GitHubUsername | FullName | ||
---|---|---|---|
akanieski | Andrew Kanieski | sample@andrewkanieski.com |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment