Privileged Access Management – demystified


 

Today’s topic:

Privileged Access Management (PAM)

Just in Time Administration

demystified

Coming with Microsoft Server 2016, we offered a new optional feature for Active Directory: the ‘Privileged Access Management Feature’. The new feature is only available with Domain Controllers OS >= Windows Server 2016.

Privileged Access Management Feature consists of two parts:

  1. Privileged Access Management (PAM) – aka Privileged Identity Management (PIM)
  2. Just in Time Administration (JiTA)

PAM represents an extra layer of security for administration – basically it’s a sort of ‘security by obscurity’ in its most positive interpretation.

 

How does it work (conceptually):

After implementing a single domain forest as admin forest, trusted by your production forest, you create shadow principals in the admin forest, that bear the SIDs of administrative groups from the production forest with it, for example from the Enterprise Admins and the Domain Admins.
You may then add user objects from the admin forest to the member attribute of the shadow principals. If such an user object logs on to resource in the production forest it will carry the SIDs of the administrative groups that correspond to the shadow principals the user object is member of.
One of the nice effects is that you can call DCpromo from GUI with an account from your admin forest that has the production Domain Admins shadow group membership and will therefore successfully pass the DCPromo internal check for Domain Admins group membership.

This happens because the Key Distribution Center (KDC) service on a Domain Controller calculates the SID list of an user’s group memberships provided by the forest this DC is member of – from:

  • sIDHistory
  • direct group memberships ( + the groups sIDHistory)
  • nested group memberships ( + the groups sIDHistory)
  • primaryGroupID ( –> member of = <Sid of the user’s domain + primaryGroupID>)
  • some default SIDs
  • memberships in shadow principals (the corresponding SIDs of the shadowed groups)
    This part only happens on DCs with OS >= Windows Server 2016.

If the trust created between production and admin forest has the following trust attributes bits set

  • enable sIDHistory
  • enable PIM trust

, the SIDs from the production forest in the calculated token make their way through the trust and thus will be present in the logon token in production.

Saying – you utilize user accounts from your admin forest, that ‘pretend’ to be member of the Domain Admins or Enterprise Admins from the production forest, but they never where, are not or will not be actual member of the production Domain Admins or Enterprise Admins.
This means that it is not possible to identify  these user accounts as high-level admin accounts by querying the well known high-level administrative groups –> ‘security by obscurity’ :-)

You may assign user accounts to the member attribute of groups or shadow principals permanently or temporary for a defined time span. If you assign membership temporary –> this is where Just in Time Administration joins the game.

Note: JiTA is not limited to be only used with shadow principals over a PIM trust – you may use this in your forest / domain with normal group memberships as well.

A temporary membership is flagged with an expiration time stamp, defined by the time of assigning the membership plus the time span defined for the membership.
The Active Directory service (NTDS) on a Domain Controller with OS >= Windows Server 2016 inspects memberships and removes these memberships after expiration time is exceeded.

Additionally does the Key Distribution Center (KDC) on such a DC set the issued Ticket Granting Ticket’s (TGT) End Time to the earliest expiration time of a membership (if there is any membership with expiration time present for the user). Session tickets will have the same End Time set.

Saying – if the expiration time of a membership exceeded –>

  • the user’s membership to this group or shadow principal is revoked
  • the TGT and the session tickets have expired
  • user requests new TGT and session tickets when contacting the resources again –> the membership is not in the token anymore

How does it work (technically):

Mandatory prerequisites are:

  • the DCs  in the forest where you want to utilize JiTA / PAM are Windows Server 2016 (or higher) machines
  • Forest Functional Level and Domain Functional Level of the admin forest must be of ‘Windows Server 2016’
  • for PAM usage:
    • a forest trust between production forest and admin forest (production trusts admin)
    • production forest DCs must be at least Windows Server 2012 R2 machines
    • if production forest DCs are Windows Server 2012 R2 machines must have July 2016 Update Rollup installed – easy check:
      run netdom trust /?
      if you see /EnablePIMTrust as last help topic –> the patch is there.
    • trustAttributes bit TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL (enable sIDHistory) is set in production for this trust
    • trustAttributes bit TRUST_ATTRIBUTE_PIM_TRUST (enable PIM trust) is set in production for this trust
    • optional feature ‘Privileged Access Management Feature’ is enabled in admin forest

The shadow principal object must be of objectClass msDs-ShadowPrincipal. The SID of the administrative group in your production forest is stored in the msDs-ShadowPrincipalSid attribute of the shadow principal.
Mandatory attributes while creation of a msDs-ShadowPrincipal object are:

  • cn
  • msDs-ShadowPrincipalSid

Such shadow principals can only be created as child objects of a msDs-ShadowPrincipalContainer object.

Shadow principals must be created in the default shadow principal container ‘ Shadow Principal Configuration’, located in the Services container, child of the configuration naming context of your admin forest – exemplary path for contoso.com:
CN=Shadow Principal Configuration,CN=Services,CN=Configuration,DC=contoso,DC=com

Note: Even though you may create msDs-ShadowPrincipalContainer in your domain naming context and therefore be able to create shadow principals here – they will not be functional for the PAM purpose.

To create a shadow principal I recommend to use PowerShell (PoSh) – the standard AD-Cmdlets are sufficient to perform the needed tasks. You will find sample code below.

Adding an user account to a shadow principal is a standard LDAP add modification request for the member attribute –>
AddRequest(distinguishedName of the shadow principal,member=distinguishedName of the user)

If we want to add an user account for a defined time span only – we use a little extension in the add modification request –>
AddRequest(distinguishedName of the shadow principal,member=<TTL=time span in seconds,distinguishedName of the user>)

There is PoSh sample code below.

Note: If you update an already assigned temporary membership, LDAP will not throw an ‘already in group’ exception, but just calculate the new expiration time for the entry.

How it’s stored in the Active Directory database:

As a quick starter:
We do not store data by its data base path (distinguishedName) but by an incrementing numeric unique identifier. These identifiers, which are called Distinguished Name Tags (DNTs), are not replicated or otherwise visible to other domain controllers.
As objects are created (directly or from replication) on a domain controller, a unique value is used.

The table below shows the data tables in the AD database and their usage.

Table

Tree

Data

Usage

datatable column, index objects -> attributes
datatable long_table column, index big data attributes
hiddentable column like system table in SQL
link_table column, index linked attribute pair assoc
link_history_table column, index linked attribute pair assoc history
sd_table column, index single instance storage for Security Descriptors
sdproptable column, index sd_table properties
sdpropcounttable column sd_table properties count
quota_table column, index AD quotas
Quota_rebuild_progress_table column AD quota progress

 

Structure of the datatable – for contoso.com:

DNT

Parent DNT

RelativeDN

RDNType
0 null com DC
1 0 contoso DC
165 1 Configuration CN
212 165 Services CN
287 212 Shadow Principal Configuration CN
2721 1 GoldAdmins OU
3436 287 ShadowDA.production CN
3445 2721 GoldAdmin.production CN

 

The distinguishedName of an object is calculated by following the Parent DNT chain and combining RDNType and RelativeDN– examples:

  • User account to be used for administration in production forest:
    CN=GoldAdmin.production,OU=GoldAdmins,DC=contoso,DC=com

    • DNT = 3445 –> DN: CN=GoldAdmin.production
    • PDNT = 2721 –> DN: CN=GoldAdmin.production,OU=GoldAdmins
    • PDNT = 1 –> DN: CN=GoldAdmin.production,OU=GoldAdmins,DC=contoso
    • PDNT = 0 –> DN: CN=GoldAdmin.production,OU=GoldAdmins,DC=contoso,DC=com
  • Shadow principal shadowing production Domain Admins:
    CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services,CN=Configuration,DC=contoso,DC=com

    • DNT = 3436 –> DN: CN=ShadowDA.production
    • PDNT = 287–> DN: CN=ShadowDA.production,CN=Shadow Principal Configuration
    • PDNT = 212–> DN: CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services
    • PDNT = 165–> DN: CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services,CN=Configuration
    • PDNT = 1 –> DN: CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services,CN=Configuration,DC=contoso
    • PDNT = 0 –> DN: CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services,CN=Configuration,DC=contoso,DC=com

 

The member attribute is part of a linked attribute pair in Active Directory. Linked attributes are pairs of attributes in which the system calculates the values of one attribute (the back link) based on the values set on the other attribute (the forward link) throughout the forest.
A back-link value on any object instance consists of the distinguishedNames (DN) of all the objects that have the object’s DN set in the corresponding forward link.
The attributes ‘member’ and ‘memberOf’ are a pair of linked attributes, where ‘member’ is the forward link and ‘memberOf’ is the back link. The link is identified by the linkID values of two attributeSchema definitions:

  • The linkID of the forward link is an even, positive, nonzero value.
  • The back link linkID is the forward linkID plus one.

The LinkID for ‘member’ is 2 and the linkID for ‘memberOf’ is 3.

Linked attribute data association is stored in the link table. We do not store the distinguishedName here, but the DNTs.

Structure of the link table – exemplary showing membership of GoldAdmin.production (DNT 3445) in ShadowDA.production (DNT 3436), with naming context DNT (NC_DNT) for the configuration naming context (DNT 165) and an expiration time set to 2017-05-09 06:09:17:

link_DNT

link_base

backlink_DNT

NC_DNT

expiration_time

3436 member 3445 165 2017-05-09 06:09:17

Column expiration_time is the place where NTDS is checking for necessary membership revocation and KDC for earliest expiration time of an user’s membership for TGT End Time calculation.

Just for completeness – NTDS follows the link_DNT –> backlink_DNT chain to calculate direct and nested group membership – example:

  • userX (DNT 5610) is member of group2 (DNT 5239)
  • group2 (DNT 5239) is member of group1 (DNT 5112)

-> userX is member of group2 and group1

link_DNT

link_base

backlink_DNT

NC_DNT

expiration_time

5112 member 5239 1
5239 member 5610 1

 

OK – this were the basics – now to another fun part – the coding.

PoSh code sample to create the PAM trust between production and admin forest:

This code must be run in the production forest with Domain Admin privileges. It creates the unidirectional forest trust between production and admin forest where production trusts admin forest.

Mandatory command line argument:

  • –ForestName ‘FDQN of the admin forest’

To create the trust we use the following classes and methods from the System.DirectoryServices.ActiveDirectory (S.DS.AD) namespace:

  • class DirectoryContext
  • class Forest
  • method Forest::CreateTrustRelationship

An overview of the available classes and their wrappings in .Net to communicate with Active Directory:

       

Unfortunately the manipulations to trustAttributes flag is not possible per S.DS.AD implementation or direct LDAP modification, we must use some APIs – which is definitely no big fun in PoSh – therefore we call netdom.exe twice to enable sIDHistory and PIM trust for this trust on the trusting side (production).

More details in the code comments.

 

#region params

param
(
    [Parameter(Position=0,Mandatory=$true)]
    [string]$ForestName    
)

#endregion

#region functions

#region retreive a DC in current forest in closest site
#endregion
function GetDC([string]$forestName)
{
    [string]$ret = [String]::Empty

    $dc = $null

    if ($forestName -ne $null)
    { $dc = Get-ADDomainController -DomainName $forestName -NextClosestSite -Discover }

    else
    { $dc = Get-ADDomainController -NextClosestSite -Discover }

    $ret = $dc.HostName.Value

    return $ret
}

#region create forest trust
# against target forest with given trustdirection and credentials
#endregion
function CreateForestTrust([string]$targetForest, [System.DirectoryServices.ActiveDirectory.TrustDirection]$direction, [pscredential]$targetUser)
{
    [bool]$created = $false

    # get a DC in current forest
    $dcinfo = GetDC

    try
    {
        # initiate.Net context object from retrieved DC
        [System.DirectoryServices.ActiveDirectory.DirectoryContext]$ctx = `
            New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('DirectoryServer', $dcinfo)         &nbsp;       

        # initiate .Net forest object from context
        [System.DirectoryServices.ActiveDirectory.Forest]$forest = `
            [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ctx)

        # initiate .Net context object for target forest
        [System.DirectoryServices.ActiveDirectory.DirectoryContext]$tctx = `
            New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $targetForest ,$targetUser.UserName, $targetUser.GetNetworkCredential().Password) 

        # initiate .Net forest object for target forest from context
        [System.DirectoryServices.ActiveDirectory.Forest]$target = `
            [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($tctx) 

        $created = $true
    }

    catch
    {
        Write-Host "Failed to initialize necessary .Net classes : $($_.Exception)"
    }

    if ($created -eq $true)
    {
        $created = $false

        try
        {
            # create foresttrust 
            $forest.CreateTrustRelationship($target, $direction)

            $created = $true

            Write-Host "Created trust between $targetForest and $($forest.Name)"
        }

        catch
        { Write-Host "Failed to create trust between $targetForest and $($forest.Name) : $($_.Exception)" }
    }

    if ($created -eq $true)    
    {
        # Unfortunately we canot edit trustAttributes by LDAP operation -&gt;
        #     we must use LSA APIs -&gt; no big fun in PoSh.
        # Therefore we call netdom.exe here-

        # enable sidhistory
        Write-Host "Setting TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL (sIDHistory) trustAttributes bit"
                
        netdom trust "$($forest.Name)" "/Domain:$targetForest" /EnableSidhistory:Yes
        
        # enable PAM trust
        Write-Host "Setting TRUST_ATTRIBUTE_PIM_TRUST (PAM)&nbsp;trustAttributes bit"
                
        netdom trust "$($forest.Name)" /Domain:$targetForest /EnablePIMTrust:Yes
 
    }    
}

#endregion

#region main

[string]$targetforest = $ForestName

# get target forest admin credentials
[PSCredential]$targetuser = Get-Credential -Message "Provide administrative creds for $targetforest"

$targetuser.UserName

# trust direction InBound - we trust the red (bastion) forest
[System.DirectoryServices.ActiveDirectory.TrustDirection]$trustdir = [System.DirectoryServices.ActiveDirectory.TrustDirection]::Outbound

CreateForestTrust $targetforest $trustdir $targetuser 

#endregion

 

PoSh code sample to create a shadow principal in the admin forest, shadowing a group (default Domain Admins – RID = 512) from the production forest:

This PoSh must be run in the admin forest with Domain Admin privileges of the forest root domain or Enterprise Admin privileges.

Mandatory command line argument:

  • –ProdForest ‘FDQN of the production forest’

Optional command line arguments:

  • –ProdGroupRid ‘RID of the production forest group to shadow’ (default = 512 –> Domain Admins)
  • –ForceFeature ‘$true / $false’ (default = $true–> calls Enable-ADOptionalFeature ‘Privileged Access Management Feature’)
     

In this sample code we mainly use PoSh AD-Cmdlets – only the casting from the Domain Admin’s objectSid to a value that can be used to be set in msDs-ShadowPrincipalSid attribute of the shadow principal is done with .Net (System.Security.Principal.SecurityIdentifier::GetBinaryForm).

Workflow:

  • get domain SID of production forest and append given group RID (-> the SID to be used in msDs-ShadowPrincipalSid)
  • get default msDs-ShadowPrincipalContainer from configuration naming context of admin forest
  • [Optional] enable PAM feature
  • create shadow principal in default msDs-ShadowPrincipalContainer with the SID of the production forest group to shadow

More details in the code comments.

 

#region params

param
(
    # the production forest to contact for it's domain sid
    [Parameter(Mandatory=$true)]
    [string]$ProdForest,

    # the RID of the production forest group to shadow
    [Parameter(Mandatory=$false)]
    [string]$ProdGroupRid = "512",

    # should we call Enable-ADOptionalFeature Privileged Access Management Feature ?
    [Parameter(Mandatory=$false)]
    [bool]$ForceFeature = $true
)

#endregion

#region functions

#region retrieve default msDs-ShadowprincipalContainer 
# from configuration naming context
#endregion
function GetShadowPrincipalContainer()
{
    [string] $ret = "CN=Shadow Principal Configuration,CN=Services,"

    # get a DC from current forest
    $dc = Get-ADDomainController -NextClosestSite -Discover
    
    # send UDP pipng to port 389 -&gt; rootDSE call
    $rootDSE = Get-ADRootDSE -Server $dc.HostName.Value

    # read configurationNamingContext attribute
    $ret += "$($rootDSE.configurationNamingContext)"

    return $ret
}

#region get SID of the production forest group to shadow
# we ask for production forest domain sid and append given group RID
#endregion
function GetGoldGroupSid([string]$goldForest, [string]$groupRid)
{
    [byte[]] $ret = $null

    # get a DC in production forest
    $dc = Get-ADDomainController -DomainName $goldForest -NextClosestSite -Discover

    # send UDP pipng to port 389 -&gt; rootDSE call
    $rootdse = Get-ADRootDSE -Server $dc.HostName.Value

    # read defaultNamingContext attribute -&gt; domain naming context
    [string] $defnc = $rootdse.defaultNamingContext

    # get domain object - calling for objectSid attribute
    $dom = Get-ADObject $defnc -Server $dc.HostName.Value -Properties objectSid

    # build goup SID
    [string]$sddl = "$($dom.objectSid.Value)-$groupRid"

    # cast to SecurityIdentifier .Net object
    [System.Security.Principal.SecurityIdentifier] $sid = [System.Security.Principal.SecurityIdentifier]$sddl

    Write-Host "Gold SID: $($sid.Value)"

    # resize byte array
    $ret = New-Object byte[] $sid.BinaryLength

    # tranlsate SecurityIdentifier .Net object to byte array
    $waste = $sid.GetBinaryForm($ret, 0)
    
    return $ret
}

#region enable Privileged Access Management Feature (in red forest -&gt; W2K16 AD)
#endregion
function EnableFeature()
{    
    try
    { 
        Enable-ADOptionalFeature "Privileged Access Management Feature" –Scope ForestOrConfigurationSet -Target $redforest
    }

    catch { }
}

#endregion

#region main

# Enable-ADOptionalFeature can be skipped by passing -ForceFeature $false
if ($ForceFeature -eq $true)
{
    EnableFeature
}


# get Domain Admins Sid from gold
[byte[]] $bsid  = GetGoldGroupSid $ProdForest $ProdGroupRid


# get path to CN=Shadow Principal Configuration,CN=Services,configNC
[string] $path = GetShadowPrincipalContainer

Write-Host "Shadow Principal Configuration: $path"

# build shadow principal name  for Gold group
[string] $goldshadow = "GoldShadow$ProdGroupRid.$($ProdForest)"

Write-Host "Creating $goldshadow"

try
{
    # create shadow principal
    New-ADObject -Type 'msDs-ShadowPrincipal' -OtherAttributes @{ 'msDs-ShadowPrincipalSid' = $bsid } -Name $goldshadow -Path $path

    Write-Host "Created $goldshadow"
}

catch
{
    Write-Host "Failed to create $goldshadow ($($_.Exception.Message))"
}

#endregion

 

PoSh code sample to (temporarely) nest an account from the admin forest into a shadow principal object of the admin forest:

This script must be run in the admin forest with Domain Admin privileges of the forest root domain or Enterprise Admin privileges.

Mandatory command line arguments:

  • –GoldCardUser ‘the cn of the user to be added as member to the shadow group
  • –ShadowGroup ‘the cn of the shadow group

Optional command line arguments:

  • –TTLMinutes ‘minutes the membership should be valid’ (default = 30)

Note: The given TTL must not be less than the max time skew for Kerberos authentication (default = 5 mins) or 0.

Workflow:

  • get default msDs-ShadowPrincipalContainer from configuration naming context of admin forest
  • find shadow principal in default msDs-ShadowPrincipalContainer
  • build add request string depending on whether we passed a TTL time span or not
  • send add modification request for member attribute

More details in the code comments.

 

#region params

param
(
    # the cn of the user to be added as member to the shadow group
    [Parameter(Mandatory=$true)]
    [string]$GoldCardUser,

    # the cn of the shadow group
    [Parameter(Mandatory=$true)]
    [string]$ShadowGroup,

    # how many minutes should the membership be valid?
    # NOTE - if the remaining TTL is less than the max time skew for Kerberos auth 
    # (default 5 mins) and not 0 -&gt; no authentication will be possible
    [Parameter(Mandatory=$false)]
    [int]$TTLMinutes = 30
)

#endregion

#region functions 

#region retrieve default msDs-ShadowprincipalContainer 
# from configuration naming context
#endregion
function GetShadowPrincipalContainer()
{
    [string] $ret = "CN=Shadow Principal Configuration,CN=Services,"

    # get a DC from current forest
    $dc = Get-ADDomainController -NextClosestSite -Discover
    
    # send UDP pipng to port 389 -&gt; rootDSE call
    $rootDSE = Get-ADRootDSE -Server $dc.HostName.Value

    # read configurationNamingContext attribute
    $ret += "$($rootDSE.configurationNamingContext)"

    return $ret
}

#region get string to be used in LDAP add modification for member
#endregion
function BuildAddRequest([string]$goldCardCN, [int]$ttlMins)
{
    [string] $ret = [String]::Empty

    # get goldcard admin for gold forest
    $goldmember = Get-ADUser -Filter { CN -eq $goldCardCN }

    # we have a TTL ?
    if ($ttlMins -ne 0)
    {
        $ttl = New-TimeSpan -Minutes $ttlMins

        # syntax must be <TTL=TimeToLife in seconds,path to member>
        $ret = "<TTL=$($ttl.TotalSeconds),$($goldmember.DistinguishedName)>"
    }

    else
    {
        # no TTL -&gt; standard add modification call
        $ret = $goldmember.DistinguishedName
    }


    return $ret
}

#endregion

#region main

# get path to CN=Shadow Principal Configuration,CN=Services,configNC
[string] $path = GetShadowPrincipalContainer

# find gold shadow group in CN=Shadow Principal Configuration,CN=Services,configNC
$goldshadow = Get-ADObject -Filter { CN -eq $ShadowGroup } -SearchBase $path -SearchScope Subtree


# build value to ad to member attribute
[string] $addreq = BuildAddRequest $GoldCardUser $TTLMinutes


# add membership for goldcard admin with TTL
Set-ADObject $goldshadow -Add @{ member = $addreq }

# can only be used for group objects in a domain naming context:
#Add-ADGroupMember 'testttl' -Members $goldmember -MemberTimeToLive (New-TimeSpan -Minutes $TTLMinutes)

 

All the best and have fun PAMing.

Michael

PFE | Have keyboard. Will travel.

 

 

Comments (4)

  1. I know this website presents quality based articles or reviews and other
    data, is there any other website which offers these kinds of stuff in quality?

  2. Michael Pocock says:

    Is this supposed to work with a child domain in a trusted forest. I have a scenario where someone needs to do a DC Promotion in a sub domain using PAM. The user is a member of the EA shadow group and was able to fully create a sub domain, but once authentication switches to the sub domain, the group membership of any PIM groups is stripped.

  3. Nice one Michael. I will trying this in a lab. That is, after you fix the couple of minor syntax errors that slipped in :)
    Look at the HTML special characters like &lt and friends. That’s a weakness of this particular syntax prettifier, you need to doublecheck those.

    1. Hi Willem, my friend

      thx for pointing this pita in the syntaxhighlighter plug-in out – ‘repaired’ .- )

      Groeten
      Michael

      PFE | Have keyboard. Will travel.

Skip to main content