Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A script used to find secrets in public gists and cross-reference them against GitHub and Azure DevOps
<#
.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
GitHubUsername FullName Email
akanieski Andrew Kanieski sample@andrewkanieski.com
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment