Using Powershell to notify when an email is involved in a data breach.

This week I worked with the Have I been Pwned API. I came up with a pretty use full little script that monitors Email addresses and notifies you if one of them is signed up for a compromised service. Have I been Pwned offers a service for this Here. Which is nice for individual accounts but if your at a business with hundreds of employees you don’t want to be adding accounts manually or sometimes you want to be emailed if someones account is on a pwned. That is where these scripts come in.

The scripts can be found:
Monitor script designed to work with AD can be found on pastebin here.
Monitor script using an array of emails can be found on pastebin here.
Additional functions made from this project can be found on pastebin here.
And as always My github has the full collection.

There are two versions of the monitor script, one with an array you can configure the email addresses manually. The other that pulls directly from active directory. A note: the monitor scripts do not care about the age of the breach. If haveibeenpwned.com gets information on a new breach that happened in 2001 and a users email over laps the user will be notified. After a breach has been identified it is logged and the user isn’t bothered again. The script also “stacks” breaches into one email so as not to spam your users with 100’s of emails.

An email for multiple breaches looks like:

1

This email is customize-able in the configuration section of the script.

Other quick notes on the use of the script before I go into configuration details. I would suggest not running the script more than once a month, or once a week at the most. The breaches can be old at times and constantly hammering the API will not do any good. There is also a sleep 5 in the script. Feel free to adjust it, I left it in to make sure larger accounts wouldn’t constantly query the API causing issues.

Configuration options for these scripts:

#Make sure the path exists or you will spam your list every time the script runs:
$path_to_notified_file = ".\db\pwnd_list.csv"

This is the database file that keeps the script from spamming your users. Make sure it is correct and writable or your users will be notified repeatedly.

Do you even want to send an email? With $email_notify set to $false the script just generates a CSV file. This lets you build a basic database of old breaches without annoying your users OR determining how many user emails have been involved in breaches.

#SMTP settings:
$email_notify = $true

Customize the Email alert the users will get:

$from = "test@example.com"
$subject = "ATTN: Account was included in a data breach"
$body_html = "Hello,
It has been noticed by an automated system that your email address was included in the following data breaches:"
$body_signature = "
It is recommended you change your passwords on those systems

Thank you
I_script_stuff Notifier Bot

#email credentials enable tested on gmail. If you don’t need credentials set $needs_email_creds to false.
$needs_email_creds = $false
#configure credential file for email password if needed:
$creds_path = “.\cred.txt”
#read-host -assecurestring | convertfrom-securestring | out-file $creds_path

The $needs_email_creds option needs you to setup a password if set to $true. This works on gmail but I haven’t tested it on other systems.
First load the $cred_path variable and then copy and paste the read-host line without the comment like so:

2
The script doesn’t prompt for anything. Just type your password for the email account and press enter. The password will be stored in the file.

Last bit you need to configure is SMTP server settings:

#SMTP server to use
$smtp = "smtp.gmail.com"
$smtp_port = "587"

Configured for google, you’ll need to know your own SMTP server settings.Monitor script designed to work with AD can be found on pastebin here.
Monitor script using an array of emails can be found on pastebin here.
Now your all set to monitor your corporate environment for breaches involving services your users may have signed up for on there email.

 

Additional functions made from this project can be found on pastebin here.


get-breachedstatus:


function get-breachedstatus() {
Param(
[Parameter(Mandatory = $true)][string]$email,
[AllowEmptyString()]$brief_report="$true"
)

try{
if($brief_report) {
$url = “https://haveibeenpwned.com/api/v2/breachedaccount/” + $email + “?truncateresponse=true”
} else {
$url = “https://haveibeenpwned.com/api/v2/breachedaccount/” + $email
}
$result = invoke-restmethod “$url” -UserAgent “I_script_stuff checker 0.01”
return $result
} catch {
return $false
}
}

This function is what powers the notified script. In the script it does a “truncated response” you can get some interesting information from a none truncated response:
Command:
Get-breachedstatus test@example.com

get-pastestatus:
This searches the API for paste breaches and provides the dump. There is no truncated response it just is the response:

function get-pastestatus() {
Param(
[Parameter(Mandatory = $true)][string]$email
)
try{
$url = "https://haveibeenpwned.com/api/v2/pasteaccount/" + $email
$result = invoke-restmethod $url -UserAgent "I_script_stuff checker 0.01"
return $result
} catch {
return $false
}
}

Get all breaches dumps the full database of breaches in case you want to cache them:

function get-allbreaches() {
try{
$url = "https://haveibeenpwned.com/api/v2/breaches"

$result = invoke-restmethod “$url” -UserAgent “I_script_stuff checker 0.01”
return $result
} catch {
return $false
}
}

Get Domain Stauts queries a specific domain for a breach:

function get-domainstatus() {
Param(
[Parameter(Mandatory = $true)][string]$domain,
)
try{
$url = "https://haveibeenpwned.com/api/v2/breach/" + $domain
$result = invoke-restmethod $url -UserAgent "I_script_stuff checker 0.01"
return $result
} catch {
return $false
}
}

You can use these functions to get started with any larger projects. Please skim the api documentation for fair use rules. Though mostly it is don’t do evil.

That is it for this week.  Thanks.

Google SafeSearch API, Google Locations, and more.

A short post this week. I have been messing with the google API and Powershell. The google APIs tend to follow a pattern so if nothing else I hope these functions work as a solid example. That said I like to make sure stuff is at least a little useful outside of examples.

My favorite is the safe browsing api. Google constantly monitors sites for Malware, Social Engineering and other attacks. This is the same system that warns Google users before clicking a link on a search. Some uses for the function would be to watch domains you own being flagged either correctly or a incorrectly. I was also kicking around the idea of doing an Active directory DNS log parser that caches the queries and compares them to the safe browsing database. This might catch users going with unsafe behavior and the like in a corporate environment.

You can find the function on pastebin here or all the scripts are on my github

Safe browsing function:
#Always fails safe search: malware.testing.google.test/testing/malware/
function get-BrowseSafe() {
Param(
[Parameter(Mandatory = $true)][string]$search,
[AllowEmptyString()]$api_key=""
)
#build the json object to send to google:
$json = '{
"client": {
"clientId": "BrowseSafe_monitor",
"clientVersion": "1"
},
"threatInfo": {
"threatTypes": ["MALWARE", "SOCIAL_ENGINEERING"],
"platformTypes": ["ANY_PLATFORM" ],
"threatEntryTypes": ["URL"],
"threatEntries": [

#loop through multiple semi-colon delimited urls
$search_arr = $search -split “;”
$count_max = $search_arr.count
$count = 1
foreach($item in $search_arr) {
if($count -eq $count_max) {
$json = $json + “{“”url””: “”$item””}”
} else {
$json = $json + “{“”url””: “”$item””},”
}
$count++
}
#close Json
$json = $json + ‘]}}’

#build url with correct api_key
$url = “https://safebrowsing.googleapis.com/v4/threatMatches:find?key=” + $api_key
try {
$result = Invoke-RestMethod “$url” -Method POST -Body $json -ContentType ‘application/json’
$result = $result.matches
} catch {
echo $_
}

if($($result.count) -eq 0) {
return $false
} else {
return $result
}
}

Search-custom uses the google custom search API. Which allows you to setup your own set of sites and query from googles search database.  You have to build a list of sites and base the search themes off of those. Each search list has its own id that can be updated. So the function provided can be used as more of a template for search sites you come up with. Be sure to get the customsearch_id provided by google after you configure your projects search list.

You can find the function on pastebin here or all the scripts are on my github

function search-custom() {
Param(
[Parameter(Mandatory = $true)][string]$search,
[AllowEmptyString()] $customsearch_id = "<provide your own search id with google>",
[AllowEmptyString()]$api_key="<your key>"
)

try {
$Search_results = invoke-restmethod “https://www.googleapis.com/customsearch/v1?q=$search&cr=us&cx=$customsearch_id&key=$api_key”
$search_results = ($search_results.items) | select title, snippet,link
return $search_results
} catch {
return $false
}
}

Search-nearby utilizes the google locations API. This is what recommends restaurants and the like near a location. I wanted this in my powershell profile so I ended up writing a quick wrapper.

You can find the function on pastebin here or all the scripts are on my github

function search-nearby() {
Param(
[Parameter(Mandatory = $true)][string]$search,
[AllowEmptyString()]$api_key="<your key>"
)

try {
$Search_results = invoke-restmethod “https://maps.googleapis.com/maps/api/place/textsearch/json?query=$search&key=$api_key”
$search_results = ($search_results.results) | select Name,types,Formatted_address,price_level,Rating
return $search_results
} catch {
return $false
}

}

The other 2 functions for this post are youtube based. It is worth checking out the youtube api directions. I found the youtube API the most confusing, and I still havn’t spent the time to figure out how to post videos. At least these will get you started with exploring youtube.

You can find the function on pastebin here or all the scripts are on my github


function Get-youtubesearch() {
Param(
[Parameter(Mandatory = $true)][string]$search,
[AllowEmptyString()]$max_page = 5,
[AllowEmptyString()]$copyright = "any",
[AllowEmptyString()]$youtube_key=""
)

$Search_results = invoke-restmethod “https://www.googleapis.com/youtube/v3/search?part=snippet&q=$search&type=video&videoLicense=$copyright&key=$youtube_key”
$page_count = 1
$video_list = @()

while(($Search_results.nextPageToken) -and ($page_count -le $max_page)) {
$next_page=$Search_results.nextPageToken

foreach($video_info in $search_results.items) {
$video_id = $video_info.id.videoid
$video_stats = invoke-restmethod “https://www.googleapis.com/youtube/v3/videos?part=statistics&id=$video_id&key=$youtube_key”
[int]$views = $video_stats.items.statistics.viewcount
[int]$likes = $video_stats.items.statistics.likecount
[int]$dislikes = $video_stats.items.statistics.dislikeCount
$title = $video_info.snippet.title
$link = “https://youtube.com/watch?v=$video_id”

$video_list += new-object psobject -Property @{
title = “$title”;
video_id = “$video_id”;
likes = $likes;
dislikes = $dislikes;
views = “$views”;
link = “$link”;
}

}

$Search_results = invoke-restmethod “https://www.googleapis.com/youtube/v3/search?part=snippet&pageToken=$next_page&type=video&q=$search&videoLicense=$copyright&key=$youtube_key”
$page_count++
}

return $video_list
}

function get-youtubepopular() {
Param(
[AllowEmptyString()]$max_page = 5,
[AllowEmptyString()]$copyright = “any”,
[AllowEmptyString()]$youtube_key=””
)

$Search_results = invoke-restmethod “https://www.googleapis.com/youtube/v3/videos?chart=mostPopular&key=$youtube_key&part=snippet”
$page_count = 1
$video_list = @()

while(($Search_results.nextPageToken) -and ($page_count -le $max_page)) {
$next_page=$Search_results.nextPageToken

foreach($video_info in $search_results.items) {
$video_id = $video_info.id
echo “second search”
$video_stats = invoke-restmethod “https://www.googleapis.com/youtube/v3/videos?part=statistics&id=$video_id&key=$youtube_key”
[int]$views = $video_stats.items.statistics.viewcount
[int]$likes = $video_stats.items.statistics.likecount
[int]$dislikes = $video_stats.items.statistics.dislikeCount
$title = $video_info.snippet.title
$link = “https://youtube.com/watch?v=$video_id”

$video_list += new-object psobject -Property @{
title = “$title”;
video_id = “$video_id”;
likes = $likes;
dislikes = $dislikes;
views = $views;
link = “$link”;
}

}

$Search_results = invoke-restmethod “https://www.googleapis.com/youtube/v3/videos?chart=mostPopular&pageToken=$next_page&key=$youtube_key”
$page_count++
}

return $video_list
}

Moving AD Objects using Powershell, Subnets, or Active directory Sites and Services

This week I was working on a script that pulls from the Computers container and places the computer object in the OU by subnet. The goal was for simple configuration and the method I came up with for configuration was using hashtables.


$Org_List = @{"192.168.0.0/28" = "ou=site1,dc=test,dc=local";

“192.168.3.0/24” = “ou=site2,dc=test,dc=local”;

“172.16.0.0/24” = “ou=site2,dc=test,dc=local”}

#Configure

$Computer_List = get-adcomputer -filter { Enabled -eq $true } -searchbase “CN=computers,DC=test,dc=local” | select DnsHostName, DistinguishedName

The complete script for this can be found on Pastebin  or on my github.

Adding new subnets and ou’s is just a matter of modifying the existing hash table and the script parses that data and moves the computer objects.

This works well, but then a friend pointed out how he needed to re-ip a whole office floor and this particular design would then need him to go back and update it. I thought about it and realized that the correct way to handle this was to piggyback it onto another maintenance task that would need to happen anyway. So I tied the script to sites and services:


$Org_List = @{"Office1" = "ou=site1,dc=test,dc=local";

“Office2” = “ou=site2,dc=test,dc=local”}

#Configure Computer search and limitations

$Computer_List = get-adcomputer -filter { Enabled -eq $true } -searchbase “CN=computers,DC=test,dc=local” | select DnsHostName, DistinguishedName

 

I still used the hash table, but now it matches the site names in my test environment:

 

 

 

 

 

 

 

 

 

 

 

 

 

Now when an office or floor is redone the normal maintenance done in Sites and Services will make sure the script will continue to drop the computer objects in the correct OU.

The full script can be found on Pastebin  or on github.

A quick breakdown of key functions in both scripts :

Find-ipcidr

 

function find-ipcidr() {

Param( [Parameter(Mandatory = $true)]$IP_Cidr )

$Ip_Cidr = $IP_Cidr.Split("/")

$Ip_Bin = ($IP_Cidr[0] -split '\.' | ForEach-Object {[System.Convert]::ToString($_,2).PadLeft(8,'0')}).ToCharArray()

for($i=0;$i -lt $Ip_Bin.length;$i++){

if($i -ge $Ip_Cidr[1]){

$Ip_Bin[$i] = "1"

}

}

[string[]]$IP_Int = @()

for($i = 0;$i -lt $Ip_Bin.length;$i++) {

$PartIpBin += $Ip_Bin[$i]

if(($i+1)%8 -eq 0){

$PartIpBin = $PartIpBin -join ""

$IP_Int += [Convert]::ToInt32($PartIpBin -join "",2)

$PartIpBin = ""

}

}

$IP_Int = $IP_Int -join "."

return $IP_Int

}

 

find-ipcidr gets the ending IP of a cidr range. For example 192.168.0.0/24 becomes 192.168.0.255. More information on CIDR can be https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing” target=”_blank”>found here.

ip_to_int32

 

function ip_to_int32(){

Param( [Parameter(Mandatory = $true)]$IP_int32 )

$IP_int32_arr = $IP_int32.split(".")

$return_int32 = ([Convert]::ToInt32($IP_int32_arr[0])*16777216 +[Convert]::ToInt32($IP_int32_arr[1])*65536 +[Convert]::ToInt32($IP_int32_arr[2])*256 +[Convert]::ToInt32($IP_int32_arr[3]))

return $return_int32

}

 

This converts the ip into a 32 bit integer. I use this in the scripts in order to tell if an ip is within a range. The code:


if(($search_ip_int32 -ge $start_of_range_int32) -and ($search_ip_int32 -le $end_of_range_int32))

Simply checks if the ip I am looking at is greater than / equal to the Start of the CIDR, or if it is with in the end of the CIDR. This is far better than other methods such as for loops and much simpler to work with.

The rest of the script is simply breaking up hashtable, running the above functions, or checking the range and performing an action.

Though my 2 scripts may not work perfectly in your environment, feel free to use any part or let me know how you have kept OU’s organized in the comments.

Thanks for reading.