Documenting with PowerShell: Breaches using the HIBP API

So I was thinking of this idea for a bit. My sales team got approached by a product that gives you information about what breaches you are in. There were a couple of issues we had with this product. The first part is that 90% of its data comes from the public “Have I been Pwned” database, while they claimed it was their own. The second was that the tool did not integrate with our documentation system directly. There were some weird limits like a maximum of one domain per client, so I figured I’d try to build something myself instead.

After some testing I’ve decided to make 3 versions of this script. One that directly uploads the breach information to IT-Glue. Another to create generic HTML files which you could easily send to clients. Lastly I also wanted to help our sales team out a little with a dynamic version. With this one you could enter emails and IP and get a nice looking report back.

So let’s get started! For all three scripts you’ll need 2 API keys. One for Have I been Pwned which will cost you €3,50 a month. You’ll need another for Shodan which can be free, premium, or bought in discount for 1 dollar once in a while.

IT-Glue version

The IT-Glue version creates a flexible asset for you, uploads the data per client. It looks up each e-mail address in the O365 tenant, and does a Shodan search for the registered domain names.

################### Secure Application Model Information ###################
$ApplicationId = 'ApplicationID'
$ApplicationSecret = 'ApplicationSecret' | Convertto-SecureString -AsPlainText -Force
$RefreshToken = 'ExtremelyLongRefreshToken'
################# /Secure Application Model Information ####################

################# API Keys #################################################
$ShodanAPIKey = 'YourShodanAPIKEy'
$HaveIBeenPwnedKey = 'HIBPAPIKey'
################# /API Keys ################################################

################# IT-Glue Information ######################################
$ITGkey = "ITGluekey"
$ITGbaseURI = "https://api.eu.itglue.com"
$FlexAssetName = "Breach v1 - Autodoc"
$Description = "Automatic Documentation for known breaches."
$TableStyling = "<th>", "<th style=`"background-color:#4CAF50`">"
################# /IT-Glue Information #####################################
 
 
write-host "Checking if IT-Glue Module is available, and if not install it." -ForegroundColor Green
#Settings IT-Glue logon information
If (Get-Module -ListAvailable -Name "ITGlueAPI") { 
    Import-module ITGlueAPI 
}
Else { 
    Install-Module ITGlueAPI -Force
    Import-Module ITGlueAPI
}
#Setting IT-Glue logon information
Add-ITGlueBaseURI -base_uri $ITGbaseURI
Add-ITGlueAPIKey $ITGkey
  
write-host "Getting IT-Glue contact list" -ForegroundColor Green
$i = 0
do {
    $AllITGlueContacts += (Get-ITGlueContacts -page_size 1000 -page_number $i).data.attributes
    $i++
    Write-Host "Retrieved $($AllITGlueContacts.count) Contacts" -ForegroundColor Yellow
}while ($AllITGlueContacts.count % 1000 -eq 0 -and $AllITGlueContacts.count -ne 0) 
write-host "Generating unique ID List" -ForegroundColor Green
$DomainList = foreach ($Contact in $AllITGlueContacts) {
    $ITGDomain = ($contact.'contact-emails'.value -split "@")[1]
    [PSCustomObject]@{
        Domain   = $ITGDomain
        OrgID    = $Contact.'organization-id'
        Combined = "$($ITGDomain)$($Contact.'organization-id')"
    }
} 
$DomainList = $DomainList | sort-object -Property Combined -Unique
  
write-host "Checking if Flexible Asset exists in IT-Glue." -foregroundColor green
$FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data
if (!$FilterID) { 
    write-host "Does not exist, creating new." -foregroundColor green
    $NewFlexAssetData = 
    @{
        type          = 'flexible-asset-types'
        attributes    = @{
            name        = $FlexAssetName
            icon        = 'sitemap'
            description = $description
        }
        relationships = @{
            "flexible-asset-fields" = @{
                data = @(
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order           = 1
                            name            = "Tenant name"
                            kind            = "Text"
                            required        = $true
                            "show-in-list"  = $true
                            "use-for-title" = $true
                        }
                    },
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order           = 2
                            name            = "Breaches"
                            kind            = "Textbox"
                            required        = $true
                            "show-in-list"  = $false
                            "use-for-title" = $false
                        }
                    },
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order          = 3
                            name           = "Shodan Info"
                            kind           = "Textbox"
                            required       = $false
                            "show-in-list" = $false
                        }
                    } 
  
                )
            }
        }
    }
    New-ITGlueFlexibleAssetTypes -Data $NewFlexAssetData
    $FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data
}


write-host "Creating credentials and tokens." -ForegroundColor Green
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal
$HIBPHeader = @{'hibp-api-key' = $HaveIBeenPwnedKey }
write-host "Connecting to Office365 to get all tenants." -ForegroundColor Green
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
foreach ($Customer in $Customers) {

    $CustomerDomains = Get-MsolDomain -TenantId $Customer.TenantId
    write-host "Finding possible organisation IDs for $($customer.name)" -ForegroundColor Green
    $orgid = foreach ($customerDomain in $customerdomains) {
        ($domainList | Where-Object { $_.domain -eq $customerDomain.name }).'OrgID' | Select-Object -Unique
    }
    write-host "  Retrieving Breach Info for $($customer.name)" -ForegroundColor Green
    $UserList = get-msoluser -all -TenantId $Customer.TenantId
    $HIBPList = foreach ($User in $UserList) {
        try {
            $Breaches = $null
            $Breaches = Invoke-RestMethod -Uri "https://haveibeenpwned.com/api/v3/breachedaccount/$($user.UserPrincipalName)?truncateResponse=false" -Headers $HIBPHeader -UserAgent 'CyberDrain.com PowerShell Breach Script'
        }
        catch {
            if ($_.Exception.Response.StatusCode.value__ -eq '404') {  } else { write-error "$($_.Exception.message)" }
        }
        start-sleep 1.5
        foreach ($Breach in $Breaches) {
            [PSCustomObject]@{
                Username              = $user.UserPrincipalName
                'Name'                = $Breach.name
                'Domain name'         = $breach.Domain
                'Date'                = $Breach.Breachdate
                'Verified by experts' = if ($Breach.isverified) { 'Yes' } else { 'No' }
                'Leaked data'         = $Breach.DataClasses -join ', '
                'Description'         = $Breach.Description
            }
        }
    }
    $BreachListHTML = $HIBPList | ConvertTo-Html -Fragment -PreContent '<h2>Breaches</h2><br> A "breach" is an incident where data is inadvertently exposed in a vulnerable system, usually due to insufficient access controls or security weaknesses in the software. HIBP aggregates breaches and enables people to assess where their personal data has been exposed.<br>' | Out-String

    write-host "Getting Shodan information for $($Customer.name)'s domains."
    $SHodanInfo = foreach ($Domain in $CustomerDomains.Name) {
        $ShodanQuery = (Invoke-RestMethod -Uri "https://api.shodan.io/shodan/host/search?key=$($ShodanAPIKey)&query=$Domain&quot; -UserAgent 'CyberDrain.com PowerShell Breach Script').matches
        foreach ($FoundItem in $ShodanQuery) {
            [PSCustomObject]@{
                'Searched for'    = $Domain
                'Found Product'   = $FoundItem.product
                'Found open port' = $FoundItem.port
                'Found IP'        = $FoundItem.ip_str
                'Found Domain'    = $FoundItem.domain
            }

        }
    }
    if (!$ShodanInfo -or $SHodanInfo) { $ShodanInfo = @{'Detection' = "No information found for domains on Shodan"} }
    $ShodanHTML = $SHodanInfo | ConvertTo-Html -Fragment -PreContent "<h2>Shodan Information</h2><br>Shodan is a search engine, but one designed specifically for internet connected devices. It scours the invisible parts of the Internet most people won’t ever see. Any internet exposed connected device can show up in a search.<br>" | Out-String
    
    $FlexAssetBody =
    @{
        type       = 'flexible-assets'
        attributes = @{
            traits = @{
                'tenant-name' = $customer.DefaultDomainName
                'breaches'    = [System.Web.HttpUtility]::HtmlDecode($BreachListHTML -replace $TableStyling)
                'shodan-info' = ($ShodanHTML -replace $TableStyling)
            }
        }
    }

    write-host "   Uploading Breach Info $($customer.name) into IT-Glue" -foregroundColor green
    foreach ($org in $orgID | Select-Object -Unique) {
        $ExistingFlexAsset = (Get-ITGlueFlexibleAssets -filter_flexible_asset_type_id $FilterID.id -filter_organization_id $org).data | Where-Object { $_.attributes.traits.'tenant-name' -eq $($Customer.DefaultDomainName) } | Select-Object -last 1
        #If the Asset does not exist, we edit the body to be in the form of a new asset, if not, we just upload.
        if (!$ExistingFlexAsset) {
            if ($FlexAssetBody.attributes.'organization-id') {
                $FlexAssetBody.attributes.'organization-id' = $org
            }
            else { 
                $FlexAssetBody.attributes.add('organization-id', $org)
                $FlexAssetBody.attributes.add('flexible-asset-type-id', $FilterID.id)
            }
            write-output "                      Creating new Breach Info for $($Customer.name) into IT-Glue organisation $org"
            New-ITGlueFlexibleAssets -data $FlexAssetBody
 
        }
        else {
            write-output "                      Updating Breach Info for $($Customer.name) into IT-Glue organisation $org"
            $ExistingFlexAsset = $ExistingFlexAsset | select-object -Last 1
            Set-ITGlueFlexibleAssets -id $ExistingFlexAsset.id  -data $FlexAssetBody
        }
 
    }
}

Generic version

So the generic version works the same as the IT-Glue version. The only difference is that it will create a file per tenant in C:\Temp. You can distribute this file or add it to your own documentation system manually or via an API I don’t know about. 🙂 I’ve added a screenshot for how this would look as requested.

################### Secure Application Model Information ###################
$ApplicationId = 'ApplicationID'
$ApplicationSecret = 'ApplicationSecret' | Convertto-SecureString -AsPlainText -Force
$RefreshToken = 'ExtremelyLongRefreshToken'
################# /Secure Application Model Information ####################

################# API Keys #################################################
$ShodanAPIKey = 'YourShodanAPIKEy'
$HaveIBeenPwnedKey = 'HIBPAPIKey'
################# /API Keys ################################################


$head = @"
<script>
function myFunction() {
    const filter = document.querySelector('#myInput').value.toUpperCase();
    const trs = document.querySelectorAll('table tr:not(.header)');
    trs.forEach(tr => tr.style.display = [...tr.children].find(td => td.innerHTML.toUpperCase().includes(filter)) ? '' : 'none');
  }</script>
<Title>LNPP - Lime Networks Partner Portal</Title>
<style>
body { background-color:#E5E4E2;
      font-family:Monospace;
      font-size:10pt; }
td, th { border:0px solid black; 
        border-collapse:collapse;
        white-space:pre; }
th { color:white;
    background-color:black; }
table, tr, td, th {
     padding: 2px; 
     margin: 0px;
     white-space:pre; }
tr:nth-child(odd) {background-color: lightgray}
table { width:95%;margin-left:5px; margin-bottom:20px; }
h2 {
font-family:Tahoma;
color:#6D7B8D;
}
.footer 
{ color:green; 
 margin-left:10px; 
 font-family:Tahoma;
 font-size:8pt;
 font-style:italic;
}
#myInput {
  background-image: url('https://www.w3schools.com/css/searchicon.png'); /* Add a search icon to input */
  background-position: 10px 12px; /* Position the search icon */
  background-repeat: no-repeat; /* Do not repeat the icon image */
  width: 50%; /* Full-width */
  font-size: 16px; /* Increase font-size */
  padding: 12px 20px 12px 40px; /* Add some padding */
  border: 1px solid #ddd; /* Add a grey border */
  margin-bottom: 12px; /* Add some space below the input */
}
</style>
"@
   
$PreContent = @"
<H1> Breach logbook</H1> <br>
   
This log contains all breaches found for the e-mail addresses in your Microsoft tenant. You can use the search to find specific e-mail addresses.
<br/>
<br/>
    
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search...">
"@
   


write-host "Creating credentials and tokens." -ForegroundColor Green
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal
$HIBPHeader = @{'hibp-api-key' = $HaveIBeenPwnedKey }
write-host "Connecting to Office365 to get all tenants." -ForegroundColor Green
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
foreach ($Customer in $Customers) {
  $CustomerDomains = Get-MsolDomain -TenantId $Customer.TenantId
    write-host "  Retrieving Breach Info for $($customer.name)" -ForegroundColor Green
    $UserList = get-msoluser -all -TenantId $Customer.TenantId
    $HIBPList = foreach ($User in $UserList) {
        try {
            $Breaches = $null
            $Breaches = Invoke-RestMethod -Uri "https://haveibeenpwned.com/api/v3/breachedaccount/$($user.UserPrincipalName)?truncateResponse=false" -Headers $HIBPHeader -UserAgent 'CyberDrain.com PowerShell Breach Script'
        }
        catch {
            if ($_.Exception.Response.StatusCode.value__ -eq '404') {  } else { write-error "$($_.Exception.message)" }
        }
        start-sleep 1.5
        foreach ($Breach in $Breaches) {
            [PSCustomObject]@{
                Username              = $user.UserPrincipalName
                'Name'                = $Breach.name
                'Domain name'         = $breach.Domain
                'Date'                = $Breach.Breachdate
                'Verified by experts' = if ($Breach.isverified) { 'Yes' } else { 'No' }
                'Leaked data'         = $Breach.DataClasses -join ', '
                'Description'         = $Breach.Description
            }
        }
    }
    $BreachListHTML = $HIBPList | ConvertTo-Html -Fragment -PreContent '<h2>Breaches</h2><br> A "breach" is an incident where data is inadvertently exposed in a vulnerable system, usually due to insufficient access controls or security weaknesses in the software. HIBP aggregates breaches and enables people to assess where their personal data has been exposed.<br>' | Out-String

    write-host "Getting Shodan information for $($Customer.name)'s domains."
    $SHodanInfo = foreach ($Domain in $CustomerDomains.Name) {
        $ShodanQuery = (Invoke-RestMethod -Uri "https://api.shodan.io/shodan/host/search?key=$($ShodanAPIKey)&query=$Domain&quot; -UserAgent 'CyberDrain.com PowerShell Breach Script').matches
        foreach ($FoundItem in $ShodanQuery) {
            [PSCustomObject]@{
                'Searched for'    = $Domain
                'Found Product'   = $FoundItem.product
                'Found open port' = $FoundItem.port
                'Found IP'        = $FoundItem.ip_str
                'Found Domain'    = $FoundItem.domain
            }

        }
    }
    if (!$ShodanInfo) { $ShodanInfo = @{ 'Detection' = "No information found for domains on Shodan" } }
    $ShodanHTML = $SHodanInfo | ConvertTo-Html -Fragment -PreContent "<h2>Shodan Information</h2><br>Shodan is a search engine, but one designed specifically for internet connected devices. It scours the invisible parts of the Internet most people won’t ever see. Any internet exposed connected device can show up in a search.<br>" | Out-String
    
$head,$PreContent,[System.Web.HttpUtility]::HtmlDecode($BreachListHTML),$ShodanHTML | Out-File "C:\temp\$($customer.name).html"
   
}

On-Demand version

So the on-demand version is transformed into a function. This allows you to enter the e-mail addresses as a list, and any IP addresses you also want to add to the report. An example query could be:

Get-BreachInfo -EmailAddress 'Person2@google.com','Person1@google.com' -IPs '1.1.1.1','cyberdrain.com' -ShodanAPIKey 'YourShodanKey' -HaveIBeenPwnedKey 'YourShodanKey'

We’re hosting this version on a Azure Function that our sales engineers can query whenever they need to. It makes it easy for them to create a report for a client on-demand.

function Get-BreachInfo {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]$EmailAddress,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]$IPs,
        [Parameter(Mandatory = $true)]$ShodanAPIKey,
        [Parameter(Mandatory = $true)]$HaveIBeenPwnedKey,
        [Parameter(Mandatory = $true)]$Outputfile
    )
    $head = @"
<script>
function myFunction() {
    const filter = document.querySelector('#myInput').value.toUpperCase();
    const trs = document.querySelectorAll('table tr:not(.header)');
    trs.forEach(tr => tr.style.display = [...tr.children].find(td => td.innerHTML.toUpperCase().includes(filter)) ? '' : 'none');
  }</script>
<Title>LNPP - Lime Networks Partner Portal</Title>
<style>
body { background-color:#E5E4E2;
      font-family:Monospace;
      font-size:10pt; }
td, th { border:0px solid black; 
        border-collapse:collapse;
        white-space:pre; }
th { color:white;
    background-color:black; }
table, tr, td, th {
     padding: 2px; 
     margin: 0px;
     white-space:pre; }
tr:nth-child(odd) {background-color: lightgray}
table { width:95%;margin-left:5px; margin-bottom:20px; }
h2 {
font-family:Tahoma;
color:#6D7B8D;
}
.footer 
{ color:green; 
 margin-left:10px; 
 font-family:Tahoma;
 font-size:8pt;
 font-style:italic;
}
#myInput {
  background-image: url('https://www.w3schools.com/css/searchicon.png'); /* Add a search icon to input */
  background-position: 10px 12px; /* Position the search icon */
  background-repeat: no-repeat; /* Do not repeat the icon image */
  width: 50%; /* Full-width */
  font-size: 16px; /* Increase font-size */
  padding: 12px 20px 12px 40px; /* Add some padding */
  border: 1px solid #ddd; /* Add a grey border */
  margin-bottom: 12px; /* Add some space below the input */
}
</style>
"@
   
    $PreContent = @"
<H1> Breach logbook</H1> <br>
   
This log contains all breaches found for the e-mail addresses in your Microsoft tenant. You can use the search to find specific e-mail addresses.
<br/>
<br/>
    
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search...">
"@
    $HIBPHeader = @{'hibp-api-key' = $HaveIBeenPwnedKey }
    write-host "  Retrieving Breach Info" -ForegroundColor Green
    $UserList = $EmailAddress
    $HIBPList = foreach ($User in $UserList) {
        try {
            $Breaches = $null
            $Breaches = Invoke-RestMethod -Uri "https://haveibeenpwned.com/api/v3/breachedaccount/$($user)?truncateResponse=false" -Headers $HIBPHeader -UserAgent 'CyberDrain.com PowerShell Breach Script'
        }
        catch {
            if ($_.Exception.Response.StatusCode.value__ -eq '404') {  } else { write-error "$($_.Exception.message)" }
        }
        start-sleep 1.5
        foreach ($Breach in $Breaches) {
            [PSCustomObject]@{
                Username              = $user
                'Name'                = $Breach.name
                'Domain name'         = $breach.Domain
                'Date'                = $Breach.Breachdate
                'Verified by experts' = if ($Breach.isverified) { 'Yes' } else { 'No' }
                'Leaked data'         = $Breach.DataClasses -join ', '
                'Description'         = $Breach.Description
            }
        }
    }
    $BreachListHTML = $HIBPList | ConvertTo-Html -Fragment -PreContent '<h2>Breaches</h2><br> A "breach" is an incident where data is inadvertently exposed in a vulnerable system, usually due to insufficient access controls or security weaknesses in the software. HIBP aggregates breaches and enables people to assess where their personal data has been exposed.<br>' | Out-String

    write-host "      Getting Shodan information." -ForegroundColor Green
    $SHodanInfo = foreach ($Domain in $IPs) {
        $ShodanQuery = (Invoke-RestMethod -Uri "https://api.shodan.io/shodan/host/search?key=$($ShodanAPIKey)&query=$Domain&quot; -UserAgent 'CyberDrain.com PowerShell Breach Script').matches
        foreach ($FoundItem in $ShodanQuery) {
            [PSCustomObject]@{
                'Searched for'    = $Domain
                'Found Product'   = $FoundItem.product
                'Found open port' = $FoundItem.port
                'Found IP'        = $FoundItem.ip_str
                'Found Domain'    = $FoundItem.domain
            }

        }
    }
    if (!$ShodanInfo) { $ShodanInfo = @{ 'Detection' = "No information found for domains on Shodan" } }
    $ShodanHTML = $SHodanInfo | ConvertTo-Html -Fragment -PreContent "<h2>Shodan Information</h2><br>Shodan is a search engine, but one designed specifically for internet connected devices. It scours the invisible parts of the Internet most people won’t ever see. Any internet exposed connected device can show up in a search.<br>" | Out-String
    
    $head, $PreContent, [System.Web.HttpUtility]::HtmlDecode($BreachListHTML), $ShodanHTML | Out-File $Outputfile
   
}

So that’s it! With this, I hope you can document your breaches a little a better and help clients understand the risks involved. As always, Happy PowerShelling!

27 thoughts on “Documenting with PowerShell: Breaches using the HIBP API

    1. Kelvin Tegelaar Post author

      I’m a big HIBP fan so I chose that one to check, but it should be fairly easy to convert to the Dehashed API too. It’s just a bit more expensive as dehashed API is 14 dollars a month, or 7 dollars per week. 🙂

      Reply
  1. Tom

    Great script, just got it working and getting results from Pwned, but the Shodan section just outputs 42. Any idea what could be wrong?

    Reply
  2. Carlo

    Another amazing script Kelvin! Ran into issues with HIBP in the on-demand script but I added “$HIBPHeader = @{‘hibp-api-key’ = $HaveIBeenPwnedKey }” and it works fine now. I’m also getting just a * and 42 for my Shodan output as well – not sure if that is by design?

    Reply
    1. Kelvin Tegelaar Post author

      Wow seems like I accidentally removed that. Added back! Thanks,

      The 42 seems to be returned when the value is 0. It simply returns the length. I’ve added a little trick for it to the script.

      Reply
  3. Zachery Frazier

    Love the script and the site. Just tried the IT Glue one and got a weird error after my second tenant.

    New-ITGlueFlexibleAssets : {“errors”:[{“source”:{“pointer”:”/data/attributes/flexible-asset-traits.breaches”},”detail”:[“is too long, must not exceed 64 kilobytes”],”title”:”Unprocessable
    Entity”,”status”:422}]}
    At D:\Users\zfrazier\SKYE Technologies, LLC\SKYE Technologies – Employee Resources\Scripts\internalscripts\Cyberdrain\breachdetection_itg.ps1:191 char:13
    + New-ITGlueFlexibleAssets -data $FlexAssetBody
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,New-ITGlueFlexibleAssets

    Reply
    1. Kelvin Tegelaar Post author

      That error occurs when the table has reach 64kb, that’s pretty huge and either you have a large environment, or lots of breaches! You can try to remove the breach description, or upload it as a file to IT-Glue instead.

      Reply
      1. Zachery Frazier

        Ah, i’ll see what I can do, I’m running it across 50 environments and some of those users go up to 150 users but I guess that could generate a bunch of info. I’ll remove the Descript ion section and see what happens.

        Reply
  4. Moez Tharani

    Hey Kelvin, would you have any suggestions on modifying this script to supply an email address within the PowerShell script itself? What I’m hoping to do is run this from within Syncro by pulling an email address from Syncro (based on the contact assigned to the asset this script will run against) and then checking the email against HIBP. I will further store the result in a custom field and then do some comparison to create a ticket if the result changes. But It’s really just that first process of supplying a single email within the script that I’m not sure on. Cheers.

    Reply
  5. Nick

    Hi Kelvin,

    You often create flexible assets with these scripts and often they are related to existing configurations. Is there a way to make them related items?

    Reply
  6. Stewart McAdoo

    After setting up the Secure App Model and getting the API keys, I am running into an issue with the script.

    I get access denied for both the HIBP and Shodan queries, specifically to using get-msoluser/domain.

    I checked against a few of our client accounts and they do list our partner as having full access. Is the app registration not passing through correctly to the client accounts?

    Reply
    1. Stewart McAdoo

      I realized that our Partner center did not have rights to access our clients and this was the cause of my error. I figured it had already been completed but enabled and working. Getting a timed out error on the shodan side but will keep troubleshooting that one!

      Reply
  7. Alp

    Hi Kelvin;
    First of all thx for the script. We are having the same issue accross all clients as well as a test domain of xyz.com
    Shodan returns;

    Shodan Information

    Shodan is a search engine, but one designed specifically for internet connected devices. It scours the invisible parts of the Internet most people won’t ever see. Any internet exposed connected device can show up in a search.
    IsReadOnly IsFixedSize IsSynchronized Keys Values SyncRoot Count
    False False False System.Collections.Hashtable+KeyCollection System.Collections.Hashtable+ValueCollection System.Object 1

    Any idea how to fix this?

    thanks

    Reply
      1. Alp

        Both.
        We had this issue in ITG, then we tried in HTML version happen there as well, and then we tried in test account with xyz.com just to make sure we should see a return same response.

        Reply
          1. Kelvin Tegelaar Post author

            Replace:

            if (!$ShodanInfo) { $ShodanInfo = [pscustomobject]@{ 'Detection' = "No information found for domains on Shodan"} }
            

            with:

            if (!$ShodanInfo) { $ShodanInfo = [pscustomobject]@{ 'Detection' = "No information found for domains on Shodan"; info = 'No info found' } }
            

            That should help! 🙂

  8. Eric Chapman

    I’m getting the following;
    Searching is only available on paid accounts (membership or higher)

    Is there a possibility to still use this script with a free shodan subscription?

    Reply
    1. Eric Chapman

      I had to pay the $49 to get a membership to make it work. (I didn’t want to wait for it to go on sale). It’s a little confusing that they have a monthly membership as well as a “registered member”. Hope this helps someone.

      Reply
  9. Henk Jansen

    Hi Kelvin,

    How does your team getting informed when there is a new breach?
    Do you check it on interval or do you have some alerts setted up in this?

    For a large customer base it’s really a job to check it by hand every month or quarter, and still you could potentially be very late, if one day after a check there is an new breach reported

    Reply
  10. Avi

    Hi Kelvin

    Thank you so much for such an awesome script. I am so excited to be able to leverage this to provide deeper scan info for our clients and help them see the value of keeping an eye on the dark recesses of the net.

    We are running across a bit of an issue getting this integrated with what we have. we are a an indirect CSP with MS, and looking at our CSP portal we don’t see the ability to establish an ApplicationID and ApplicationSecret that can be applied to all tenants. Is there a way to get the script working in this event, or would we have to have an app ID and secret for each individual tenant and run the script per client, with necessary modifications? Thank you very much again for your excellent skills and knowledge.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.