ADSCRIPT-009: Malformed Scripts

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

What it checks

For every script discovered by the upstream logon-script enumerator (NETLOGON share, SYSVOL Scripts folders, user scriptPath attribute, and GPO User / Computer Scripts under Machine\Scripts and User\Scripts) the check evaluates: - File encoding: byte-order-mark presence and value (UTF-8 BOM EF BB BF, UTF-16 LE FF FE, UTF-16 BE FE FF), and whether the encoding is consistent with the file extension. A .bat or .cmd file with a UTF-16 BOM will not execute correctly under cmd.exe. A .ps1 with mixed encodings often fails to parse on Windows PowerShell 5.1. - Null bytes (0x00) anywhere in a text script. Null bytes are a strong signal of binary contamination, truncated downloads, or deliberate parser confusion. - Mixed or inconsistent line endings (CRLF and bare LF or bare CR in the same file). Batch files are particularly sensitive: a single LF-only line can cause goto labels to silently fail. - PowerShell parse validation: the file content is passed to [System.Management.Automation.Language.Parser]::ParseInput and the returned ParseError collection is recorded (line, column, message). - Batch file structural checks: unmatched ( and ) blocks, goto targets that have no matching :label, set /p or for /f statements truncated at EOL, and invalid command tokens at the start of a line. - VBScript / WSH structural checks: unbalanced Sub / Function / If / End blocks, unterminated string literals, and Option Explicit declarations with undeclared variables flagged for review. - File size anomalies: zero-byte scripts (logon will succeed but do nothing) and unusually large scripts (multi-megabyte .bat files are atypical and often indicate embedded base64 payloads). The output is one record per script with the file path, encoding, size, error category (ParseError, EncodingMismatch, NullBytes, MixedLineEndings, UnbalancedBlocks, ZeroByte, OversizedText), and the first three diagnostic messages.

Why it matters

Logon scripts run under the user context at every interactive logon and, when assigned through Group Policy Computer Startup, run as SYSTEM at boot. A script that silently fails leaves whatever it was supposed to enforce, drive mappings, printer connections, registry hardening, proxy configuration, NTLM signing requirements, in a partial state, often without any visible error to the user or the help desk. That gap is exactly what compliance baselines are supposed to close. The security dimension is the more serious one. SYSVOL and NETLOGON are world-readable to authenticated users by design, so an attacker with any domain credential can enumerate every script that runs at logon and identify high-value targets. If the attacker also has Write to a script (through ADACL findings on SYSVOL, a delegated Group Policy Creator Owners equivalent, or a misconfigured share), they can append a one-liner that runs in whatever security context the script already enjoys. Encoding tricks make this harder to detect. A batch file saved as UTF-16 with a hidden Unicode RIGHT-TO-LEFT OVERRIDE (U+202E) can present a benign command to a reviewer reading it in Notepad while cmd.exe executes something different. Null-byte injection has been used to truncate scripts in static review tools that read C-style strings while the actual interpreter reads the full content. Malformed scripts are also a strong tampering indicator on their own. Legitimate administrators almost never check in scripts that fail PSScriptAnalyzer or that have mixed encodings. A script that suddenly develops parse errors between two scans, especially one that has been stable for months, is a high-priority hunt lead.

Attack path

1. Reconnaissance: the attacker reads NETLOGON (\\domain\NETLOGON) and SYSVOL (\\domain\SYSVOL\domain\scripts and the Policies tree) as an unprivileged domain user, indexes every .bat, .cmd, .ps1, .vbs, and .wsf script, and identifies which are referenced by GPO Logon / Startup Scripts or by the scriptPath attribute on user accounts. 2. Target selection: scripts that run as SYSTEM at boot (Computer Startup Scripts) are preferred. Failing those, scripts that target privileged users (help-desk staff with local-admin rights on workstations, or service accounts with logon rights to servers) are second-best. 3. Write primitive: the attacker obtains Write to the script through an ADACL finding (excessive permissions on SYSVOL or on a GPO container), through Group Policy Creator Owners membership, or through a delegated edit right. 4. Payload injection with encoding cover: instead of appending a clean line of malicious code, the attacker uses encoding to evade casual review. Examples observed in the wild include saving a modified .bat as UTF-16 LE with a BOM (cmd.exe partially executes the file and skips lines, but the malicious section still runs through a goto), embedding a base64 PowerShell payload behind a continuation of an existing line, and inserting U+202E and other Unicode bidirectional override characters to make the displayed source disagree with the executed source (the Trojan Source class of attack, CVE-2021-42574). 5. Execution: the next logon (user scripts) or boot (computer scripts) runs the injected code on every targeted host. Group Policy refresh on Domain Controllers is especially valuable if the script is in a GPO linked to the Domain Controllers OU. 6. Persistence and evasion: the corrupted file may produce intermittent parser errors in the Application or Group Policy event log, but those errors are typically dismissed as routine, especially when the rest of the script continues to function. Defenders without a baseline of validated scripts have no way to distinguish a newly introduced parse error from longstanding noise.

How Guerrilla assesses it

Guerrilla collects script paths from four sources: the NETLOGON share (resolved per Domain Controller to detect FRS / DFSR replication drift), SYSVOL \\<domain>\sysvol\<domain>\scripts, the User scriptPath attribute on every user account read via LDAP, and the Logon / Startup Script extensions in every GPO (parsed from Machine\Scripts\scripts.ini and User\Scripts\scripts.ini in the GPO file system path). For each file the check reads the raw bytes (not the decoded text) and runs three layers of validation: 1. Byte-level inspection: BOM detection, null-byte scan, line-ending histogram, and a check that the declared encoding matches the file extension (cmd.exe requires ANSI or UTF-8 without BOM on modern Windows, .ps1 expects UTF-8 with BOM for non-ASCII characters on Windows PowerShell 5.1, .vbs is interpreted as ANSI by wscript.exe by default). 2. Parser-level inspection: PowerShell scripts are parsed with the in-box [System.Management.Automation.Language.Parser] API, which returns a structured collection of ParseError records (Extent, Message, ErrorId). Batch files are tokenised with a lightweight state machine that tracks parenthesis depth, label declarations and goto references, and the start-of-line command. VBScript and WSH files use a similar block-balance check. 3. Optional deep analysis: if PSScriptAnalyzer is available on the host running the audit, the check invokes Invoke-ScriptAnalyzer on each .ps1 and records Error and Warning diagnostics. ScriptAnalyzer is not required because air-gapped audit hosts often cannot install the module. Findings are emitted with the absolute path, the source channel (NETLOGON, SYSVOL, scriptPath, GPO), the file hash (SHA-256, useful for diffing across scans), and the diagnostic detail. The check does not modify the files and does not execute them.

Recommended value

All scripts pass syntax validation with no encoding anomalies or structural errors

Remediation

Validate script syntax using appropriate tools: PowerShell scripts with Test-ScriptFileInfo or PSScriptAnalyzer, batch files with manual review for unclosed blocks and invalid commands, VBScript with WSH syntax checking. Check file encoding for unexpected byte sequences or mixed encoding. Review scripts with unusual encoding (UTF-16 with BOM in batch files, null bytes) for potential injection.

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-009
ScenarioExpected verdict
cleanPASS
known-badWARN
throttledNot Assessed

Framework mappings

NIST SP 800-53
CM-3, SI-7
MITRE ATT&CK
T1059