Making Identity Tracking work with Microsoft Active Directory completely

0 Likes
over 5 years ago

While Identity Tracking (IdT) is a powerful add-on that allows you to view an identity's activity from a holistic approach, there are some limitations due to restrictions by the event source.

For IdT to function, the event must contain 3 vital pieces of information:

  1. (Initiator|Target)UserName
  2. (Initiator|Target)UserDomain
  3. Tenant

When it comes to Microsoft Active Directory, there are some EventIDs that only contain 2 of the 3 required fields (normally missing the UserDomain). However, the event sometimes contains the objectSID.

The 2011.1r5 release of the Microsoft Active Directory and Windows Collector will now add the objectSID to the (Initiator|Target)UserID field if it is available (or will default to S-1-0-0).

"Great, so how do I make IdT work then?", you may ask. Well, you can use the Sentinel Mapping feature to map the missing fields to the (Initiator|Target)UserName when the (Initiator|Target)UserID field is set.

First off, you'll need to create the Map first via Sentinel Control Center (SCC):

MapDataConfiguration.png

Next, we need to build a list that contains the objectSID's and their respective UserNames and Domain Names. The following PowerShell script can be Scheduled to run on a Windows Server at frequent intervals to export this information and upload it directly via the Sentinel REST APIs to the above Map.

NOTE: PowerShell is inherently bad at reading large files and this is required before posting to Sentinel. Make sure you have enough memory and CPU to account for the size of your AD extracts.

<#
.SYNOPSIS
	Exports all user objects' SID and sAMAccountName and updates Sentinel
.DESCRIPTION
	Exports all user objects' objectSID, sAMAccountName, and NetBIOSName Domain Name for the current domain and updates the Sentinel Map.
.NOTES
	File Name    : sid-to-sam.ps1
	Author       : Ben Walter
	Prerequisite : PowerShell v3  and Active Directory Modules
	SSL          : Import the Sentinel Web Service CA Cert into machine local store for trust.
.PARAMETER adlistfile
	Path to a csv file of the Active Directory DNS Domains to query along with credentials.
	CSV Format (keeping headers):
		"Domain","Username","SecurePassword"
	SecurePassword is built as follows (MUST be run as same account for which this script will run!):
		$PlainPassword = "P@ssw0rd"
		$SecurePassword = $PlainPassword | ConvertTo-SecureString -AsPlainText -Force
		$SecurePassword | ConvertFrom-SecureString
.PARAMETER encodedCreds
	Provide the Sentinel credentials in Basic Authentication format.
	To work this out, use the following powershell command:
	[System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("admin:c0mpl3xp@ssword"))
.PARAMETER sentinelurl
	Provide the base URL of Sentinel.
.PARAMETER sentinelmap
	Provide the name of the Sentinel Map to update.
.EXAMPLE
	sid-to-sam.ps1 sid-to-sam.csv YWRtaW46YzBtcGwzeHBAc3N3b3Jk https://sentinel.server.com:8443 SIDMapping
.EXAMPLE
	sid-to-sam.ps1 -encodedCreds YWRtaW46YzBtcGwzeHBAc3N3b3Jk -sentinelurl https://sentinel.server.com:8443 -sentinelmap SIDMapping
#>

#REQUIRES -Version 3.0
#REQUIRES -Modules ActiveDirectory

# Variables
param(
	[parameter(Mandatory=$false)][string]$adlistfile,
	[parameter(Mandatory=$true)][string]$encodedCreds,
	[parameter(Mandatory=$true)][string]$sentinelurl,
	[parameter(Mandatory=$true)][string]$sentinelmap
)
$debug = $true;
$temppath = $(Get-Location).tostring()   "\";
$sentct = "application/json";
$sentauth = ($sentinelurl   "/SentinelAuthServices/auth/tokens/");
$sentemaps = ($sentinelurl   "/SentinelRESTServices/objects/emap/");
$sentmaps = ($sentinelurl   "/SentinelRESTServices/maps/");
If ($debug)
{
	$logfile = ($temppath   $sentinelmap   ".log");
	Add-Content $logfile ("------ "   $(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   " ------");
}

Write-Host ("");

# Pre Cleanup incase of failure mid-call
If (Test-Path ($temppath   $sentinelmap   ".csv"))
{
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Removing partial cache file");
	}
	Remove-Item ($temppath   $sentinelmap   ".csv");
}

# Check for necessary modules
If (!(Get-Module -ListAvailable -Name ActiveDirectory))
{
	# Notify missing modules
	Write-Host ("This requires the Active Directory Modules to be installed.");
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": This requires the Active Directory Modules to be installed.");
	}
	Write-Host ("");
	Write-Host ("Attempting install...");
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Attempting install");
	}
	Import-Module ServerManager;
	Add-WindowsFeature RSAT-AD-PowerShell;
	Write-Host ("");
	$ErrorActionPreference = "Stop";
	Exit 0;
}

# Load necessary modules
If (!(Get-Module ActiveDirectory))
{
	Write-Host ("Importing the Active Directory Module...");
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Importing the Active Directory Module");
	}
	Write-Host ("");
	Import-Module ActiveDirectory;
}

# Find users and export
If ($adlistfile)
{
	# Read Domain List File
	Write-Host ("Reading domain list from "   $adlistfile   " file...");
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Reading domain list from "   $adlistfile   " file");
	}
	Write-Host ("");
	$DomainList = Import-Csv $adlistfile;
	$importCSVVar = $DomainList | Measure-Object | Select-Object;
	$count = (($importCSVVar).Count - 1);
	If ($count -gt 0)
	{
		# Loop through each domain
		Foreach ($addomain In $DomainList)
		{
			$thisdomain = $addomain.Domain;
			$thisusername = $addomain.Username;
			$thispassword = $addomain.SecurePassword | ConvertTo-SecureString;
			$thiscredential = New-Object System.Management.Automation.PSCredential($thisusername,$thispassword);
			# Get NetBIOSDomain name
			Write-Host ("Getting NetBIOSDomain name of "   $thisdomain   " as "   $thisusername   "...");
			If ($debug)
			{
				Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Getting NetBIOSDomain name of "   $thisdomain   " as "   $thisusername);
			}
			$domain = (Get-ADDomain -Server $thisdomain -Credential $thiscredential).NetBIOSName;
			Write-Host ("");
			# Get User List and append to CSV
			Write-Host ("Beginning export of "   $domain   " as "   $thisusername   "...");
			If ($debug)
			{
				Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Beginning export of "   $domain   " as "   $thisusername);
			}
			Write-Host ("");
			Get-ADUser -Server $thisdomain -Credential $thiscredential -Filter * -Properties objectSID,sAMAccountName | select objectSID,@{label="sAMAccountName";expression={$_.sAMAccountName.tolower()}},@{name="NetBIOSDomain";expression={$domain}} | Export-CSV ($temppath   $sentinelmap   ".csv") -NoTypeInformation -Append;
			Write-Host ("Export of "   $domain   " complete!");
			If ($debug)
			{
				Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Export of "   $domain   " complete");
			}
			Write-Host ("");
		}
	}
	Else
	{
		Write-Host ("Invalid CSV format for "   $adlist);
		Write-Host ("");
		$ErrorActionPreference = "Stop";
		Exit 0;
	}
}
Else
{
	# Get NetBIOSDomain name
	Write-Host ("Getting NetBIOSDomain name...");
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Getting NetBIOSDomain name");
	}
	$domain = (Get-ADDomain).NetBIOSName;
	Write-Host ("");
	# Get User List and append to CSV
	Write-Host ("Beginning export of "   $domain   "...");
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Beginning export of "   $domain);
	}
	Write-Host ("");
	Get-ADUser -Filter * -Properties objectSID,sAMAccountName | select objectSID,@{label="sAMAccountName";expression={$_.sAMAccountName.tolower()}},@{name="NetBIOSDomain";expression={$domain}} | Export-CSV ($temppath   $sentinelmap   ".csv") -NoTypeInformation;
	Write-Host ("Export of "   $domain   " complete!");
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Export of "   $domain   " complete");
	}
	Write-Host ("");
}

Write-Host ("Converting export for Sentinel...");
If ($debug)
{
	Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Converting export for Sentinel");
}
(Get-Content ($temppath   $sentinelmap   ".csv") | select -Skip 1) | Set-Content ($temppath   $sentinelmap   ".csv");
Write-Host ("");

# Build Basic Authentication
$basicAuthValue = ("Basic "   $encodedCreds);
$basicHeader = @{Authorization = $basicAuthValue};

# Request SAML Token
Write-Host "Connecting to Sentinel...";
If ($debug)
{
	Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Connecting to Sentinel");
}
$authtokens = Invoke-RestMethod -ContentType $sentct -Uri $sentauth -Method POST -Headers $basicHeader;
$samltoken = $authtokens.Token;
Write-Host ("");
If ((!($samltoken)) -or ($samltoken -eq ""))
{
	Write-Host ("Failed to get authentication token!");
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Failed to get authentication token");
	}
	Write-Host ("");
	$ErrorActionPreference = "Stop";
	Exit 0;
}

# Build SAML Authentication
$samlAuthValue = ("X-SAML "   $samltoken);
$SAMLHeader = @{Authorization = $samlAuthValue};

# Find Map Data URL
Write-Host "Finding Map to update...";
If ($debug)
{
	Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Finding Map to update");
}
$jsonobj = Invoke-RestMethod -ContentType $sentct -Uri ($sentemaps   "?pagesize=200") -Method GET -Headers $SAMLHeader;
Foreach ($obj In $jsonobj.objects)
{
	If ($obj.name -eq $sentinelmap)
	{
		$href = "@href";
		$sentinelmapobjurl = $obj.meta.$href;
		$sentinelmapobjurl = ($sentinelmapobjurl -replace $sentemaps,$sentmaps);
	}
}
Write-Host ("");
If ((!($sentinelmapobjurl)) -or ($sentinelmapobjurl -eq ""))
{
	Write-Host ("Failed to get map data URL for "   $sentinelmap   "!");
	If ($debug)
	{
		Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Failed to get map data URL for "   $sentinelmap);
	}
	Write-Host ("");
	$ErrorActionPreference = "Stop";
	Exit 0;
}

Write-Host ("Overwriting Map "   ($sentinelmapobjurl -replace $sentmaps,"")   "...");
If ($debug)
{
	Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Overwriting Map "   ($sentinelmapobjurl -replace $sentmaps,""));
}

Add-Type -AssemblyName System.Web;
$mimeType = [System.Web.MimeMapping]::GetMimeMapping($temppath   $sentinelmap   ".csv");
if ($mimeType)
{
	$ContentType = $mimeType;
}
else
{
	$ContentType = "text/csv";
}
$boundary = [guid]::NewGuid().ToString();
$body = "--"   $boundary   "`r`n";
$body  = "Content-Disposition: form-data; name=`"file`"; filename=`""   $sentinelmap   ".csv`"`r`n";
$body  = "Content-Type: "   $ContentType   "`r`n";
$body  = "`r`n";
$body  = Get-Content -Path ($temppath   $sentinelmap   ".csv") -Raw;
$body  = "--"   $boundary   "--`r`n";
Remove-Item ($temppath   $sentinelmap   ".csv");

try
{
	$postresult = Invoke-WebRequest -Uri $sentinelmapobjurl -Method POST -ContentType "multipart/form-data; boundary=$boundary" -Body $body -Headers $SAMLHeader;
}
catch [Exception]
{
	$postresult = $_;
}

Write-Host ("");
Write-Host ($postresult);
If ($debug)
{
	Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": "   $postresult);
}
Write-Host ("");

# Delete SAML Authentication
Write-Host ("Logging out of Sentinel...");
If ($debug)
{
	Add-Content $logfile ($(Get-Date -Format "yyyy-MM-dd_HH-mm-ss")   ": Logging out of Sentinel");
}
$logoffuri = ($sentauth   [System.Web.HttpUtility]::UrlEncode($samltoken));
$logoff = Invoke-WebRequest -Headers $basicHeader -Method DELETE -Uri $logoffuri;
Write-Host ("");
Exit 1;

Note: The delete of the SAML Token errors out. I'm not sure what PowerShell is doing to stuff it, but the passed URI is correct, so leaving it in anyway.

For command line help:

PS C:\> Get-Help \.sid-to-sam.ps1 -full

If your password contains special characters, use this script to get the Secure Password value:

<#
.SYNOPSIS
	Converts string password to secure password
.DESCRIPTION
	Converts console input to secure password and should be run as the user who needs to use the secure password
.NOTES
	File Name    : convert-password.ps1
	Author       : Ben Walter
	Prerequisite : PowerShell v3  and Active Directory Modules
.EXAMPLE
	convert-password.ps1
#>

#REQUIRES -Version 3.0
#REQUIRES -Modules ActiveDirectory

$PlainPassword = Read-Host -Prompt 'Enter plain password'
$SecurePassword = $PlainPassword | ConvertTo-SecureString -AsPlainText -Force
$SecurePassword | ConvertFrom-SecureString

Scheduled Task Action:

Program/script: PowerShell
Add arguments (optional): -noprofile -executionpolicy bypass -command "& \"C:\sid-to-sam.ps1\" -adlist \"C:\sid-to-sam.csv\" -encodedCreds \"YWRtaW46YzBtcGwzeHBAc3N3b3Jk\" -sentinelurl \"https://sentinel.server.com:8443\" -sentinelmap SIDMapping; exit $LASTEXITCODE"
Start in (optional): C:\

or

Program/script: PowerShell
Add arguments (optional): -noprofile -executionpolicy bypass -command "& \"C:\sid-to-sam.ps1\" -encodedCreds \"YWRtaW46YzBtcGwzeHBAc3N3b3Jk\" -sentinelurl \"https://sentinel.server.com:8443\" -sentinelmap SIDMapping; exit $LASTEXITCODE"
Start in (optional): C:\

Once the map is populated, change the fields to use the map (only Target shown below, you can do the equivalent for Initiator):

TargetUserDomain.png

TargetUserName.png

Labels:

Support Tip
Comment List
Anonymous
Related Discussions
Recommended