Security #8600 ยป WINDOWS_SERVICE_UNQUOTED_PATH_ASSESSMENT.md
Suricata Windows Service Install/Update Creates Unquoted LocalSystem ImagePath, Enabling Local Privilege Escalation in Writable-Parent Deployments
Date: 2026-05-25
Finding: Suricata's Windows service install/update logic creates an unquoted LocalSystem service ImagePath, enabling local privilege escalation in writable-parent deployments.
Severity: High local privilege escalation; deployment-dependent; not remotely triggerable by network traffic.
CWE: CWE-428, Unquoted Search Path or Element
MITRE ATT&CK: T1574.009, Hijack Execution Flow: Path Interception by Unquoted Path
Status: Confirmed in source, validated with a benign Windows process-creation PoC, and validated with a disposable SCM service running as LocalSystem.
Summary
Suricata's Windows service installation and service parameter update logic constructs the service ImagePath by copying the running executable path with GetModuleFileName, appending command-line arguments separated by spaces, and passing the resulting string to the Windows Service Control Manager without quoting the executable path.
When a Windows service binary path contains spaces and is not quoted, Windows process creation can resolve and execute an earlier path component before the intended executable. If an attacker can place an executable at one of those earlier candidate paths, that executable can be launched instead of suricata.exe.
The issue affects the Windows-specific service paths:
-
SCServiceInstallinsrc/win32-service.c -
SCServiceChangeParamsinsrc/win32-service.c
Because the service is created with lpServiceStartName == NULL, Microsoft documents that the service runs as LocalSystem. A disposable SCM validation confirmed this behavior: the hijacked marker executable ran as SYSTEM with SID S-1-5-18.
This is best classified as a high-severity local privilege escalation. It has critical impact on affected Windows hosts, but it is not remotely triggerable by network traffic and still depends on local write access to a candidate path plus a service start or restart event.
This should not be treated as a pure deployment misconfiguration. The unsafe service configuration originates in Suricata's own service installation/update logic. Exploitability is deployment-dependent: default protected C:\Program Files\Suricata installations are harder to exploit under normal ACLs, while spaced installations beneath commonly writable parents, or parents writable in the assessed environment such as C:\ProgramData, C:\Users\Public, or permissive operational directories, are realistically exploitable.
Technical Root Cause Analysis
Vulnerable Service Install Path Construction
Location: src/win32-service.c, function SCServiceInstall
Relevant code:
if (GetModuleFileName(NULL, path, MAX_PATH) == 0 ){
SCLogError("Can't get path to service binary: %d", (int)GetLastError());
break;
}
/* skip name of binary itself */
for (i = 1; i < argc; i++) {
if ((strlen(argv[i]) <= strlen("--service-install")) && (strncmp("--service-install", argv[i], strlen(argv[i])) == 0)) {
continue;
}
strlcat(path, " ", sizeof(path) - strlen(path) - 1);
strlcat(path, argv[i], sizeof(path) - strlen(path) - 1);
}
service = CreateService(
scm,
PROG_NAME,
PROG_NAME,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
path,
NULL,
NULL,
NULL,
NULL,
NULL);
GetModuleFileName(NULL, path, MAX_PATH) returns the current executable path, for example:
C:\Program Files\Suricata\suricata.exe
The code then appends service arguments using a raw space separator, producing a service binary path similar to:
C:\Program Files\Suricata\suricata.exe -c C:\Program Files\Suricata\suricata.yaml
The executable portion is not enclosed in quotes before being passed as lpBinaryPathName.
Microsoft's CreateServiceA documentation explicitly states that if the service binary path contains a space, it must be quoted so that it is correctly interpreted. Microsoft also documents that when lpServiceStartName is NULL, CreateService uses the LocalSystem account.
Reference:
Vulnerable Service Change Path Construction
Location: src/win32-service.c, function SCServiceChangeParams
The same pattern is repeated when changing service startup parameters:
if (GetModuleFileName(NULL, path, MAX_PATH) == 0 ){
SCLogError("Can't get path to service binary: %d", (int)GetLastError());
break;
}
/* skip name of binary itself */
for (i = 1; i < argc; i++) {
if ((strlen(argv[i]) <= strlen("--service-change-params")) && (strncmp("--service-change-params", argv[i], strlen(argv[i])) == 0)) {
continue;
}
strlcat(path, " ", sizeof(path) - strlen(path) - 1);
strlcat(path, argv[i], sizeof(path) - strlen(path) - 1);
}
if (!ChangeServiceConfig(
service,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
path,
NULL,
NULL,
NULL,
NULL,
NULL,
PROG_NAME))
This means a service that was initially created safely by another mechanism can later be made vulnerable if --service-change-params is run from a Suricata executable path containing spaces.
Why This Is Vulnerable
Microsoft documents the exact unsafe behavior in CreateProcessA security remarks. When lpApplicationName is NULL and the executable path in lpCommandLine contains spaces, Windows may interpret earlier path components as executable candidates.
For a command line such as:
C:\Program Files\Suricata\suricata.exe -c C:\Program Files\Suricata\suricata.yaml
Windows may try candidate paths such as:
C:\Program.exe
C:\Program Files\Suricata\suricata.exe
The general class becomes exploitable when an attacker can create one of the earlier executable candidates.
Reference:
Root Cause
The root cause is unsafe construction of a Windows service command line:
- The executable path and arguments are combined into a single string.
- The executable path is not quoted.
- Arguments are appended without Windows command-line escaping.
- The service is configured to run as LocalSystem by default.
- No validation is performed to detect ambiguous service binary paths before installation or update.
The code assumes that the path returned by GetModuleFileName can safely become the first token of a service ImagePath. That assumption is false on Windows when paths contain whitespace.
Affected Assets & Attack Surface
Affected Platform
This issue is Windows-specific and affects builds compiled with OS_WIN32.
The exposed functionality is present in the Suricata command-line options:
--service-install
--service-change-params
The relevant run modes are dispatched from src/suricata.c:
case RUNMODE_INSTALL_SERVICE:
if (SCServiceInstall(argc, argv)) {
return TM_ECODE_FAILED;
}
SCLogInfo("Suricata service has been successfully installed.");
return TM_ECODE_DONE;
case RUNMODE_CHANGE_SERVICE_PARAMS:
if (SCServiceChangeParams(argc, argv)) {
return TM_ECODE_FAILED;
}
SCLogInfo("Suricata service startup parameters has been successfully changed.");
return TM_ECODE_DONE;
Affected Assets
Potentially affected assets include:
- Windows hosts running Suricata as a Windows service.
- Windows hosts where Suricata was installed or updated using Suricata's built-in service installation logic.
- Windows hosts where service parameters were changed using Suricata's
--service-change-paramsmode. - Windows deployments installed under paths containing spaces, including common paths such as
C:\Program Files\Suricata. - Custom Windows deployments installed under commonly writable parents, or parents writable in the assessed environment, such as
C:\ProgramData,C:\Users\Public, shared application directories, or operational tool directories with weak ACLs.
Exposed Trust Boundaries
This issue crosses three important trust boundaries:
- Filesystem trust boundary: a low-privileged user may be able to write an executable to an earlier ambiguous path component.
- Service Control Manager trust boundary: the unquoted path is persisted as privileged service configuration.
- Privilege boundary: the service is launched as LocalSystem when
lpServiceStartNameisNULL.
Attacker Discovery Methods
A local attacker or incident responder can discover vulnerable services using standard Windows enumeration:
Get-CimInstance Win32_Service |
Where-Object {
$_.PathName -match '\s' -and
$_.PathName -notmatch '^"'
} |
Select-Object Name, StartName, State, PathName
Equivalent command-line discovery:
wmic service get name,displayname,pathname,startmode | findstr /i /v "\""
An attacker would then inspect the resulting path for writable candidate locations. For example:
C:\ProgramData\Suricata Service\Real Dir\suricata.exe
can create the candidate:
C:\ProgramData\Suricata.exe
if C:\ProgramData permits the attacker to create files.
Exploitation Walkthrough
The following walkthrough describes exploitation at a level suitable for defensive validation and incident-response readiness. It avoids persistence and destructive payload behavior.
Preconditions
Exploitation requires:
- Windows host.
- Suricata installed or updated as a Windows service through the vulnerable code path.
- Service
ImagePathcontains an unquoted executable path with whitespace. - Attacker can write an executable to an earlier candidate path.
- Service is started or restarted after the candidate executable is present.
Adversarial Workflow
-
Enumerate services and identify an unquoted Suricata service path.
Example vulnerable service path:
C:\ProgramData\Suricata Service\Real Dir\suricata.exe --service-mode -c C:\ProgramData\Suricata\suricata.yaml -
Determine Windows' earlier executable candidates.
For the example above, candidate paths include:
C:\ProgramData\Suricata.exe C:\ProgramData\Suricata Service\Real.exe C:\ProgramData\Suricata Service\Real Dir\suricata.exe -
Test write access to earlier candidate paths.
In the validated environment, the current non-admin user could write to:
C:\ProgramData C:\Users\Public %TEMP%The same user could not write to:
C:\ C:\Program Files C:\Program Files (x86) -
Place a benign validation executable at the first writable candidate path.
For the example:
C:\ProgramData\Suricata.exe -
Wait for or trigger service start.
A local low-privileged attacker may not be able to start a demand-start service directly, depending on the service DACL. Practical trigger opportunities include:
- system reboot if operational tooling starts the service;
- administrator troubleshooting;
- service restart by monitoring or management tooling;
- scheduled maintenance;
- EDR, deployment, or orchestration workflows that restart services.
-
Windows resolves and runs the attacker-controlled candidate executable instead of the intended
suricata.exe. -
The candidate executable runs in the service context. For the affected
CreateServicecall, that context defaults to LocalSystem.
Constraint Reduction
The exploitability is weaker for the common default installation under C:\Program Files\Suricata\suricata.exe because the main earlier candidate is typically protected from standard-user writes:
C:\Program.exe
C:\Program Files\Suricata.exe becomes an earlier candidate only if a later path component contains another space, for example C:\Program Files\Suricata Service\suricata.exe.
However, the precondition is substantially reduced in custom deployments:
- If Suricata is installed under
C:\ProgramData\Suricata Service\..., a standard user may be able to createC:\ProgramData\Suricata.exewhereC:\ProgramDatais writable by that user. - If Suricata is installed under
C:\Users\Public\Suricata Service\..., a standard user may be able to create a candidate executable underC:\Users\Public. - If enterprise deployment tooling installs Suricata into a shared operations directory with spaces and permissive ACLs, the same attack applies.
Therefore the finding should not be dismissed as purely theoretical or limited to misconfigured root directories. It is directly exploitable in realistic writable-parent deployments.
Proof-of-Concept & Evidence
Source Evidence
The vulnerable source locations are:
-
src/win32-service.c:222: obtains executable path withGetModuleFileName. -
src/win32-service.c:232-233: appends arguments using raw spaces. -
src/win32-service.c:249: passes unquotedpathtoCreateService. -
src/win32-service.c:253-254: passesNULLfor service account and password. -
src/win32-service.c:347: same path construction in service parameter update. -
src/win32-service.c:357-358: same raw argument append. -
src/win32-service.c:376: passes unquotedpathtoChangeServiceConfig.
Git blame showed the vulnerable service construction pattern dates back to the older Windows service implementation and has remained materially unchanged in current code.
Benign PoC Design
The validation PoC used:
- a launcher that calls
CreateProcessA(NULL, cmdline, ...); - a benign payload executable that records:
- actual module path;
-
argv[0]; - username;
- user SID;
- parsed arguments;
- positive and negative controls.
The PoC did not persist, elevate, modify services, or perform destructive actions.
Control A: Unquoted Path With Earlier Candidate Present
Command line passed to CreateProcessA:
C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc path test\Real Dir\suricata.exe --service-mode -c suricata.yaml
Earlier candidate present:
C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc.exe
Observed result:
module_path=C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc.exe
argv0=C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc
user=One
sid=S-1-5-21-3388575877-2469963850-3643760867-1003
argc=7
arg1=path
arg2=test\Real
arg3=Dir\suricata.exe
arg4=--service-mode
arg5=-c
arg6=suricata.yaml
Conclusion: the earlier candidate executable was selected and launched.
Control B: Quoted Path With Earlier Candidate Present
Command line passed to CreateProcessA:
"C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc path test\Real Dir\suricata.exe" --service-mode -c suricata.yaml
Earlier candidate still present:
C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc.exe
Observed result:
module_path=C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc path test\Real Dir\suricata.exe
argv0=C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc path test\Real Dir\suricata.exe
user=One
sid=S-1-5-21-3388575877-2469963850-3643760867-1003
argc=4
arg1=--service-mode
arg2=-c
arg3=suricata.yaml
Conclusion: quoting the executable path prevents the hijack.
Control C: Unquoted Path After Removing Earlier Candidate
Command line passed to CreateProcessA:
C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc path test\Real Dir\suricata.exe --service-mode -c suricata.yaml
Earlier candidate removed.
Observed result:
module_path=C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc path test\Real Dir\suricata.exe
argv0=C:\Users\One\AppData\Local\Temp\suricata_usp_validation_env\svc path test\Real Dir\suricata.exe
user=One
sid=S-1-5-21-3388575877-2469963850-3643760867-1003
argc=4
arg1=--service-mode
arg2=-c
arg3=suricata.yaml
Conclusion: the hijack depends on candidate placement, and the intended binary runs when no earlier candidate exists.
Writable-Parent Validation
A realistic writable-parent case was validated using:
C:\ProgramData\Suricata Service\Real Dir\suricata.exe --service-mode -c C:\ProgramData\Suricata\suricata.yaml
Candidate path:
C:\ProgramData\Suricata.exe
Observed result:
module_path=C:\ProgramData\Suricata.exe
argv0=C:\ProgramData\Suricata
user=One
sid=S-1-5-21-3388575877-2469963850-3643760867-1003
argc=6
arg1=Service\Real
arg2=Dir\suricata.exe
arg3=--service-mode
arg4=-c
arg5=C:\ProgramData\Suricata\suricata.yaml
Conclusion: a standard non-admin user can exploit the path resolution primitive when Suricata is installed under a spaced directory beneath a commonly writable parent, or a parent writable in the assessed environment, such as C:\ProgramData.
Writable-Parent ACL and Write Evidence
A standard non-admin user was able to create and delete benign probe files in commonly writable parent locations in the assessed environment:
writable=false path=C:\suricata_usp_write_probe.tmp
writable=false path=C:\Program Files\suricata_usp_write_probe.tmp
writable=false path=C:\Program Files (x86)\suricata_usp_write_probe.tmp
writable=true path=C:\ProgramData\suricata_usp_write_probe.tmp
writable=true path=C:\Users\Public\suricata_usp_write_probe.tmp
writable=true path=%TEMP%\suricata_usp_write_probe.tmp
The C:\ProgramData ACL also showed a BUILTIN\Users write grant in the assessed environment. This supports the deployment-dependent risk distinction: common protected Program Files installs are harder to exploit, while writable-parent service paths can be exploited by a standard user without unusual privileges where parent ACLs permit candidate creation.
Disposable SCM Validation
A disposable Windows service was created from an elevated shell using an intentionally unquoted Suricata-shaped service path beneath a spaced writable-parent directory.
Service binary path used for validation:
C:\ProgramData\SuricataUSP Validation\Real Dir\suricata.exe --service-mode -c C:\ProgramData\SuricataUSPValidation\suricata.yaml
Earlier candidate executable placed:
C:\ProgramData\SuricataUSP.exe
The service configuration confirmed that the BINARY_PATH_NAME was unquoted and the service account was LocalSystem:
SERVICE_NAME: SuricataUSPValidation
TYPE : 10 WIN32_OWN_PROCESS
START_TYPE : 3 DEMAND_START
ERROR_CONTROL : 1 NORMAL
BINARY_PATH_NAME : C:\ProgramData\SuricataUSP Validation\Real Dir\suricata.exe --service-mode -c C:\ProgramData\SuricataUSPValidation\suricata.yaml
DISPLAY_NAME : SuricataUSPValidation
SERVICE_START_NAME : LocalSystem
Starting the disposable service invoked the marker executable at the earlier candidate path and executed it as LocalSystem:
service_main_invoked=1
module_path=C:\ProgramData\SuricataUSP.exe
user=SYSTEM
sid=S-1-5-18
argc=1
arg0=SuricataUSPValidation
The disposable service was stopped/deleted after validation, and the temporary candidate executable and test directories were removed.
Impact Assessment
Confidentiality
High. LocalSystem execution allows access to sensitive files, process memory, credentials, and security tooling state.
Integrity
High. An attacker can modify system configuration, service configuration, Suricata configuration, rules, binaries, logs, and other security controls.
Availability
High. The attacker can prevent Suricata from running, crash the service, remove detection content, or disrupt host/network monitoring.
Security Monitoring Impact
This finding is particularly important because Suricata is itself a security monitoring component. Exploitation can both elevate privileges and degrade defensive visibility.
Blast Radius
The direct blast radius is local to the Windows host. Secondary blast radius depends on:
- credentials accessible from the compromised host;
- Suricata's role in network monitoring;
- trust relationships with SIEM, log collectors, and management systems;
- whether the host has access to packet capture interfaces or sensitive network segments.
Remediation Guidance
Code-Level Fix
The service binary path must quote the executable path separately from its arguments and must escape arguments according to Windows command-line parsing rules.
The service ImagePath should be constructed like:
"C:\Program Files\Suricata\suricata.exe" -c "C:\Program Files\Suricata\suricata.yaml"
Recommended code changes:
- Store the executable path separately from the argument string.
- Quote the executable path unconditionally.
- Escape embedded quotes and backslashes according to Windows command-line parsing rules.
- Quote arguments that contain whitespace or shell-sensitive characters.
- Apply the same helper to both
SCServiceInstallandSCServiceChangeParams. - Fail closed if the final service command line would be truncated.
- Prefer wide-character Windows APIs and a dedicated Windows argument-quoting helper instead of hand-building an ANSI command line with
strlcat. - Add regression tests for paths with spaces, quoted arguments, long paths, Unicode paths, and service parameter updates.
Deployment Mitigation
For deployed systems, inspect the current service path and quote the executable portion if needed:
sc.exe qc Suricata
sc.exe config Suricata binPath= "\"C:\Program Files\Suricata\suricata.exe\" -c \"C:\Program Files\Suricata\suricata.yaml\""
sc.exe qc Suricata
Hardening Recommendations
- Install Suricata under a protected directory with no standard-user write access.
- Avoid service executable paths under
C:\ProgramData,C:\Users\Public,%TEMP%, shared writable directories, or network shares. - Remove suspicious candidate executables such as
C:\Program.exeor deployment-specific candidates likeC:\ProgramData\Suricata.exe. For paths with later spaced components, also check candidates derived from those components, such asC:\Program Files\Suricata Service.exe. - Audit ACLs on the Suricata installation directory and all parent directories.
- Restrict permissions to start, stop, and reconfigure the Suricata service.
- Use application control policies such as WDAC or AppLocker to prevent execution from writable directories.
- Add CI/static analysis checks for
CreateServiceandChangeServiceConfigcall sites that construct service paths.
Risk Classification
Recommended severity: High.
The vulnerability enables arbitrary code execution as LocalSystem when exploited against a vulnerable Windows service configuration. The impact is severe and can result in full host compromise and security monitoring degradation.
It is not classified as Critical because exploitation is local, requires write access to an earlier candidate path, and requires a service start or restart. Risk increases in deployments where Suricata is installed under commonly writable parents, or parents writable in the assessed environment, such as C:\ProgramData or C:\Users\Public, where service restarts are automated, or where Suricata hosts have privileged monitoring access.
OISF may assign a different severity under its own policy. Their Critical category is focused on remotely triggerable, traffic-based issues in default-enabled Tier 1 functionality, while command-line or unlikely-configuration issues may be rated lower. The recommended submission framing is therefore: High local privilege escalation, deployment-dependent, not remote RCE.
Typical protected Program Files deployment:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H = 7.0 High
Writable-parent deployment:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H = 7.8 High
Status: confirmed in the assessed source tree and validated with live SCM execution as LocalSystem. No public Suricata-specific CVE, GHSA, or advisory was identified during review.