Understanding Active Directory: The Technology Behind Enterprise AI Security - Part 7: Security and Best Practices

Understanding Active Directory: The Technology Behind Enterprise AI Security - Part 7: Security and Best Practices

Hello! In this seventh installment of the series, we cover Security and Best Practices - considerations for production environments.

Series overview


Chapter 7: Security and Best Practices

Part 7 is about bringing the authentication infrastructure built in the previous installments to a state where it can run in production.

In the first half, 7.1, we harden the entry points: how to handle Basic authentication, which is practically plaintext; how to bring the password policy in line with current recommendations; and how far to narrow administrative privileges. In the second half, 7.2, we build the mechanisms for noticing that someone has gotten in: which logs to collect, how to spot anomalies, and how to check things regularly.

Each section opens with a four-panel manga. The six characters are an auditor, an IT manager, a CISO, a developer, a help desk member, and a trainer, and all the scenes are fictional. All numbers and recommendations in the text are based on official Microsoft documentation, RFCs, and NIST publications, and the sources are listed at the end of each section.

7.1 Managing Credentials

7.1.1 Security Risks of Basic Authentication

Manga: in a training session, decoding a Basic authentication header reveals the username and password as is
Looking inside Basic authentication at a security training. Nothing is sealed, so decoding the header shows everything as is
  • TrainerLet's look inside Basic authentication
  • TrainerUsername and password are just Base64-encoded, not encrypted
  • Help DeskI decoded it and could read the password as is!
  • TrainerThat's why Basic auth is used only over a TLS-encrypted path

Basic authentication has the simplest mechanism of all, and without TLS it sends the username and password in a form anyone can reverse.

The reason is that it does not encrypt the credentials. RFC 7617 specifies that the client sends the username and password joined by a colon and encoded in Base64, and it also states that the scheme is not considered secure unless used together with an external secure system such as TLS. Base64 is an encoding that anyone can reverse, not encryption.

What the RFCs say about Basic authentication
  • The credentials are user-id:password encoded in Base64. This is the same as sending them in cleartext over the network
  • It is not a secure method of user authentication unless protected by TLS or similar
  • It SHOULD NOT be used without enhancements such as HTTPS
  • A proxy can require the same scheme. It responds with 407, and the client answers with the Proxy-Authorization header

Where the credentials get stolen

In the setup used throughout this series, traffic goes from an office PC through the proxy to external generative AI services. The place where credentials get stolen is not between the proxy and the external site, but between the PC and the proxy.

Office PC Browser Proxy Auth and logging External site Generative AI, etc. Proxy-Authorization: Basic … Travels here as plain Base64 (effectively plaintext without TLS to the proxy) HTTPS (TLS) Encrypted to the external site Even if the external site uses HTTPS, credentials for the proxy cross a separate segment
Figure: The segment that carries proxy authentication credentials. Even if the external site uses HTTPS, the credentials for the proxy cross a separate segment

If the external site uses HTTPS, the content is encrypted. But authentication to the proxy happens in the CONNECT request, before the HTTPS tunnel is built. RFC 9110 says proxy authentication is used to check permission to establish a tunnel, and its CONNECT example includes Proxy-Authorization. The client may send the credentials after receiving the proxy's 407 response, or attach them from the very first request. Either way, if the segment between the PC and the proxy is not protected by TLS, anyone who can observe that segment sees the credentials as is.

Where it can be observedWhat happensHow to protect it
Same networkOn public Wi-Fi, or when an attacker intercepts the path with ARP spoofing, traffic between the PC and the proxy can be readUse TLS for the connection to the proxy itself. From outside the office, protect the path with a VPN and still use TLS for the proxy connection inside it (a VPN protects the path; it does not replace encryption of the proxy connection)
Proxy logsIf the proxy is set to log request headers as is, the Base64 credentials end up in the logsDo not write the header to logs. Restrict who can read the logs
Fake proxyIf users are lured to a "free proxy" or a fake auto-configuration (PAC) file, they hand their credentials to the attackerDistribute proxy settings through Group Policy and do not let users change them

Why Basic authentication remains, and how to keep it safely

As we saw in Part 4, when the proxy offers Negotiate, NTLM, and Basic, which one is used depends on the client's implementation and settings. Within Negotiate, Kerberos is preferred, and NTLM is chosen when Kerberos is not available. Falling back to Basic is a separate mechanism: it depends on whether the proxy offers Basic and whether the client is configured to accept it. Basic is often kept as a fallback for devices not joined to the domain and for software that does not support integrated authentication.

If you keep Basic authentication. When you keep it as a fallback, put three things in place together. Use TLS for the segment to the proxy, limit which clients may use Basic by IP address or device, and distinguish connections authenticated with Basic in the logs so you can monitor them. If the fallback is wide open, an environment you thought was protected by Kerberos gets a plaintext back door.

7.1.2 Password Management Best Practices

Manga: a meeting to revise the password policy
Revising the password policy. Go for length, stop frequent changes, and ban common passwords
  • CISONew policy: long passwords instead of short, complex ones
  • Help DeskDo we still change them regularly? Every 3 months is tough...
  • CISONo more scheduled changes. Instead, we'll block leaked passwords
  • DeveloperWhat about service accounts?
  • CISOUse gMSA. Windows manages the passwords

Over the past few years, password policy has shifted from "make it complex and change it often" to "make it long, do not force changes, and ban the ones that have leaked."

NIST and Microsoft point in the same direction. The latest version of NIST SP 800-63B requires passwords used as a single factor to be at least 15 characters, does not impose composition rules such as mixing character types, does not require periodic changes, and requires checking against lists of known compromised passwords. For Microsoft 365 accounts, Microsoft recommends a minimum of 14 characters and recommends not setting an expiration for cloud-only accounts. These are recommendations for Microsoft 365, not rules that apply directly to on-premises Active Directory, but the direction is the same.

ItemOld common practiceNIST SP 800-63BMicrosoft 365 recommendation
Length8 characters15 or more as a single factor. 8 or more as part of multi-factor14 or more characters
Mixed character typesForce uppercase, lowercase, digits, and symbolsNo composition rules (SHALL NOT)Explains that length and symbol requirements push passwords into predictable patterns
Periodic changesChange every 90 daysNot required. Force a change only when there is evidence of compromiseNo expiration recommended for cloud-only accounts
BlocklistNoneReject passwords found in lists of compromised or commonly used passwordsBan easy-to-guess passwords
Maximum lengthShort upper limitAccept at least 64 characters
Multi-factorOptionalStates explicitly that passwords are not phishing-resistantMake multi-factor authentication registration mandatory
Sources: Password policy recommendations (Microsoft 365), NIST SP 800-63B (Digital Identity Guidelines: Authentication)

Why the change? The more constraints people face, the more they create passwords that follow the same pattern. Microsoft explains that requirements for length, symbols, and changes tend to push passwords into predictable patterns. Attackers know the "one uppercase letter, one symbol, digits at the end" pattern well.

What you can set in the Active Directory password policy

The domain password policy lives at the following location in Group Policy. The defaults are values that have been used for a long time, not today's recommended values.

Computer Configuration
└ Windows Settings
  └ Security Settings
    └ Account Policies
      └ Password Policy
SettingDefault domain policyDirection of review
Enforce password history24 passwordsKeep as is. Prevents reuse
Maximum password age42 daysMove toward ending periodic changes. Before you stop them, first set up the practice of forcing a reset when a compromise is detected
Minimum password age1 dayKeep as is. Prevents cycling through the history at once to get back to the old password
Minimum password length7 charactersSet your organization's target and raise it. NIST's 15 characters (single factor) and Microsoft 365's 14 characters are good guides. Microsoft's best practice also recommends 8 or more
Password must meet complexity requirementsEnabledEnabled by default (at least 3 character types, and must not contain the account name or display name). Since NIST does not impose composition rules, consider a transition to disabling it once you have enough length and a blocklist in place
Store passwords using reversible encryptionDisabledKeep it disabled. Enabling it stores passwords in what is effectively plaintext
Sources: Password Policy, Password must meet complexity requirements

This table covers the default domain policy. Since Windows Server 2008, fine-grained password policies (PSOs) let you apply a different policy to specific groups or users, so you can, for example, require longer passwords only for administrators and service accounts. The audit in 7.2.3 checks PSOs as well.

Where the built-in policy falls short. The domain password policy has no check against lists of leaked passwords. To do this in on-premises Active Directory, you need an on-premises deployment of Microsoft Entra Password Protection (which uses Entra ID's global banned password list and a custom list to reject password changes on the DCs), or an equivalent product. It helps the design to remember that "go for length" and "do not force changes" can be done with built-in features, while "ban what has leaked" needs an additional mechanism.

Service accounts should not be managed by people

Accounts used by applications and services have problems that differ from those of human accounts. There are three: the service stops when the password expires, the same password stays in use for years because people fear changing it, and the password is written in plaintext in configuration files.

Windows Server has an account type for this. A group Managed Service Account (gMSA) has its password managed automatically by Windows and can be used on multiple servers. Administrators never need to know the password. It works with Windows services, scheduled tasks, and other components that support gMSA, so for some applications you need to check whether they support it.

  1. 1Prepare a KDS root key (only once, the first time in the forest). In a production environment with multiple domain controllers, wait up to 10 hours after creating it for the key to replicate to all DCs before creating a gMSA
  2. 2Create a security group for the computers allowed to retrieve the password (WebServers in the example) and add the computer accounts of the target servers. After adding them, restart each server or have it obtain new Kerberos tickets so the group membership takes effect
  3. 3Create the gMSA. Pass New-ADServiceAccount a name, a DNS host name, and the group from step 2
  4. 4Install the gMSA on the server that uses it, and set the service's "Log On" account to DOMAIN\name$ (leave the password fields empty)
# 1. KDS root key (once per forest). Create it only if none exists. With multiple DCs in production, wait 10 hours after creating it
if (-not (Get-KdsRootKey)) {
  Add-KdsRootKey -EffectiveImmediately
}
# 1'. Only to skip the 10-hour wait in a lab (a single DC). Do not run this in production
Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))
# 2. Create the gMSA. Only computers in the WebServers group can retrieve the password
New-ADServiceAccount -Name "svc_webapp" `
  -DNSHostName "svc_webapp.example.local" `
  -PrincipalsAllowedToRetrieveManagedPassword "WebServers"

# 3. On the server that uses it
Install-ADServiceAccount -Identity "svc_webapp"
Test-ADServiceAccount -Identity "svc_webapp"   # True means it can be used
About setting -EffectiveTime in the past. Microsoft's documentation presents this as a step "to skip the wait in a test environment with only one DC." If you rush this in production, password retrieval can fail before the key has replicated to all DCs. -EffectiveImmediately also only "makes the key effective right away"; it does not guarantee that replication to all DCs has finished.

What lies beyond passwords

However well you tune the policy, a password is a secret you hand over to wherever you type it, so typing it into a fake sign-in page gets it stolen. NIST also states plainly that passwords are not phishing-resistant.

In an Active Directory environment, there are three paths toward authentication that does not rely on passwords. None of them "sends a secret." Instead, they "sign with a key held inside an authenticator" (the device's security module, a smart card, or a security key). Phishing resistance comes from two things: the private key never leaves the authenticator, and the key is bound to the legitimate relying party, so it does not respond to a fake one.

MethodSomething you haveSomething you know or arePhishing-resistantWhere it fits
Smart card + PINA physical card holding a certificatePINYesLong-standing Active Directory environments. Requires a certificate infrastructure (AD CS)
Windows Hello for BusinessA private key protected by the device's security modulePIN or biometrics. The PIN never leaves the deviceYesCompany-issued Windows PCs. A path to passwordless
FIDO2 security key (passkey)A USB/NFC device holding the keyPIN or biometricsYesHeavily regulated industries and privileged users. Recommended by Microsoft
Sources: Windows Hello for Business overview, Passkeys (FIDO2) authentication method in Microsoft Entra ID, NIST SP 800-63B (Digital Identity Guidelines: Authentication)

It is also worth noting how this relates to integrated Windows authentication at the proxy, the main subject of this series. Even when users sign in with Windows Hello for Business or a smart card, Kerberos ticket issuance afterward is unchanged, so Negotiate authentication at the proxy keeps working. The only thing that changes is what is presented at the first sign-in. On the other hand, if you leave the Basic authentication fallback in place, it still needs a password, which undercuts the point of going passwordless. That is why 7.1.1 recommended limiting the fallback.

7.1.3 Minimizing Administrative Privileges

Manga: an audit reveals 50 people in Domain Admins
An audit finds 50 people in Domain Admins. The flashback (panels 2 and 3) shows a help desk member who only sets up printers holding the top-level privileges, and a mistake that affects the whole domain
  • AuditorYou have 50 people in Domain Admins
  • IT ManagerIt was handy, so everyone got it...
  • Help DeskMy job is just printer setup. But I'm in Domain Admins...
  • Help DeskOops! I changed a setting for the whole domain by mistake!
  • AuditorGive people only the rights their work needs
  • IT ManagerI see...

Grant administrative privileges only to the extent the task requires, and only for as long as it is needed.

This is the principle Microsoft has consistently recommended for running Active Directory. Top-level groups such as Domain Admins are containers for emergencies, not for everyday work.

What Microsoft recommends in its official documentation
  • Domain Admins membership is needed only in "break-glass" situations that require elevated rights on every computer in the domain, and for domain-wide changes
  • Enterprise Admins should have 0 members day to day. Add members temporarily only when building a forest or setting up trusts between forests
  • Do not sign in to less privileged systems (member servers or PCs) with privileged accounts
  • Configure auditing so that alerts are raised when Domain Admins membership or attributes change

Why 50 people in Domain Admins is dangerous

There are two reasons.

The entry points multiply to 50. If the privileged credentials of just one of the 50 are stolen, the attacker gains a foothold to take over the entire domain. On top of that, signing in to an ordinary PC with a Domain Admins account leaves traces of the credentials on that PC, and they can be extracted if the PC is compromised. This is why Microsoft says not to sign in to less privileged systems with privileged accounts.

Mistakes spread to everything. Even a staff member who only installs printer drivers can touch Group Policy and the schema if they are in Domain Admins. When privileges exceed the scope of the job, nothing stops a mistake.

Grant rights per task (delegation)

In the manga's example, managing printers does not need Domain Admins. There are ways to grant only the scope required for each task.

TaskCommon grantRecommended approach
Reset passwords for users in a departmentDomain AdminsDelegate only "Reset password" on that department's OU
Join a department's new PCs to the domainDomain AdminsDelegate "Create computer objects" on that department's OU
Manage printersDomain AdminsAdmin rights limited to the target print server (that server's local groups, or the printer's manage permissions)
Run backups and restoresDomain AdminsLimit the scope with local rights on the target servers, or with roles in the backup product
Change domain-wide settingsStanding Domain AdminsAdd to Domain Admins only for the task, and remove when done
The built-in Operators groups are not a substitute. Account Operators, Server Operators, Backup Operators, and Print Operators are built-in domain groups, and Microsoft treats them as privileged groups. Account Operators can manage domain users and groups broadly, Server Operators can administer domain controllers, and Backup Operators can override security restrictions for the purpose of backup and restore. Rather than putting people in these groups because they seem "better than Domain Admins," the basic approach, when the required scope is defined by an OU or specific servers, is to narrow the scope and operations with the Delegation of Control wizard in Active Directory Users and Computers or with local rights on the servers.

Separate by tier (the Tier model)

Once delegation has narrowed rights per task, the next step is to decide which tier's credentials may be used on which tier. The Tier model that Microsoft has presented for Active Directory splits what you protect into three tiers and does not allow upper-tier credentials to be used on lower tiers.

Tier 0  Control layer Domain controllers, AD, identity infrastructure Tier 1  Servers and applications Member servers, business applications Tier 2  Users Client PCs, help desk work Rules • Separate admin accounts per tier • No higher-tier creds on lower-tier devices • Tier 0 work only from a dedicated PAW • Lower tiers never manage higher tiers
Figure: The three tiers of the Tier model. The key point is never using upper-tier credentials on a lower tier
TierWhat it protectsWho manages itRules
Tier 0Domain controllers, AD, identity infrastructureA very small dedicated teamDedicated admin accounts and dedicated devices (PAWs). Never sign in to other tiers
Tier 1Member servers, business applicationsServer administratorsDo not use Tier 0 credentials. Do not touch Tier 0 settings
Tier 2Client PCsHelp desk, PC administratorsDo not touch servers

Implementation starts with creating dedicated admin accounts and groups for each tier (for example, Tier0-Admins, Tier1-ServerAdmins, and Tier2-WorkstationAdmins). Then use user rights assignment in Group Policy so that each tier's admin accounts cannot sign in to devices and servers in lower tiers. There are five kinds of logon rights: interactive, Remote Desktop, network, logon as a service, and logon as a batch job. Decide which ones to deny based on the account's purpose and the systems it manages. If you deny all five across the board, admin accounts can no longer get into their legitimate management servers, so plan the paths to PAWs and management servers separately. Design the scope of OU delegation to match the tiers as well.

How Microsoft puts it today. Building on the Tier model, Microsoft now presents the "enterprise access model," which includes the cloud. Tier 0 has expanded into the control plane, the old Tier 1 has split into the management plane and the data/workload plane, and the old Tier 2 has split into user access and app access. This is not a renaming; it redraws the boundaries between roles. For protecting on-premises Active Directory, the three-tier idea is the starting point for design, and in environments that include cloud management infrastructure, you revisit the boundaries along the enterprise access model.

Do top-level work from a dedicated device (PAW)

Tier 0 administrators also use separate devices for their work. This is the privileged access workstation (PAW).

A PAW is the strictest device configuration, intended for roles whose compromise would have a serious impact on the organization. It has three aims: protect administrator credentials, reduce the device's attack surface, and separate the management path from everyday work. At its core is cutting email and web browsing, the main phishing routes, off from the management device. Email and the web are used with a different account on a different device.

Device levelGeneral apps and web browsingApps that can runOther
Enterprise (general work)AllowedNo restrictionsAnti-malware and EDR required
Specialized (specialized work)AllowedOnly apps installed by administratorsUsers cannot manage their own devices
Privileged (PAW)Not allowedStrictly limited with application controlDisk encrypted with BitLocker. Web access denied by default except for an allow list
Sources: Securing devices as part of the privileged access story

What you can do this week

  1. 1List the members of Domain Admins and Enterprise Admins (the audit script in 7.2.3 gets them for you)
  2. 2Remove the accounts used for everyday work. Give administrators two accounts: one for regular use and one for administration
  3. 3Replace the work of the people you removed with delegated rights on OUs, local rights on the target servers, or roles in management products
  4. 4Create admin groups for each tier, and block cross-tier sign-ins with Group Policy
  5. 5Provide PAWs for Tier 0 administrators

7.2 Monitoring and Logging

7.2.1 Collecting and Analyzing Authentication Logs

Manga: in the monitoring room, the CISO explains to a developer which logs to collect
Four kinds of logs to collect: the Windows security log, proxy access logs, Active Directory changes, and DNS queries
  • DeveloperWhich logs should we collect?
  • CISOFour kinds tied to auth: Windows, proxy, AD changes, and DNS
  • CISODon't leave them on the machines. Collect them in one place
  • DeveloperIn one place, we can search them right away during an investigation

The sections so far were about hardening the entry points. From here on, the topic is noticing when someone has gotten in.

To notice, you need to record authentication-related events without gaps, gather them in one place, and keep them in a form people can read. In an Active Directory environment, the following four kinds of logs form the foundation.

LogWhere to get itWhat it tells youTypical fields
Windows security logDomain controllers, each server, PCsWho signed in, when, from where, and by what method. Failures and lockoutsEvent IDs 4624 / 4625 / 4672 / 4740
Proxy access logsProxy serverWho sent and received how much data to and from which external sites. How often authentication was requested (407)User name, destination, date and time, bytes, status code
Active Directory changesDomain controllersCreation and deletion of users and groups, additions to group membership, password changesAdditions to groups are event IDs 4728 / 4732 / 4756
DNS queriesDNS serversQueries for names used by malware, surges in nonexistent names (NXDOMAIN)Query source, name, response code
Sources: Event 4624: An account was successfully logged on, Event 4625: An account failed to log on, Event 4672: Special privileges assigned to new logon, Event 4740: A user account was locked out, Use Windows Event Forwarding to help with intrusion detection
Event IDs to cover first
  • 4624 Successful logon. The logon type (Logon Type) distinguishes interactive (2), network (3), Remote Desktop (10), and so on
  • 4625 Failed logon. The first sign of brute-force attacks and password spraying
  • 4672 Logon with special privileges. Raised when a session with administrator-level rights is created. It also fires for SYSTEM logons, so collect it with exclusion conditions
  • 4740 Account lockout. A sign that failures exceeded the threshold

How to collect: Windows Event Forwarding

Logs are useless if they stay on the machine where they were generated, because the logs on a compromised machine get erased. Windows has a built-in way to send logs to one place without additional products, called Windows Event Forwarding (WEF).

Domain controller Proxy server Member server Client PC Collector Windows Event Forwarding Source-initiated subscription Analyze, store SIEM, search, retention Tamper detection winrm qc -q on each wecutil qc /q Two subscriptions: a minimal baseline from every machine, plus details from suspect machines
Figure: Gather each machine's event logs at a collector and pass them on for analysis and storage

There are two key settings. Configure the collection service on the collector, and enable Windows Remote Management on the sending side. Each is a one-line command.

# On the collector (the receiving side), run as administrator
wecutil qc /q
winrm qc -q

# On each source (the sending side). This can also be configured in bulk with Group Policy
winrm qc -q

This is only initialization, and no events flow yet. To start forwarding, create a "source-initiated" subscription on the collector, distribute the collector's location (SubscriptionManager) to the sending side through Group Policy, confirm that the sending side has permission to read the Security log and the right audit policy, and verify that events arrive in the collector's "Forwarded Events" log.

Which events a subscription collects is specified with XPath. For example, to collect only successful logons, failed logons, and lockouts, use the following.

<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">
      *[System[(EventID=4624 or EventID=4625 or EventID=4740)]]
    </Select>
  </Query>
</QueryList>
# On the collector, check the list and status of the subscriptions you created
wecutil es
wecutil gr "SubscriptionName"   # Shows each source's connection status and last arrival time
Microsoft's two-tier approach. Microsoft describes a configuration with two subscriptions: a "baseline" subscription that collects a minimal set from every machine, and a "targeted" subscription that collects details only from suspicious machines. Collecting everything from every machine drowns you in volume, while watching only suspicious machines misses the first signs. If you track Kerberos authentication on domain controllers, also include the ticket request events (4768 / 4769 / 4771).

How to read them: catching late-night logons

The first step in spotting anomalies in the collected logs can be a simple condition. For example, "successful interactive logons in the last 24 hours before 6 a.m. or after 10 p.m."

# List interactive logons (LogonType 2 and 10) late at night or early in the morning over the last 24 hours
New-Item -ItemType Directory -Path 'C:\\audit' -Force | Out-Null
$since = (Get-Date).AddDays(-1)
$hits = Get-WinEvent -FilterHashtable @{ LogName = 'Security'; ID = 4624; StartTime = $since } |
  Where-Object {
    $lt = $_.Properties[8].Value
    ($lt -eq 2 -or $lt -eq 10) -and ($_.TimeCreated.Hour -lt 6 -or $_.TimeCreated.Hour -ge 22)
  } |
  Select-Object TimeCreated,
    @{ N = 'User';        E = { $_.Properties[5].Value } },
    @{ N = 'Domain';      E = { $_.Properties[6].Value } },
    @{ N = 'LogonType';   E = { $_.Properties[8].Value } },
    @{ N = 'Workstation'; E = { $_.Properties[11].Value } }

$hits | Format-Table -AutoSize
$hits | Export-Csv -Path "C:\\audit\\night-logons-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation -Encoding UTF8
About the Properties indexes. In the XML of event 4624, TargetUserName is the 6th field (5 when counting from 0), TargetDomainName is the 7th (6), LogonType is the 9th (8), and WorkstationName is the 12th (11). The indexes differ by event ID, so check the XML of the event when you handle a different one. For notifications, the current recommendation is to send them to a ticketing system or a chat webhook rather than with Send-MailMessage. Microsoft marks Send-MailMessage as obsolete, and it does not guarantee secure connections to SMTP servers.

7.2.2 Detecting Abnormal Access Patterns

Manga: the IT manager notices an impossible travel alert and the CISO explains it
Someone who signed in from the office 30 minutes ago has also signed in over VPN from overseas. Someone else may be using the same credentials
  • IT ManagerSomeone in the office 30 minutes ago just logged on from overseas?
  • CISONobody can travel that far in 30 minutes. Someone else is using the account
  • CISOWe catch late-night logons, mass uploads, and repeated auth failures the same way
  • IT ManagerI'll disable the account and check with the owner
  • CISOPlease do

Once the logs are collected, the next step is finding what is "different from usual."

To do that, you first need to know what "usual" is: who uses which systems, at what times, from which devices. This is called a baseline. Baselines differ by person and department, so some things slip past a single company-wide threshold.

What to watchExample baselineExample anomalyMain data source
Time of dayInteractive logons during weekday business hoursInteractive logons late at night or on holidaysSecurity log 4624
Location and deviceUsual devices, the office network, the usual VPN egressUnfamiliar devices, overseas IPs, two distant places within a short timeSecurity log, VPN logs
Systems usedOnly the servers that match the roleNetwork logons (Logon Type 3) to servers unrelated to the roleSecurity log 4624, 4672
Traffic volumeDistribution of daily sent and received volumeLarge downloads or uploads in a short timeProxy access logs
Authentication failuresOccasional typosA few failures each across many users (password spraying), repeated failures for one userSecurity log 4625 / 4740, proxy authentication result logs (frequent 407s are a supporting sign)

Impossible travel

9:00 9:30 10:00 Logon from the office Interactive (Logon Type 2) VPN logon from an overseas IP Same user name Too far to travel in 30 minutes. Someone else may be using the same credentials
Figure: Logons with the same user name are recorded from distant places with a time gap too short to travel

The easiest anomaly to understand is "impossible travel." Microsoft Defender for Cloud Apps defines it as activity from two geographically distant locations recorded within a time shorter than it would take to travel between them, and detects it as a sign that someone else is using the same credentials.

The idea is simple. Compute a speed from the distance between the two logon locations and the time between them, and raise an alert if it exceeds a realistic limit. Locations are derived from IP addresses at the country or region level. If you implement this yourself, logons inside the office come from private addresses that cannot be mapped to a location, so treat "the office network" as a single location, and for VPN logons use the source IP recorded in the VPN connection logs. You need to match authentication logs and VPN logs by user name and time.

Typical false positives. When traffic goes through a VPN or a cloud proxy, the egress IP can be in a different country from where the user actually is. Registering your company's VPN egress and the routes of the cloud services you use as "known locations" in advance eliminates most of these alerts.

What to look for in proxy logs

In this series' environment, all traffic to external generative AI services goes through the proxy. That means the proxy logs are the very record of "who sent what outside." If they are loaded into a form you can aggregate with SQL, the following three queries are a starting point (written in MySQL 8.0 syntax).

-- 1. Users whose total sent and received exceeded 1 GiB in the last hour (bytes is assumed to be the column for total sent and received)
SELECT username, SUM(bytes) AS total_bytes, COUNT(*) AS requests
FROM proxy_logs
WHERE ts > NOW() - INTERVAL 1 HOUR
GROUP BY username
HAVING SUM(bytes) > 1073741824;

-- 2. Connections to destinations not on the allow list (most frequent first). NOT IN returns nothing if the allow list contains NULL, so use NOT EXISTS
SELECT p.username, p.dst_host, COUNT(*) AS cnt
FROM proxy_logs AS p
WHERE NOT EXISTS (
  SELECT 1 FROM allowed_domains AS a WHERE a.domain = p.dst_host
)
GROUP BY p.username, p.dst_host
ORDER BY cnt DESC;

-- 3. Users who received more than 10 responses of 407 in the last 10 minutes (407 = Proxy Authentication Required)
SELECT username, COUNT(*) AS challenges
FROM proxy_logs
WHERE status_code = 407
  AND ts > NOW() - INTERVAL 10 MINUTE
GROUP BY username
HAVING COUNT(*) > 10;

The 407 in the third query is the response RFC 9110 defines as "authentication is required to use the proxy." It is not an authentication failure in itself. Even with normal authentication, the first request gets one 407 back, and the request then goes through with credentials attached. So frequent 407s mean "suspected failures," not confirmed ones. To confirm, look at the proxy's own authentication result logs (for Squid, the results of the authentication helper). The first 407 often has no user name attached, so track it by source IP or per connection.

Thresholds such as 1 GiB and 10 times are only a starting point. Collect your own baseline for 1 to 2 weeks, then reset them just outside that distribution. The destination check in the second query is an exact host name match, so decide separately whether the allow list should cover subdomains.

The table and column names are examples. proxy_logs and allowed_domains are example tables for proxy access logs loaded into a database. Adapt them to the actual log format and column names of Squid or whatever proxy you use.

7.2.3 Conducting Regular Audits

Manga: the auditor and the IT manager facing a monthly audit checklist, and a developer automating it with a script
A monthly audit done by hand does not last. A script produces the lists, and people only make the judgments
  • AuditorOn the 1st of every month, we check these 5 items
  • IT ManagerAll by hand? We haven't even finished last month's yet...
  • DeveloperLet's leave generating the lists to a script
  • AuditorThen people only judge the rows that differ from the target state

Settings drift a little at a time from the day they are put in place. Someone added to Domain Admins temporarily stays there, accounts of people who have left stay enabled, a password set to never expire as an exception is forgotten. This is called "configuration drift."

The basic way to find drift and correct it is to write out the current state at fixed intervals and compare it with the target state. Once a month, check the following five items. These five are the audit items this article recommends, each mapped to what was decided in 7.1. In addition, changes to privileged group membership (event IDs 4728 / 4732 / 4756) are monitored continuously with the mechanism from 7.2.1 instead of waiting for the monthly audit, and the monthly audit serves to catch what that missed.

ItemWhat to checkTarget state (what was decided in 7.1)
1. Privileged accountsMembers of Domain Admins, Enterprise Admins, and Schema Admins, and their last logonEnterprise Admins has 0 members. Domain Admins has only a few break-glass members. No everyday-work accounts are in them
2. Password policyThe domain policy and the contents of fine-grained password policies (PSOs)Still the length and maximum age decided in 7.1.2. No unknown exceptions
3. Dormant accountsEnabled users with no logon for 90 days or moreAccounts of people who have left are disabled. Long absences are known
4. Service accountsAccounts whose passwords never expire, and their rightsThose that can be moved are migrated to gMSA. The reasons for the rest are recorded
5. Log settingsAudit policy, WEF subscription status, free space at the storage locationThe events from 7.2.1 arrive from every machine. No machine is missing
Sources: Appendix B: Privileged Accounts and Groups in Active Directory

Doing it by hand does not last, so automate the export

Checking five items by hand every month does not last. Leave producing the lists to a script, and have people judge only the "rows that differ from the target state." The following script runs on a domain controller or a management machine with RSAT installed, and exports to CSV the lists for items 1 to 4 and the current values of the item 2 policies. Item 5 (log settings) is checked with wecutil gr from 7.2.1 and by reviewing the audit policy.

# Monthly AD audit. Export the lists to CSV (requires the ActiveDirectory module)
Import-Module ActiveDirectory
$stamp  = Get-Date -Format yyyyMM
$outDir = "C:\\audit\\$stamp"
New-Item -ItemType Directory -Path $outDir -Force | Out-Null

# 1. Privileged group members. Keep direct members (nested groups also appear as rows) and effective members with nesting expanded
#    in the same 7 columns (Export-Csv cannot append correctly unless the column structure matches)
$rows = foreach ($g in 'Domain Admins', 'Enterprise Admins', 'Schema Admins') {
  Get-ADGroupMember -Identity $g |
    Select-Object @{ N = 'Group'; E = { $g } }, @{ N = 'Kind'; E = { 'direct' } },
      objectClass, SamAccountName, Name,
      @{ N = 'Enabled'; E = { $null } }, @{ N = 'LastLogonDate'; E = { $null } }
  Get-ADGroupMember -Identity $g -Recursive |
    Where-Object { $_.objectClass -eq 'user' } |
    Get-ADUser -Properties LastLogonDate, Enabled |
    Select-Object @{ N = 'Group'; E = { $g } }, @{ N = 'Kind'; E = { 'effective' } },
      objectClass, SamAccountName, Name, Enabled, LastLogonDate
}
$rows | Export-Csv -Path "$outDir\\privileged.csv" -NoTypeInformation -Encoding UTF8

# 2. Current values of the domain password policy and the fine-grained password policies (PSOs)
Get-ADDefaultDomainPasswordPolicy |
  Export-Csv -Path "$outDir\\domain-password-policy.csv" -NoTypeInformation -Encoding UTF8
Get-ADFineGrainedPasswordPolicy -Filter * |
  Select-Object Name, Precedence, MinPasswordLength, MaxPasswordAge, ComplexityEnabled,
    @{ N = 'AppliesTo'; E = { ($_.AppliesTo | ForEach-Object { $_.ToString() }) -join '; ' } } |
  Export-Csv -Path "$outDir\\fine-grained-policy.csv" -NoTypeInformation -Encoding UTF8

# 3. Enabled users with no logon for 90 days or more. Keep rows with an empty LastLogonDate (no record) so they can be reviewed separately
$cutoff = (Get-Date).AddDays(-90)
Get-ADUser -Filter 'Enabled -eq $true' -Properties LastLogonDate |
  Where-Object { $null -eq $_.LastLogonDate -or $_.LastLogonDate -lt $cutoff } |
  Select-Object SamAccountName, Name, LastLogonDate |
  Export-Csv -Path "$outDir\\inactive-90d.csv" -NoTypeInformation -Encoding UTF8

# 4. Enabled users whose passwords never expire
Get-ADUser -Filter 'PasswordNeverExpires -eq $true -and Enabled -eq $true' -Properties PasswordLastSet |
  Select-Object SamAccountName, Name, PasswordLastSet |
  Export-Csv -Path "$outDir\\password-never-expires.csv" -NoTypeInformation -Encoding UTF8

# 5. Users created in the past 30 days
Get-ADUser -Filter * -Properties whenCreated |
  Where-Object { $_.whenCreated -gt (Get-Date).AddDays(-30) } |
  Select-Object SamAccountName, Name, whenCreated |
  Export-Csv -Path "$outDir\\created-30d.csv" -NoTypeInformation -Encoding UTF8

Get-ChildItem $outDir | Select-Object Name, Length
The precision of LastLogonDate. LastLogonDate is derived from the lastLogonTimestamp attribute. This attribute is not updated at every logon; it is updated only when the previous value is older than msDS-LogonTimeSyncInterval (the initial update interval is 14 days minus a random amount). That is enough for coarse checks such as "90 days or more," but not for day-level decisions. An empty row does not necessarily mean "never signed in," so look at it together with the creation date. When you need the exact last logon, collect lastLogon from each domain controller and take the maximum.

The purpose and rights of service accounts cannot be determined from the list of non-expiring passwords alone. Check them against the service configuration (which service on which server runs under that account) and the rights delegated on OUs and servers.

The exported CSV only becomes an audit when you diff it against last month's and record, for each changed row, "is this an approved change?" and "who will fix it?" Not stopping at producing the lists is what matters most for keeping it going.

How much log data to keep, and how

Log retention is the foundation of auditing. The retention period is determined by the regulations that apply to your industry and your contracts with business partners, so first confirm with your legal or audit department which regulations apply to your company, and write the period and its basis into your internal rules. A period with no basis, such as "one year for now," turns out to be either too short or needlessly long when it is actually demanded.

Once the period is set, the requirements for how to keep the logs are the same regardless of regulation.

RequirementWhyExample implementation
Tamper-proofPrevent an intruder from deleting or rewriting the logsWrite-once (WORM) storage. Compute hashes of log files finalized after compression in a separate job, and record them in a tamper-resistant store
SearchableDuring an investigation, narrow down by period and user within minutesA SIEM, or storage indexed by time. Compress in a searchable format
Provably completeAvoid ending up with "there are no logs for this period"Check arrivals from each machine daily and alert on machines that stopped
Restricted readersThe logs themselves can contain personal activity and credentials like those in 7.1.1Limit read access to the storage to the audit team, and log the reads too

If you aggregate logs on a Linux log server, leave rotation and compression to logrotate. Do not compute the tamper-detection hashes inside logrotate. Compute them in a separate job on files whose compression has finished and whose contents are final, and record them in a non-rewritable store separate from the log server. If the hashes sit in ordinary files on the same server, an intruder can rewrite both the logs and the hashes.

# /etc/logrotate.d/security-logs (handles only rotation and compression)
/var/log/security/*.log {
    daily
    # rotate is the number of rotations, not the number of days kept
    rotate 400
    compress
    delaycompress
    missingok
    notifempty
    create 0600 root root
}
rotate is not a number of days. rotate is the number of rotations, not the retention period in days. Even with daily rotation, empty days are skipped by notifempty, and days when the server was down do not count. What guarantees the retention period is a mechanism on the storage side, such as the retention settings of WORM storage or object storage.

Chapter summary

Prevent Limit Basic, long passwords Least privilege, tiers, PAW Detect Log collection, anomalies Monthly audits, inventory Respond Disable accounts, fix rights Revise policies Feed the configuration drift and unneeded rights found in audits back into prevention
Figure: The cycle of prevention, detection, and response. Audit results feed back into the next round of prevention

Security is a cycle of "prevention," "detection," and "response." Harden the entry points in 7.1 (prevention), notice drift and anomalies through the monitoring in 7.2.1 and 7.2.2 and the audits in 7.2.3 (detection), and correct what you find and feed it back into policy (response). As long as these three keep turning, your authentication infrastructure grows stronger over time instead of weaker.

Next time, in Part 8, we will turn everything so far into concrete configuration examples, and look at how to combine it with AI security tools, from small to large environments.

Read more