Capture User Devices
Discovering User Devices
Section titled “Discovering User Devices”This is for mapping usernames to MAC addresses via IP address. It requires multiple points of integration and captures data from Windows Domains.
- We use CIDR notation for IP address filtering
Remote Event Query
Section titled “Remote Event Query”This grabs user device details that are interacting with the domain controller. It takes five minutes worth of details, so should run every five minutes.
# Required for reliable Resolve-DnsName.import-module dnsclient
# Helper for filtering IP addresses belonging to a subnetfunction checkSubnet ([string]$cidr, [string]$ip) { $network, [uint32]$subnetlen = $cidr.Split('/') $a = [uint32[]]$network.split('.') [uint32] $unetwork = ($a[0] -shl 24) + ($a[1] -shl 16) + ($a[2] -shl 8) + $a[3] $mask = (-bnot [uint32]0) -shl (32 - $subnetlen) $a = [uint32[]]$ip.split('.') [uint32] $uip = ($a[0] -shl 24) + ($a[1] -shl 16) + ($a[2] -shl 8) + $a[3] $unetwork -eq ($mask -band $uip)}
# Use a password file: https://blogs.technet.microsoft.com/robcost/2008/05/01/powershell-tip-storing-and-using-password-credentials/$User = "YourDomain\service_account"$PWord = ConvertTo-SecureString -String "service_account_pass" -AsPlainText -Force$Credential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $PWord
$results = New-Object System.Collections.Generic.List[System.Object]$ips = @()$events = $null
try { Write-Host "Requesting events from remote server...";
$events = Get-WinEvent -ComputerName "domain.controller.com" -Credential $Credential -LogName "Security" -FilterXPath @" *[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4768 and TimeCreated[timediff(@SystemTime) <= 320000]]] and *[EventData[Data[@Name='Status'] and (Data='0x0')]] and *[EventData[Data[@Name='TargetDomainName'] and (Data='YourDomain')]]"@} catch { Write-Host "Server found no results..."; Write-Host $_.Exception.Message; exit 0}
Write-Host "Events received from remote server";
# This makes the events look like they were requested locally# (remote event requests come back as generic objects)ForEach ($event in $events) { $eventXML = [xml]$event.ToXml()
# Iterate through each one of the XML message properties For ($i=0; $i -lt $eventXML.Event.EventData.Data.Count; $i++) { # Append these as object properties Add-Member -InputObject $event -MemberType NoteProperty -Force ` -Name $eventXML.Event.EventData.Data[$i].name ` -Value $eventXML.Event.EventData.Data[$i].'#text' }}
Write-Host "IP addresses discovered:";
$events | ForEach-Object { try { $ip = $_.IpAddress $username = $_.TargetUserName $userlower = $username.ToLower() $domain = $_.TargetDomainName
# Ensure the event includes the IP address if ([string]::IsNullOrWhiteSpace($ip) -Or ($ip -eq "-") -Or [string]::IsNullOrWhiteSpace($username) -Or [string]::IsNullOrWhiteSpace($domain)) { return }
# Ensure IP address is of the correct type [IPAddress]$address = $ip if (($PSVersionTable.PSVersion.Major -ge 5) -Or (($PSVersionTable.PSVersion.Major -eq 4) -And ($PSVersionTable.PSVersion.Minor -ge 5))) { if ($address.IsIPv4MappedToIPv6) { $ip = $address.MapToIPv4().IPAddressToString } else { # Ignore IPv6 if ($address.AddressFamily.ToString() -eq "InterNetworkV6") { Write-Host "Ignoring IPv6 address: ", $ip return } } } else { # Check IPv6 Mapping manually if (($address.AddressFamily.ToString() -eq "InterNetworkV6") -And $ip.StartsWith("::ffff:")) { $new_ip = $ip.Split("::ffff:")[-1]
try { [IPAddress]$address = $new_ip if ($address.AddressFamily.ToString() -eq "InterNetwork") { $ip = $new_ip } } catch { # we are not interested in this IP address Write-Host "Ignoring IPv6 address: ", $ip return } } }
# Check the IP address hasn't been seen already if ($ips.Contains($ip)) { return }
# Filter IP ranges, service accounts and computer names$ if ( ` ( ` (checkSubnet "127.0.0.0/16" $ip) -Or ` (checkSubnet "192.168.0.0/16" $ip) -Or ` (checkSubnet "192.155.0.0/16" $ip) ` ) -and ` (!$userlower.StartsWith("sccm.")) -and ` (!$userlower.StartsWith("svc.")) -and ` ($username[-1] -ne "$") ` ) { $ips += $ip Write-Host $ip;
# Try to grab the computers hostname try { $hostname = (Resolve-DnsName $ip -ErrorAction SilentlyContinue)[0].NameHost $results.Add(@($ip,$username,$domain,$hostname)) } catch { $results.Add(@($ip,$username,$domain)) } } } catch { Write-Host "Error parsing event"; Write-Host $_.Exception.Message; }}
$resultArr = $results.ToArray()
# Only post to the server if there are resultsif ($resultArr.length -gt 0) { Write-Host "Posting to control server";
# Send to the server $postParams = ConvertTo-Json @($resultArr) $res = Invoke-WebRequest -UseBasicParsing -Uri https://placeos.server.com/api/engine/v2/webhook/trig-98UC/notify?secret=046856ff816d49f26261d3c9c9789da&exec=true&mod=LocationServices&method=ip_mappings -Method POST -Body $postParams -ContentType "application/json" -TimeoutSec 40 Write-Host "Response code was:" $res.StatusCode;
if ($res.StatusCode -ne 200) { Write-Host "Webhook post failed..."; exit 1 }} else { Write-Host "No results found...";}
Querying a MS Network Policy Server (RADIUS)
Section titled “Querying a MS Network Policy Server (RADIUS)”This allows us to grab MAC addresses of BYOD devices. Useful if tracking mobile phones on the Wi-Fi is desirable.
# Required for reliable Resolve-DnsName.import-module dnsclientimport-module dhcpserver
# Use a password file: https://blogs.technet.microsoft.com/robcost/2008/05/01/powershell-tip-storing-and-using-password-credentials/$User = "YourDomain\service_account"$PWord = ConvertTo-SecureString -String "service_account_pass" -AsPlainText -Force$Credential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $PWord
$results = New-Object System.Collections.Generic.List[System.Object]$macs = @()$events = $null
try { Write-Host "Requesting events from Network Policy Server...";
$events = Get-WinEvent -ComputerName "radius.server.com" -Credential $Credential -LogName "Security" -FilterXPath @" *[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=6278 and TimeCreated[timediff(@SystemTime) <= 90000]]] and *[EventData[Data[@Name='SubjectDomainName'] and (Data='YourDomain')]]"@} catch { Write-Host "Server found no results..."; Write-Host $_.Exception.Message; exit 0}
Write-Host "Events received from remote server";
# This makes the events look like they were requested locally# (remote event requests come back as generic objects)ForEach ($event in $events) { $eventXML = [xml]$event.ToXml()
# Iterate through each one of the XML message properties For ($i=0; $i -lt $eventXML.Event.EventData.Data.Count; $i++) { # Append these as object properties Add-Member -InputObject $event -MemberType NoteProperty -Force ` -Name $eventXML.Event.EventData.Data[$i].name ` -Value $eventXML.Event.EventData.Data[$i].'#text' }}
Write-Host "MAC addresses discovered:";
$events | ForEach-Object { try { $mac_address = $_.CallingStationID # Username in domain\username format $username = $_.FullyQualifiedSubjectUserName $ip = $null
# Grab the IP address assigned to the MAC address try { $ip = Get-DhcpServerv4Scope -ComputerName "dhcpserver.contoso.com" -ScopeId 192.168.4.0 | Get-DhcpServerv4Lease -ComputerName "dhcpserver.contoso.com" | where {$_.Clientid -like "$mac_address"} $ip = $ip.IPAddress.IPAddressToString } catch { # Ignore errors as it just means we won't able find the hostname }
# Ensure the event includes the username and device mac address if ([string]::IsNullOrWhiteSpace($mac_address) -Or ($mac_address -eq "-") -Or [string]::IsNullOrWhiteSpace($username) -Or ($username -eq "-")) { return }
# Check the IP address hasn't been seen already if ($macs.Contains($mac_address)) { return }
# Filter IP ranges and computer name$ $macs += $mac_address Write-Host $mac_address
# Try to grab the computers hostname try { $hostname = (Resolve-DnsName $ip -ErrorAction SilentlyContinue)[0].NameHost $results.Add(@($mac_address,$username,$hostname)) } catch { $results.Add(@($mac_address,$username)) } } catch { Write-Host "Error parsing event"; Write-Host $_.Exception.Message; }}
$resultArr = $results.ToArray()
# Only post to the server if there are resultsif ($resultArr.length -gt 0) { Write-Host "Posting to control server";
# Send to the server $postParams = ConvertTo-Json @($resultArr) $res = Invoke-WebRequest -UseBasicParsing -Uri https://placeos.server.com/api/engine/v2/webhook/trig-98UC/notify?secret=046856ff816d49f26261d3c9c9789da&exec=true&mod=LocationServices&method=mac_mappings -Method POST -Body $postParams -ContentType "application/json" -TimeoutSec 40 Write-Host "Response code was:" $res.StatusCode;
if ($res.StatusCode -ne 202) { Write-Host "Webhook post failed..."; exit 1 }} else { Write-Host "No results found...";}
Workstation Monitoring
Section titled “Workstation Monitoring”This is for when users log onto a shared resource and we want to know who is sitting at which workstation. We should attach an event to particular events using the filter below. More details on how to set this up are here
<Triggers> <EventTrigger> <ValueQueries> <Value name="username">Event/EventData/Data[@Name='TargetUserName']</Value> <Value name="domain">Event/EventData/Data[@Name='TargetDomainName']</Value> </ValueQueries> <Enabled>true</Enabled> <Subscription><QueryList><Query Id="0" Path="Security"><Select Path="Security">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4624]] and *[EventData[Data[@Name='LogonType'] and (Data='2' or Data='7')]]</Select></Query></QueryList></Subscription> </EventTrigger> </Triggers>
This catches log off events and marks the workstation as free.
param ( [Parameter(Mandatory=$false)][string]$username, [Parameter(Mandatory=$false)][string]$domain)
# Get the IP address of the local PC$ipV4 = Test-Connection -ComputerName $env:COMPUTERNAME -Count 1 | Select -ExpandProperty IPV4Address$ip = $ipV4.IPAddressToString
# Ensure the event includes the IP addressif ([string]::IsNullOrWhiteSpace($ip) -Or ($ip -eq "-") -Or [string]::IsNullOrWhiteSpace($username) -Or [string]::IsNullOrWhiteSpace($domain)) { Write-Host "IP address was blank"; exit 0}
# Post to details to server$postParams = ConvertTo-Json @(,@($ip,$username,$domain))Invoke-WebRequest -UseBasicParsing -Uri https://placeos.server.com/api/engine/v2/webhook/trig-98UC/notify?secret=046856ff816d49f26261d3c9c9789da&exec=true&mod=LocationServices&method=ip_mappings -Method POST -Body $postParams -ContentType "application/json"
Untrusted or Self Signed Certificates
Section titled “Untrusted or Self Signed Certificates”Add this to ignore certificate errors
add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } }"@[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
Protocol violation errors
Section titled “Protocol violation errors”Add this to ignore errors, see this thread on Powershell wget protocol violation
function Set-UseUnsafeHeaderParsing{ param( [Parameter(Mandatory,ParameterSetName='Enable')] [switch]$Enable,
[Parameter(Mandatory,ParameterSetName='Disable')] [switch]$Disable )
$ShouldEnable = $PSCmdlet.ParameterSetName -eq 'Enable'
$netAssembly = [Reflection.Assembly]::GetAssembly([System.Net.Configuration.SettingsSection])
if($netAssembly) { $bindingFlags = [Reflection.BindingFlags] 'Static,GetProperty,NonPublic' $settingsType = $netAssembly.GetType('System.Net.Configuration.SettingsSectionInternal')
$instance = $settingsType.InvokeMember('Section', $bindingFlags, $null, $null, @())
if($instance) { $bindingFlags = 'NonPublic','Instance' $useUnsafeHeaderParsingField = $settingsType.GetField('useUnsafeHeaderParsing', $bindingFlags)
if($useUnsafeHeaderParsingField) { $useUnsafeHeaderParsingField.SetValue($instance, $ShouldEnable) } } }}
# Call this before Invoke-WebRequestSet-UseUnsafeHeaderParsing -Enable