Search This Blog

Aug 7, 2026

How to identify sequence of events of a delegated account logon and resource access

 

Sequence of events 

  1. T0, TGT requested and granted 

  2. T1, where T1-T0 < 10hours, user uses app (I don't want to use log into app because I don't know if there was any sort of logon happening) 

  3. TGT was renewed as (let's call our new TGT ST1).

  4. ST1 then requested another ST (let's call it ST2) 

  5. ST2 was used to access resource

How to track what kerberos ticket was used to renew or to request a new ticket?

In event 4768  (TGT request), 4769 (ST request) and event 4770 (renewal), you can use “Response Ticket Hash” (the hash of a resulting ticket) and “Request Ticket Hash” (the requesting ticket hash) to chain events together. 



How to interpret  event 4770 

Although the wording is 'service ticket was renewed', if the requesting hash is one of TGT, and the “service name” is krbtgt, then it is really a TGT renewal instead of a ST renewal 

Why the Wording Exists & What It Means

  1. The Wording Context: To the KDC, krbtgt is formally stored as a Service Principal Name (krbtgt/DOMAIN.COM). From the operating system's internal logging template perspective, requesting an extension on the krbtgt SPN gets stamped with the generic message "A Kerberos service ticket was renewed", even though functionally it is a TGT renewal.

  2. The Flow:
    Existing TGTEvent 4770 (Renew)​Renewed TGTEvent 4769 (TGS-REQ)​Real Service Ticket (ST)

  • Step 1 (Event 4770): The client or application server submits its existing, valid TGT (krbtgt) back to the KDC to refresh its expiration clock.

  • Step 2 (Event 4769 #1): The client immediately presents that freshly renewed TGT (krbtgt) as the Request Ticket Hash to ask for a "real" Service Ticket (e.g., targeting DC$ for LDAP/RPC, or an application SPN).

  • Step 3 (Event 4769 #2, if present): That initial ST is then passed along in a S4U2Proxy / constrained delegation chain as a Request Ticket Hash to mint a second downstream ST.



To improve the sequence of events with event IDs: 

  1. TGT granting (4768), this updates lastLogon on DC where it happens 

  2. TGT renewal (4770) 

  3. Request ST (4769)


Step

Action

Event ID & Details

lastLogon Behavior

1. Initial Authentication

TGT Granting

Event 4768 (AS-REQ)


• User authenticates with credentials/cert.


• KDC issues initial TGT (krbtgt).


• Generates initial Response Ticket Hash.

UPDATED


Stamps lastLogon on the specific DC that processed the request.

2. Session Extension

TGT Renewal

Event 4770 (TGS-REQ for krbtgt)


• Client submits valid TGT before 10-hr expiration.


• KDC extends TGT validity clock.


• Windows logs this as "A Kerberos service ticket was renewed".

NOT UPDATED


TGT renewals explicitly bypass lastLogon updates to reduce DC write overhead.

3. Resource Access

Request Service Ticket (ST)

Event 4769 (TGS-REQ for SPN)


• Client presents renewed TGT (Request Ticket Hash matches 4770/4768 Response Hash).


• KDC issues Service Ticket (Response Ticket Hash) for target SPN (DC$, HTTP/, LDAP/, etc.).

NOT UPDATED


Requesting service tickets never updates lastLogon.

Key Takeaway 

Because Steps 2 and 3 can loop repeatedly for up to 7 days (or indefinitely via application-managed session caches) without ever returning to Step 1:

Step 1 (4768)⟶[Step 2 (4770)⟶Step 3 (4769)] (Repeats indefinitely)

A user can actively generate 4770s and 4769s on a DC every single day, yet their lastLogon attribute will remain completely frozen at the timestamp of their original Step 1 event.


Let’s suppose the app uses the ST to read group membership - this action seems to create a sequence of events alongside ticket renewal/request events 

  1. 4624, account logon 

  2. 4627, read group membership 

  3. 4634, account logoff

What Is Happening (The 6-Event Lifecycle)

When the application uses a Service Ticket to query group membership on a Domain Controller over LDAP, Active Directory handles it in two separate phases: Kerberos Ticket Exchange (KDC level) followed by Network Authentication & Session Execution (DC OS/LSASS level).

[PHASE 1: Kerberos Ticket Exchange (KDC Service)]

  1. Event 4768: TGT requested

  2. Event 4770: TGT is renewed (TGT_Hash_A -> TGT_Hash_B)

  3. Event 4769: Service Ticket (ST) requested for DC$ / LDAP using TGT_Hash_B


[PHASE 2: LDAP Query Execution (DC Operating System / LSASS)

  1. Event 4624: Network Logon (Type 3) onto the DC using the ST

  2. Event 4627: Group Membership evaluation (Read user's AD groups for token

  3. Event 4634: Account Logoff (LDAP connection closed)

All phase 2 events should have same logon ID, 

Why Linking Phase 1 (Kerberos) to Phase 2 (Logon) Is Difficult

Your observation that there is no clean, direct identifier (like a single shared GUID) connecting the 4769/4770 events to the 4624/4627/4634 events is a known structural gap in Windows Security auditing.

  1. Layer Separation:

  • Events 4768 / 4769 / 4770 are emitted by the KDC service (Kerberos ticket issuer).

  • Events 4624 / 4627 / 4634 are emitted by LSA / Security Subsystem (Resource server evaluating access).

  1. No LogonId in Kerberos Events: The KDC does not assign or know about the Windows TargetLogonId (0x...) that LSA creates when Event 4624 fires.

  2. Ticket Hashes Are Not Logged in 4624: The Windows 4624 logon event records details like IP address, logon type, and user name, but it does not log the Request Ticket Hash of the Kerberos ticket used to authenticate.

How to Correlate the Two Sets of Events

  1. Account_Name should be same

  2. Time should be very close to each other

  3. Source Network Address should be same as Client Address

  4. 4624/4627/4634 have same logon ID



Feb 12, 2026

PowerShell - Beware What Your Function Returns

 Your PowerShell script could return something totally different from what you would have thought. Considering below function/script:

function demo-returnValues {
    "1"
    "2"
    return [string]"3"
}

$dn = demo-returnValues
write-host "return Type: $($dn.gettype().fullname)"
write-host "element count $($dn.count)"
write-host " Values:"
$dn|foreach {write-host "[[$_]]"}

It doesn't return a string of value "3"! Rather it returns an array of 3 elements. See its output:

return Type: System.Object[]
element count 3
Values:
[[1]]
[[2]]
[[3]]

This is because anything within a function that emits value that are not caught will be added to a pipeline. What you intend to return is also added to this pipeline. Finally it is this pipeline that gets returned.


 To fix this, you can do one of below 3

  1. Assigned to variable
  2. Suppressed by [void]
  3. piped to out-null
Revised function below. This returns only a string of "4"

function demo-returnValues {
    [void]"1"
    $uselessVar = "2"
    "3" | out-null
    return [string]"4"
}


Nov 7, 2025

Kerberoasting simple facts

 

Prerequisites for possible attack

  1. Attacker already possess an account in domain
  2. Attacker has access to KDC
  3. Targeted account must have SPN

 

Attack path:

  1. Attacker logs in with account A
  2. Attacker request TGS against account B that has SPN, using SPN to obtain ticket
  3. Attacker dumps the ticket and crack it offline
  4. Attacker knows password of user B

 

Prevention:

  1. Strong passwords
  2. Disable RC4 encryption support for Kerberos tickets (this can be done on DC side and/or user account side)
    1. On DCs, use GPO to disable RC4 support “Security Options -> Network security: Configure encryption types allowed for Kerberos”
    2. On user account, attribute msDS-SupportedEncryptionsTypes
  3. Normal account should NOT have SPNs
  4. Use gMSA so password is random and strong

 

Detection:

  1. Spikes in EventID 4769 for same SPN
  2. Spikes in EventID 4769 from a normal user account

Jun 26, 2025

Entra ID extension attributes

 There are 4 types of extension attributes

  1. Extension attribute 1-15. This is a legacy borrow from on-prem extension attribute introduced by Exchange
  2. Directory Extension (tied to an application, but can be consumed by other applications)
  3. Schema Extension (tenant-wide)
  4. Open Extension
Please see https://learn.microsoft.com/en-us/graph/extensibility-overview

How to include "directory extension attribute" (type #2 above) in claims

  1. need to use Graph API to create claim mapping policy
  2. use below POST command and  JSON body of the Graph call

POST https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies
{
  "definition": [
    "{ 
      \"ClaimsMappingPolicy\": {
        \"Version\":1,
        \"IncludeBasicClaimSet\":\"true\",
        \"ClaimsSchema\": [
          {
            \"Source\":\"user\",
            \"ID\":\"extension_hostingAppID_deviceID\",
            \"JwtClaimType\":\"deviceID\"
          }
        ]
      }
    }"
  ],
  "displayName": "IncludeDeviceID",
  "isOrganizationDefault": false
}
  1. Make a note of returned policy ID for steps followed
  2. make a POST call as below to assgin the policy to consuming app
command: POST 
https://graph.microsoft.com/v1.0//servicePrincipals/{id}/claimsMappingPolicies/$ref

            where ID is objectID of SPN 

      Body
      {
        "@odata.id": "https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/policyID"
      } // where id is policy ID
    1. Pay attention to different GUID used. In the actual policy, appID of hosting app is used(remove dashes); In POST command, objectID of consuming app is used
    2. Last step, enable app to accept custom claim
    PATCH https://graph.microsoft.com/v1.0/applications/{objID of app}
    Content-type: application/json

    {
      "api": {
        "acceptMappedClaims": true,
        "requestedAccessTokenVersion": 2
      }
    }



     

    Jun 2, 2025

    Add Google as IdP for Entra applications

     External IDP (e.g.Google) | Application (e.g. Azure Entra)

    ------------------------- | ------------------------------

    create an app in google dev console | Configure Google as IdP

    gets client ID ---> | fill in client ID

    gets client secrect ---> | fill in client secrect

    fill in redirect URIs | <--- Find URIs from MS official website

    After above, google can be added as an IdP in user flow.


    reference:

    https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-google-federation-customers

    Mar 14, 2025

    Running AD cmdlets within foreach parallel script block



     Powershell's parallel foreach script block runs in its own runspace so anything defined outside of the block is not visible in it. A few steps to make AD cmdlets work:


    1. Import activedirectory module within the block. It may throw warning "Error initializing default drive", which can be safely ignored but you will have to specify DC to establish connection via -server parameter in get-ad* cmdlets
      1. get-aduser -server "DC1.foobar.com" -.....
    2. The runspace won't have your credential from main session either so you have to transfer credential explicitly into script block

      $cred = get-credential
      $users | foreach -parallel {
            get-aduser -identity $_.samAccountName -credential $using:cred
      }
    3. If there are too many concurrent connections to AD, some connections may fail. Tweak to find the ThrottleLimit that works for you. 
    4. Use inputObject to return result ---> This is very handy as other ways to return value is complicated
      $_ | add-member -notepropertyname "pn" -notepropertyValue "pv"
    5. Putting it altogether
      $cred = get-credential
      $users | foreach -parallel {
            import-module ActiveDictory
            $u=get-aduser -identity $_.samAccountName -credential $using:cred
            $_ | add-member -NotePropertyName "DN" -NotePropertyValue $u.distinguishedName
      } -ThrottleLimit 5

    [UPDATE]

    So limiting the number of threads is not ideal, with the number as long as 2, there is still chance where connection be refused by DC, not to mention we lost most of benefit if the number is too low.

    One workaround is to make sure only one runspace connects to a particular DC at a time. This can be achieved by using a file as a lock. First get list of all DCs in a domain, then when a connection is made to a DC, obtain an exclusive handle to a file that represents the DC (e.g. "dc01.lock"). Once finish access the DC, release the lock file.

    # Acquire lock before connecting to a DC
    $server = $null
    while ($null -eq $server){                   
      foreach ($DC in $using:dcs) {
        try {
              $lockFile = [system.io.file]::open("c:\temp\$($DC).lock",
                             'OpenOrCreate','ReadWrite','None')
              $server = $DC
              break
        }catch{                }
    }
    if($null -eq $server) {Start-Sleep -Milliseconds 50}
    }
    try {
        get-aduser -server $server ....

        # Release lock
        $lockFile.close()
        remove-item "c:\temp\$($server).lock" -force -erroraction silentlyContinue
       
    }catch {}



    There are other ways to implement a lock, such as described in Dave's blog, but above file lock works very well and is less complicated.

    Jan 14, 2025

    Why it's so easy to confuse between OAuth and OpenID Connect

     OAuth is an authorization protocol that wasn't designed for authentication. All it gets ( and cares) is an access token from resource server that gives it access to certain resources. Technically it doesn't know (and it doesn't need to know) the owner behind those resources. 

    The reason that OAuth often seems to be an authentication protocol - and tons of applications do use it for authenticatino purpose - is that in all use cases of OAuth, the resource it was granted access to almost always contain something that can be used/seen/considered as an piece of ID, such as an email address. However, strictly speaking, just because the client (requestor) has obtained an email (or other ID-related info), it shouldn't assume it as a true identity.  

     For true authentiction, applications should use OpenID Connect, which is just an extension of OAuth. The extension provides an ID token instead of an access token.

    Apr 29, 2024

    What is "alias" type in whoami output?

     You probably noticed that besides "well-known group" and "group" in the output of whoami /all command, there is also another type called "alias". There was much result in googling to tell what this exactly is.

    After much searching, find this document: SAM Remote Protocol - not that kind of doc you'd think of for the question we have above. Anyhow, even info in this doc is obscure: 

    alias object: See resource group

    then:

    resource group: A group object whose membership is added to the authorization context only if the server receiving the context is a member of the same domain as the resource group.

    Translation:

    An alias is a domain local group from same domain as the resource server where it receives the context

    Feb 28, 2024

    AzureAD module for Graph Notes

    1.  How to install AzureAD module without internet connection
      1. Download nupkg file from PowerShell Gallery
      2. for module that has dependences, you can download all nupkg files into same folder
      3. copy nupkg file to a dedicated folder
      4. Assuming you have NuGet available, run "Register-PSRepository -Name <pickAName4YourRepository> -SourceLocation <absolute path to nupkg file>"
      5. You can now "find-module -repository <repositoryName>"
      6. "Install-Module -Name <moduleName>"
      7. placeholder
    2. Install modules behind company proxy
      1. run below as admin
      2. [System.Net.WebRequest]::DefaultWebProxy.Credentials = Get-Credential
      3. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    3. ODATA filter syntax
      1. Get-AzureADUser -Filter "proxyAddresses/any(c:c eq 'smtp:user@domain.com')"
      2. Get-AzureADUser -Filter "Department eq 'HP'"
      3. Get-AzureADDirectoryROle -filter "DisplayName eq 'application administrator'"
      4. Find reference on Oasis website
      5. placeholder
    4. Connect to graph behind proxy
    # [NOTE] Set up proxy. Below works for PS 5
    [System.Net.WebRequest]::DefaultWebProxy.Credentials = Get-Credential
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

     

    # Powershell 7 is using [System.Net.HttpWebRequest]::DefaultWebProxy instead of [System.Net.WebRequest]
    [System.Net.HttpWebRequest]::DefaultWebProxy = New-Object System.Net.WebProxy($null)  
      # this may work in companies where it can authenticate automatically
    [System.Net.HttpWebRequest]::DefaultWebProxy.Credentials = Get-Credential
    # Prompt for credential in companies that needs authN to use proxy

     [System.Net.HttpWebRequest]::DefaultWebProxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials 

    # this can be used when proxy uses your default credential (it could be your domain credential, it could be your Azure cendenital, depending on your environment)

    1. placeholder

    Jan 31, 2024

    [PowerShell] When ExpandProperty is not good enough

    The ExpandProperty parameter in select-object cmdlet is useful to view full values of a compound property (e.g. when a property's value is an array or an object). However the limitation is also obvious. It accepts only one property, so we are forced to write a script block to process all results, using a different way to convert/expand properties one by one, before we can finally assembly the output.

    The other way to do it is to use inline expression. See below

    $targetedProperties=@(
        samaccountname,
        @{l='membership'; e={$_.memberof}}
        @{l='allEmailAddresses'; e={$_.proxyAddresses}}
    ) 
    $uObj = get-aduser 'johnDoe' -properties *
    $expandedObj = $uObj | select $targetedProperties
     



    Array that includes most meaningful AD attributes for admins


    $meaningfulP = @(
        "AccountExpirationDate"
        #"accountExpires" # above converted value is readable to human - blank means never
        "AccountLockoutTime"
        "AccountNotDelegated"
        "AllowReversiblePasswordEncryption"
        #"BadLogonCount" # these are temporary values that are reset by AD periodically
        #"badPasswordTime"
        #"badPwdCount"
        "c"
        "CannotChangePassword"
        "CanonicalName"
        "City"
        "CN"
        "co"
        "codePage"
        "Company"
        "Country"
        "countryCode"
        "Created"
        "createTimeStamp"
        "Deleted"
        "Department"
        #"departmentNumber"
        @{l="deptNumber";e={$_.departmentNumber}}
        "Description"
        "DisplayName"
        "DistinguishedName"
        "Division"
        "EmailAddress"
        "EmployeeID"
        "EmployeeNumber"
        "employeeType"
        "Enabled"
        "extensionAttribute12"
        "extensionAttribute14"
        "extensionAttribute2"
        "extensionAttribute3"
        "extensionAttribute4"
        "extensionAttribute5"
        "extensionAttribute6"
        "extensionAttribute8"
        "extensionAttribute9"
        "Fax"
        "GivenName"
        "HomeDirectory"
        "HomedirRequired"
        "HomeDrive"
        "HomePage"
        "HomePhone"
        "Initials"
        "instanceType"
        "isDeleted"
        "l"
        "LastBadPasswordAttempt"
        "LastKnownParent"
        "LastLogonDate"
        "legacyExchangeDN"
        "LockedOut"
        "lockoutTime"
        "logonCount"
        "LogonWorkstations"
        "mail"
        "mailNickname"
        "Manager"
        #"MemberOf"
        @{l='membership';e={($_.Memberof)[0..20]}} #to prevent this value to become too large to fit into Excel cell limit
        "MNSLogonAccount"
        "MobilePhone"
        "Modified"
        "modifyTimeStamp"
        "Name"
        "ObjectCategory"
        "ObjectClass"
        "Office"
        "OfficePhone"
        "Organization"
        "OtherName"
        "PasswordExpired"
        "PasswordLastSet"
        "PasswordNeverExpires"
        "PasswordNotRequired"
        "physicalDeliveryOfficeName"
        "POBox"
        "PostalCode"
        "preferredLanguage"
        "ProfilePath"
        "ProtectedFromAccidentalDeletion"
        #"proxyAddresses"
        @{l='allEmailAddr';e={$_.proxyAddresses}}
        "SamAccountName"
        "sAMAccountType"
        "ScriptPath"
        "sDRightsEffective"
        #"ServicePrincipalNames"
        @{l='SPN';e={$_.ServicePrincipalNames}}
        "SmartcardLogonRequired"
        "sn"
        "st"
        "State"
        "StreetAddress"
        "Surname"
        "targetAddress"
        "Title"
        "TrustedForDelegation"
        "TrustedToAuthForDelegation"
        "UseDESKeyOnly"
        "userAccountControl"
        "UserPrincipalName"
        "whenChanged"
        "whenCreated"
    )

    Dec 2, 2023

    Typescript with VS code notes

    IDE related

    IDE - launch profile

    1. To add a different launch profile (i.e. run same source file with different settings, or specify a different start script etc.). Open launch.json file in editor, click on "Add Configuration" button. Resulting file below
          // Sample launch.json
          "version": "0.2.0",
          "configurations": [
              {
                  "type": "node",
                  "request": "launch",
                  "name": "Run Dist/index.js",
                  "program": "./dist/index.js",
                  "envFile": "${workspaceFolder}/.env",
                  "outFiles": [
                      "${workspaceFolder}/**/*.js"
                  ]
              },
              {
                  "type": "node",
                  "request": "launch",
                  "name": "Run testSMS.js",
                  "program": "./dist/testSMS.js",
                  "envFile": "${workspaceFolder}/.env",
                  "outFiles": [
                      "${workspaceFolder}/**/*.js"
                  ]
              }
          ]
      }
    2. Select a launch item to run
      1. Click "Run & Debug" button
      2. at top left corner, click on dropdown list besides green triangle, it should list 2 launch items listed in above sample file, one called "run index.js", the other called "run testsms.js".
      3. Select either one to run
    3. Any environment variables you specified in envFile above, you will have to define them as well in other running environments. For example, if you run the script from command line using "node.exe" then you have to "set env variables". If run in Azure app service, it should be defined under app service, configuration \ application settings section
    4. In launch.json, "type" could be "node" or "node-terminal" etc., it determines how/where screen output is sent. Using type=node together with below so outputs are sent to Debug console instead of Terminal console. Advantages of Debug console: filtering, setting breakpoint, coloring etc.
          "console": "internalConsole",            // <--- Force Debug Console
          "outputCapture": "std"                   // <--- Captures std out and err
    5. Bulletpoint placeholder
    Source code version control

    How to change code for a github project
    1. Click on the "source control" icon in left hand navigation bar (ctrl-shift-g)
    2. click "clone repository"
    3. select "clone from github(remote source code)"
    4. save it to a local folder
    5. You can run 'npx tsc' to compile

    6. Once finish coding, you can commit etc
    How to make a local copy of published module/library, modify/debug locally, then publish when done
    1. Make a local copy of the module and link to it
      1. in main app, "npm install moduleName", this will download and update dependency
      2. in module, run "npm link" //this link command is global, so you only need to make one local copy, and it's available machine wide. In all other places you need this module, just run next command "npm link moduleName"
      3. in main app, run "npm link moduleName"
    2. In module, once finish testing
      1. "npm ver #newVersionNumber" //Increase module version
      2. "npm publish"                             // publish to npm repository
      3. "npm unlink"                               // delete link
    3. In main app
      1. "npm unlink moduleName"        //disconnect link
      2. (optional) update package.json to use new module version
      3. "npm install moduleName"
      4. verify that new version is listed in package.json dependency section
    4. Other related commands
      1. npm ls -g --depth=0 --link=true  <To see linked library globally>
      2. npm ls --link=true                       <to see what's linked in your current project>
      3. npm ls grage-lib-jl                      <To see where a specific package is linked from>
    How to update a library (applies to scenarios where library is a separate rep and uploaded to npm)
    1. "npm install" to insall all dependant moudles
    2. Do NOT update the library source code in main program
    3. open a separate code window, make changes
    4. finish change and commit/sync
    5. "npm version patch" to update patch number. (or use other npm version  parameter to update minor version or major version)
    6. "npm publish" to publish it to NPM repository
    7. back to main program, 
      1. if package.json uses "^version#" in dependencies section, run "npm update", it should pull the latest version
      2. if package.json uses "version #" dependencies section, then edit the version# to be latest version, then remove library folder, and "npm install"

    Azure related

    Deploy
    1. With Azure extension setup, you can just right click on an Azure app, right click, "deploy" to deploy current project 
    2. Download deployment: 
      kudu zip api
    3. There are multiple ways an app can be deployed
      1. setup CI/CD in Azure app service
      2. setup github as external git source
      3. in github, set up Github Actions. This involves create a workflow yml file in which you can define with or without triggers. Sync triggers work same way as CI/CD pratically
    4. app name in workflow file must match what's in Azure
    5. If you have multiple package.json file in different folders that definds different dependencies for each folder, then "npm install" must be called in all folders. Define "npm install" and "npm build" in root package.json in such a way that it calls both in each sub-locations
    6. You need to define "engines" section with expected nodejs version in package.json file

    Typescript syntax

    1. import * from "./ws" means importing a file "ws.ts" under same folder
      import * fro "ws" means importing a 3rd party module called ws from node-module folder

    Nov 22, 2023

    Demo - Regex

    •  any string as is but a particular string: ^(?!particularString$).*
    • Grouped match (it will return named group, give a host FQDN, below will return domainName   ^.*?\.(?<domainName>.*)
    • Matches duplicate line ^((?-s).+?)\R(?=(?s).*?^\1(?:\R|\z))
    • AD domain NETBIOS name when standalone
      [a-zA-Z0-9](?!.*[,:~!@#\$%\^'\.\(\)\{\}_ \/\\]).{0,14}\\
    • SAMaccountName
      ^(?!.*[\"\/\\\[\]:;|=,\+\*\?<>]).{1,19}$
    • AD domain NETBIOS name when followed by \userName (this also groups domain/user)
      ([a-zA-Z0-9](?![^\\]*[,:~!@#\$%\^'\.\(\)\{\}_ \/]).{0,14})\\((?!.*[\"\/\\\[\]:;|=,\+\*\?<>]).{1,19})
    • same for powershell match
      -match '^    ([a-zA-Z0-9](?![^\\]*[,:~!@#\$%\^''\.\(\)\{\}_ \/]).{0,14})\\((?!.*[\"\/\\\[\]:;|=,\+\*\?<>]).{1,19})'
    • DN --> OU path (stripping CN name)
      -match '^((.+?),)(OU=.*|CN=.*)' $OUPath = $matches[3]

    Nov 20, 2023

    Azure AD: Risky User VS. Risky Sign-in

     

    Differences between “Risky Sign-In” and “Risk User”

    • Risky sign-in: abnormally in sign in activities, such as unusual location, impossible travels etc.
    • Risky user: An account that MS believes to have high probability of having been comprised (e.g. leaked credential)

     

    More importantly, the difference lies in how they are dealt with:

    • Risky Sign-in: requires additional authentication (e.g. MFA)
    • Risky User: Make old credential invalid (e.g. reset password)

     

    If we are to target “Risky Users”, Risky User Policy can be used to force password change. 

     

    Similarly, If we are to target “Risky Sign Ins”, we can use “Risky Sign in Policy” to enforce MFA.