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" -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" -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" -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!

14 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

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.