ADSCRIPT-008: External Resource References

Platform
Active Directory
Category
AD Logon Scripts & Network Shares
Severity
High
Zero Trust pillar
Applications & Workloads (weight 2)
Golden fixtures
3
Branch coverage
Observed: fixtures prove the verdicts they exercise
Provenance
baseline

What it checks

The check walks four sources of logon-time script execution and parses each artifact for external references. 1. User scriptPath attribute: Get-ADUser -LDAPFilter "(scriptPath=*)" -Properties scriptPath returns the legacy per-user logon script path. The value is resolved against \\domain\NETLOGON\<scriptPath> and the file body is read. 2. Group Policy Logon/Logoff scripts: every GPO is enumerated through Get-GPO -All. For each GPO the check parses Scripts.ini under \\domain\SYSVOL\<domain>\Policies\{GUID}\User\Scripts\scripts.ini and \\domain\SYSVOL\<domain>\Policies\{GUID}\User\Scripts\psscripts.ini, then reads each referenced .bat, .cmd, .vbs, .ps1, .js, or .wsf body. 3. Group Policy Startup/Shutdown scripts: the same parse runs against the Machine\Scripts subtree of every GPO. 4. NETLOGON share contents: Get-ChildItem \\<domain>\NETLOGON -Recurse -Include *.bat,*.cmd,*.vbs,*.ps1,*.js,*.wsf,*.kix,*.ini enumerates every script in the share, including legacy KIX, dial-up, and migration scripts that may still be referenced by user objects no longer enumerated through standard GPO tooling. For each script body the check applies a regex sweep for the following external reference patterns: HTTP/HTTPS: https?:\/\/[^\s'"]+ FTP/SFTP: (?:s?ftp|tftp):\/\/[^\s'"]+ UNC paths: \\\\[^\\\s]+\\[^\s'"]+ (server portion compared against domain FQDN, NetBIOS name, and any configured trusted-domain list; non-match is flagged) Cloud storage: hostnames matching *.blob.core.windows.net, *.s3.amazonaws.com, *.storage.googleapis.com, *.sharepoint.com, *.dropbox.com, raw.githubusercontent.com, cdn.jsdelivr.net, registry.npmjs.org, pypi.org, chocolatey.org, *.b2.backblazeb2.com, *.r2.cloudflarestorage.com Direct IPs: \b(?:\d{1,3}\.){3}\d{1,3}\b followed by a path component Package CDNs: Invoke-WebRequest, Invoke-RestMethod, BitsTransfer, curl.exe, wget, certutil -urlcache -split -f, msiexec /i http(s), regsvr32 /s /n /u /i:http(s) For PowerShell scripts the check also flags one-liner download cradles: IEX (New-Object Net.WebClient).DownloadString, iwr ... | iex, irm ... | iex, and the same pattern with Invoke-Expression spelled out. For batch and cmd scripts it flags certutil, bitsadmin /transfer, and powershell -c "iex (irm ...)". For VBScript and WSH it flags WScript.Shell with MSXML2.XMLHTTP or XMLHTTPRequest. Each finding records: source (User|GPO-Logon|GPO-Logoff|GPO-Startup|GPO-Shutdown|NETLOGON), source identifier (user samAccountName, GPO display name and GUID, or NETLOGON path), script file path, line number, full matched text, classification (HTTP, FTP, UNC-External, Cloud, IP, DownloadCradle), and the run-as context the script will execute under (the user identity for Logon/Logoff, SYSTEM for Startup/Shutdown). The check also captures whether the referenced URL is currently reachable, whether HTTPS certificates validate, and whether the domain in the URL is still registered (an unregistered domain is a high-severity finding: an attacker can claim the domain and own the payload).

Why it matters

Logon and startup scripts are one of the highest-fanout code-execution surfaces in Active Directory. A script linked at the domain root runs on every interactive logon across every member workstation; a startup script at the same scope runs as SYSTEM on every boot. When that script reaches outside the domain for any portion of its payload, the trust boundary of the directory extends to whoever controls the external resource. Five concrete failure modes follow. First, domain hijack and re-registration. Scripts written years ago commonly reference vendor download paths, internal-tooling URLs published on personal blogs, or hostnames belonging to staff who have since left. When the registration lapses, anyone can claim the hostname and serve a malicious .ps1 or .msi from the same path. Every endpoint that runs the script on the next logon executes the attacker payload under the logged-on user or SYSTEM. Mandiant and Microsoft Threat Intelligence have documented this pattern repeatedly under T1584 (compromise infrastructure) and T1608.001 (upload malware to compromised site). Second, transitive supply-chain compromise. A script that pulls a binary from raw.githubusercontent.com, registry.npmjs.org, pypi.org, or chocolatey.org consumes whatever the upstream maintainer ships. The 2021 ua-parser-js, the 2022 ctx, the 2023 3CX, and the 2024 xz-utils incidents all show that upstream package compromise propagates instantly to any consumer that does not pin a hash. A logon script that runs choco install <package> -y on every boot is a perpetual subscription to the upstream maintainer's threat model. Third, traffic interception and replacement. HTTP (no S) downloads can be replaced in transit by anyone with on-path access: the corporate proxy, the ISP, the coffee-shop wifi, the captive portal, a rogue ARP responder. Even HTTPS is not a guarantee if the script disables certificate validation, accepts self-signed responses, or runs on a host whose root store has been seeded with an attacker CA (a common post-exploitation step). Fourth, data exfiltration. External references are bidirectional. A script that posts to a Slack webhook, a Discord webhook, an Azure Storage SAS URL, or an attacker-controlled domain on every logon is a continuous beacon. Defenders looking for outbound C2 frequently miss this because the request originates from the logged-on user agent (PowerShell, curl, BitsTransfer) rather than an injected process, and the destination is a legitimate cloud service that proxy allowlists permit. Fifth, identity and credential leakage. Scripts that authenticate to an external resource frequently embed credentials: a SharePoint Online cookie, a personal access token, an AWS access key, a service-account password used to mount a non-domain share. Anyone who can read SYSVOL (every authenticated domain user) can extract those credentials and reuse them against the external service, often against tenant-wide privileged scopes. The blast radius scales with the linked scope of the GPO and with the population of users whose scriptPath attribute references the affected file. A single external reference in a script linked to the Authenticated Users default scope is a domain-wide execution primitive in attacker hands.

Attack path

1. Reconnaissance: the attacker, holding any authenticated domain user, enumerates SYSVOL with dir \\domain\SYSVOL\domain\scripts and dir \\domain\SYSVOL\domain\Policies\*\User\Scripts and reads every script body. SYSVOL is readable by Authenticated Users by design. 2. External reference inventory: the attacker greps every script for http://, https://, ftp://, \\ paths whose server is not in the local forest, raw.githubusercontent.com, *.blob.core.windows.net, *.s3.amazonaws.com, and Invoke-WebRequest/IEX/certutil/bitsadmin invocations. PowerView and SharpHound surface the same data programmatically. 3. Hijack target selection: the attacker triages the external references by takeover feasibility: - Expired or unregistered domains (whois lookup): claim immediately. - GitHub repositories owned by deleted accounts or accounts vulnerable to username squatting after rename: register the username and re-create the repo at the same path. - NPM, PyPI, NuGet packages with maintainer accounts that lack 2FA: phish or credential-stuff the maintainer. - HTTP (no S) URLs: position on-path through ARP spoofing on the user subnet, a malicious DHCP option, or a captured upstream router. - Cloud storage buckets whose access policy is public-write or whose container name is guessable and currently unregistered: claim the container in the attacker's own subscription. 4. Payload staging: the attacker replaces the legitimate file at the external URL with a payload that performs the original action plus the attacker action. The original action is preserved so users and helpdesk do not notice the script "broke." The attacker action is typically a Cobalt Strike beacon, an Empire stager, a Sliver implant, or a credential-harvesting LSASS read. 5. Detonation: every domain workstation that executes the logon or startup script on the next logon or boot downloads and runs the attacker payload. For Logon scripts the payload runs as the logged-on user, including domain admins and helpdesk on jump hosts. For Startup scripts the payload runs as SYSTEM on the local machine. Within hours of the swap the attacker has interactive sessions across the population. 6. Lateral movement and persistence: with a foothold on hundreds or thousands of endpoints, the attacker uses the same script-staging vector for persistence. They modify the external resource periodically with rotating implants to evade detection, knowing every workstation will re-fetch on the next logon. 7. Privilege escalation: any Tier-0 administrator who logs on to a workstation whose logon script has been hijacked is compromised on the first logon. Helpdesk and desktop-support accounts that touch a high volume of endpoints accelerate this further. 8. Detection evasion: because the script itself was not modified (it still references the same URL), every change-detection control on SYSVOL (file integrity monitor, GPO change auditing, Repadmin /showrepl) passes. The attacker tradecraft is entirely upstream of the directory.

How Guerrilla assesses it

Guerrilla loads the ActiveDirectory and GroupPolicy modules and runs four passes. Pass 1 (User scriptPath): Get-ADUser -LDAPFilter "(scriptPath=*)" -Properties scriptPath, samAccountName, enabled. For each enabled user, resolve the scriptPath against \\$env:USERDNSDOMAIN\NETLOGON\ and read the file body. Pass 2 (GPO Logon/Logoff and Startup/Shutdown): Get-GPO -All -Domain $env:USERDNSDOMAIN. For each GPO, test for the existence of \\$env:USERDNSDOMAIN\SYSVOL\$env:USERDNSDOMAIN\Policies\{$gpo.Id}\User\Scripts and \Machine\Scripts. Parse scripts.ini and psscripts.ini for each. Read each referenced script body. Pass 3 (NETLOGON contents): Get-ChildItem \\$env:USERDNSDOMAIN\NETLOGON -Recurse -Include *.bat,*.cmd,*.vbs,*.ps1,*.js,*.wsf,*.kix and read each file body. Pass 4 (regex sweep): for every script body collected by passes 1 through 3, the check runs the regex set documented under WhatItChecks and emits one row per match. The domain portion of each UNC and URL match is compared against (Get-ADForest).Domains and (Get-ADTrust -Filter *).Name; anything outside that set is classified UNC-External or Cloud. For every URL match the check optionally runs a reachability probe (Invoke-WebRequest -UseBasicParsing -Method Head -TimeoutSec 5 -MaximumRedirection 0) and a domain registration check (whois lookup or RDAP query for the apex domain). Unregistered apex domains are flagged Critical and surfaced separately at the top of the report. Output columns: Source (User|GPO-Logon|GPO-Startup|...), SourceId (samAccountName or GPO GUID), ScriptPath, LineNumber, MatchType (HTTP|HTTPS|FTP|UNC-External|Cloud|IP|DownloadCradle), MatchText, RunAsContext (User|SYSTEM), DomainRegistered (Yes|No|Unknown), HttpsValid (Yes|No|n/a), and Notes. False-positive handling: a whitelist file at $GuerrillaConfig\external-refs-allowlist.json can list approved external references (vendor patch URLs, signed package feeds with hash pinning) that the check ignores. The check still reports allowlisted entries in an Informational row so the security team has a complete inventory.

Recommended value

No references to external URLs, internet resources, or non-domain UNC paths in logon scripts

Remediation

Scan all scripts for HTTP/HTTPS URLs, FTP references, non-domain UNC paths, and cloud storage URLs (OneDrive, SharePoint Online, Azure Blob, AWS S3). Replace external references with locally hosted copies on internal file shares. If external resources are required, implement integrity verification (hash checks) before execution. Document all approved external resource dependencies.

Fixture-proven verdicts

Every verdict below is proven by a golden fixture in the module's gating test suite. This table derives from the last green run; it cannot be edited by hand.

Verdict scenarios for ADSCRIPT-008
ScenarioExpected verdict
cleanPASS
known-badFAIL
throttledNot Assessed

Framework mappings

NIST SP 800-53
SC-7, SI-7, CM-5
MITRE ATT&CK
T1059, T1105