Repository navigation
fix(windows): local privilege escalation via binary tampering on unprivileged installs - #16058
Merged
ycombinator merged 9 commits intoAug 11, 2026
Conversation
|
Pinging @elastic/elastic-agent-control-plane (Team:Elastic-Agent-Control-Plane) |
Contributor
|
This pull request does not have a backport label. Could you fix it @ycombinator? 🙏
|
ycombinator
requested review from
leehinman
and removed request for
macdewee and
swiatekm
August 6, 2026 16:57
leehinman
reviewed
Aug 6, 2026
leehinman
left a comment
Contributor
There was a problem hiding this comment.
Overall LGTM.
Claude flagged needing runtime.KeepAlive(sd), needs double checking.
leehinman
previously approved these changes
Aug 6, 2026
ycombinator
force-pushed
the
fix/lpe-windows-unprivileged-binary-tampering
branch
from
August 7, 2026 13:37
5cca22a to
aae1cba
Compare
ycombinator
force-pushed
the
fix/lpe-windows-unprivileged-binary-tampering
branch
from
August 7, 2026 14:54
aae1cba to
15fb6c0
Compare
ycombinator
enabled auto-merge (squash)
August 7, 2026 14:56
leehinman
previously approved these changes
Aug 7, 2026
blakerouse
previously approved these changes
Aug 7, 2026
ycombinator
force-pushed
the
fix/lpe-windows-unprivileged-binary-tampering
branch
from
August 7, 2026 23:24
95051f6 to
8eb2235
Compare
leehinman
approved these changes
Aug 8, 2026
ycombinator
force-pushed
the
fix/lpe-windows-unprivileged-binary-tampering
branch
from
August 10, 2026 15:05
1021e6b to
dc93771
Compare
💔 Build Failed
Failed CI StepsHistory
cc @ycombinator |
The default mask 0770 translated to Modify (write) access for the elastic-agent group on Windows ACLs. Members of that group could replace component binaries and have them executed by the service account after a restart. Changed to 0750 so the group retains read+execute but loses write. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SystemSID was set to S-1-5-32-544 (the Administrators group), the same as AdministratorSID. The SYSTEM account SID is S-1-5-18. This meant the SYSTEM account was never explicitly granted access in FixPermissions ACLs, and the HasStrictExecPerms implementation about to be added would not have correctly allowed SYSTEM write access. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Windows implementation was a no-op stub that always returned nil, allowing the agent to execute component binaries whose ACLs had been tampered with. This meant a member of the elastic-agent group could replace agentbeat.exe and have it run as elastic-agent-user, which holds SeImpersonatePrivilege and can be escalated to SYSTEM. The implementation reads the file's DACL via GetNamedSecurityInfo and checks every ACCESS_ALLOWED_ACE. SYSTEM, Administrators, and the file owner may hold any access; any other SID with write-capable bits (GENERIC_WRITE, GENERIC_ALL, FILE_WRITE_DATA, FILE_APPEND_DATA, FILE_WRITE_EA, FILE_WRITE_ATTRIBUTES, DELETE, WRITE_DAC, WRITE_OWNER) is rejected with an error, preventing execution. HasStrictExecPermsAndOwnership delegates to HasStrictExecPerms; the uid parameter has no Windows equivalent and is ignored. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The SystemSID fix (S-1-5-32-544 → S-1-5-18) means SYSTEM and Administrators are now separate entries in the ACL. Update the unprivileged install check to assert SYSTEM's presence and allow 5 unique SIDs instead of 4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Defensive guard: keeps the SECURITY_DESCRIPTOR alive through the ACE loop so that derived interior pointers (dacl, ace, sid) remain valid even if a future x/sys version backs the SD with Windows-heap memory subject to a GC finalizer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…istinct ACE The SystemSID fix (S-1-5-32-544 → S-1-5-18) means SYSTEM now gets its own ACE separate from Administrators in both privileged and unprivileged installs. Update the privileged branch to expect 3 SIDs (Administrators, SYSTEM, Interactive) and assert SYSTEM is present. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ility Old agents (pre SystemSID fix) collapsed SYSTEM and Administrators into a single ACE (S-1-5-32-544). Upgrade tests install the old agent first, so the pre-upgrade DACL check must not hard-require a distinct SYSTEM ACE. Raise the max SID counts (unprivileged: 5, privileged: 3) to allow SYSTEM when present, but drop the hard assertion so old installs still pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ycombinator
force-pushed
the
fix/lpe-windows-unprivileged-binary-tampering
branch
from
August 10, 2026 17:17
dc93771 to
1a77a57
Compare
blakerouse
approved these changes
Aug 11, 2026
Contributor
Author
|
@Mergifyio backport 9.5 9.4 8.19 |
Contributor
✅ Backports have been createdDetails
Cherry-pick of eb324b0 has failed: To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally |
This was referenced Aug 11, 2026
ycombinator
added a commit
that referenced
this pull request
Aug 11, 2026
…ivileged installs (#16058) (#16154) * fix(windows): remove group write from default install permissions The default mask 0770 translated to Modify (write) access for the elastic-agent group on Windows ACLs. Members of that group could replace component binaries and have them executed by the service account after a restart. Changed to 0750 so the group retains read+execute but loses write. * fix(windows): correct SystemSID constant value SystemSID was set to S-1-5-32-544 (the Administrators group), the same as AdministratorSID. The SYSTEM account SID is S-1-5-18. This meant the SYSTEM account was never explicitly granted access in FixPermissions ACLs, and the HasStrictExecPerms implementation about to be added would not have correctly allowed SYSTEM write access. * fix(windows): implement HasStrictExecPerms to block tampered binaries The Windows implementation was a no-op stub that always returned nil, allowing the agent to execute component binaries whose ACLs had been tampered with. This meant a member of the elastic-agent group could replace agentbeat.exe and have it run as elastic-agent-user, which holds SeImpersonatePrivilege and can be escalated to SYSTEM. The implementation reads the file's DACL via GetNamedSecurityInfo and checks every ACCESS_ALLOWED_ACE. SYSTEM, Administrators, and the file owner may hold any access; any other SID with write-capable bits (GENERIC_WRITE, GENERIC_ALL, FILE_WRITE_DATA, FILE_APPEND_DATA, FILE_WRITE_EA, FILE_WRITE_ATTRIBUTES, DELETE, WRITE_DAC, WRITE_OWNER) is rejected with an error, preventing execution. HasStrictExecPermsAndOwnership delegates to HasStrictExecPerms; the uid parameter has no Windows equivalent and is ignored. * chore: add changelog fragment for Windows LPE security fix * fix(windows): fix sid.String() call — returns one value not two * fix(test): update Windows DACL check to expect SYSTEM as distinct ACE The SystemSID fix (S-1-5-32-544 → S-1-5-18) means SYSTEM and Administrators are now separate entries in the ACL. Update the unprivileged install check to assert SYSTEM's presence and allow 5 unique SIDs instead of 4. * fix(windows): add runtime.KeepAlive(sd) in HasStrictExecPerms Defensive guard: keeps the SECURITY_DESCRIPTOR alive through the ACE loop so that derived interior pointers (dacl, ace, sid) remain valid even if a future x/sys version backs the SD with Windows-heap memory subject to a GC finalizer. --------- (cherry picked from commit eb324b0) Co-authored-by: Shaunak Kashyap <ycombinator@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
ycombinator
added a commit
that referenced
this pull request
Aug 12, 2026
…ivileged installs (#16058) * fix(windows): remove group write from default install permissions The default mask 0770 translated to Modify (write) access for the elastic-agent group on Windows ACLs. Members of that group could replace component binaries and have them executed by the service account after a restart. Changed to 0750 so the group retains read+execute but loses write. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(windows): correct SystemSID constant value SystemSID was set to S-1-5-32-544 (the Administrators group), the same as AdministratorSID. The SYSTEM account SID is S-1-5-18. This meant the SYSTEM account was never explicitly granted access in FixPermissions ACLs, and the HasStrictExecPerms implementation about to be added would not have correctly allowed SYSTEM write access. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(windows): implement HasStrictExecPerms to block tampered binaries The Windows implementation was a no-op stub that always returned nil, allowing the agent to execute component binaries whose ACLs had been tampered with. This meant a member of the elastic-agent group could replace agentbeat.exe and have it run as elastic-agent-user, which holds SeImpersonatePrivilege and can be escalated to SYSTEM. The implementation reads the file's DACL via GetNamedSecurityInfo and checks every ACCESS_ALLOWED_ACE. SYSTEM, Administrators, and the file owner may hold any access; any other SID with write-capable bits (GENERIC_WRITE, GENERIC_ALL, FILE_WRITE_DATA, FILE_APPEND_DATA, FILE_WRITE_EA, FILE_WRITE_ATTRIBUTES, DELETE, WRITE_DAC, WRITE_OWNER) is rejected with an error, preventing execution. HasStrictExecPermsAndOwnership delegates to HasStrictExecPerms; the uid parameter has no Windows equivalent and is ignored. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: add changelog fragment for Windows LPE security fix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(windows): fix sid.String() call — returns one value not two Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(test): update Windows DACL check to expect SYSTEM as distinct ACE The SystemSID fix (S-1-5-32-544 → S-1-5-18) means SYSTEM and Administrators are now separate entries in the ACL. Update the unprivileged install check to assert SYSTEM's presence and allow 5 unique SIDs instead of 4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(windows): add runtime.KeepAlive(sd) in HasStrictExecPerms Defensive guard: keeps the SECURITY_DESCRIPTOR alive through the ACE loop so that derived interior pointers (dacl, ace, sid) remain valid even if a future x/sys version backs the SD with Windows-heap memory subject to a GC finalizer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit eb324b0)
5 tasks done
ycombinator
added a commit
that referenced
this pull request
Aug 12, 2026
…ivileged installs (#16058) (#16153) * fix(windows): remove group write from default install permissions The default mask 0770 translated to Modify (write) access for the elastic-agent group on Windows ACLs. Members of that group could replace component binaries and have them executed by the service account after a restart. Changed to 0750 so the group retains read+execute but loses write. * fix(windows): correct SystemSID constant value SystemSID was set to S-1-5-32-544 (the Administrators group), the same as AdministratorSID. The SYSTEM account SID is S-1-5-18. This meant the SYSTEM account was never explicitly granted access in FixPermissions ACLs, and the HasStrictExecPerms implementation about to be added would not have correctly allowed SYSTEM write access. * fix(windows): implement HasStrictExecPerms to block tampered binaries The Windows implementation was a no-op stub that always returned nil, allowing the agent to execute component binaries whose ACLs had been tampered with. This meant a member of the elastic-agent group could replace agentbeat.exe and have it run as elastic-agent-user, which holds SeImpersonatePrivilege and can be escalated to SYSTEM. The implementation reads the file's DACL via GetNamedSecurityInfo and checks every ACCESS_ALLOWED_ACE. SYSTEM, Administrators, and the file owner may hold any access; any other SID with write-capable bits (GENERIC_WRITE, GENERIC_ALL, FILE_WRITE_DATA, FILE_APPEND_DATA, FILE_WRITE_EA, FILE_WRITE_ATTRIBUTES, DELETE, WRITE_DAC, WRITE_OWNER) is rejected with an error, preventing execution. HasStrictExecPermsAndOwnership delegates to HasStrictExecPerms; the uid parameter has no Windows equivalent and is ignored. * chore: add changelog fragment for Windows LPE security fix * fix(windows): fix sid.String() call — returns one value not two * fix(test): update Windows DACL check to expect SYSTEM as distinct ACE The SystemSID fix (S-1-5-32-544 → S-1-5-18) means SYSTEM and Administrators are now separate entries in the ACL. Update the unprivileged install check to assert SYSTEM's presence and allow 5 unique SIDs instead of 4. * fix(windows): add runtime.KeepAlive(sd) in HasStrictExecPerms Defensive guard: keeps the SECURITY_DESCRIPTOR alive through the ACE loop so that derived interior pointers (dacl, ace, sid) remain valid even if a future x/sys version backs the SD with Windows-heap memory subject to a GC finalizer. --------- (cherry picked from commit eb324b0) Co-authored-by: Shaunak Kashyap <ycombinator@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
ycombinator
added a commit
that referenced
this pull request
Aug 12, 2026
ycombinator
added a commit
that referenced
this pull request
Aug 12, 2026
… binary tampering on unprivileged installs (#16155) * fix(windows): local privilege escalation via binary tampering on unprivileged installs (#16058) * fix(windows): remove group write from default install permissions The default mask 0770 translated to Modify (write) access for the elastic-agent group on Windows ACLs. Members of that group could replace component binaries and have them executed by the service account after a restart. Changed to 0750 so the group retains read+execute but loses write. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(windows): correct SystemSID constant value SystemSID was set to S-1-5-32-544 (the Administrators group), the same as AdministratorSID. The SYSTEM account SID is S-1-5-18. This meant the SYSTEM account was never explicitly granted access in FixPermissions ACLs, and the HasStrictExecPerms implementation about to be added would not have correctly allowed SYSTEM write access. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(windows): implement HasStrictExecPerms to block tampered binaries The Windows implementation was a no-op stub that always returned nil, allowing the agent to execute component binaries whose ACLs had been tampered with. This meant a member of the elastic-agent group could replace agentbeat.exe and have it run as elastic-agent-user, which holds SeImpersonatePrivilege and can be escalated to SYSTEM. The implementation reads the file's DACL via GetNamedSecurityInfo and checks every ACCESS_ALLOWED_ACE. SYSTEM, Administrators, and the file owner may hold any access; any other SID with write-capable bits (GENERIC_WRITE, GENERIC_ALL, FILE_WRITE_DATA, FILE_APPEND_DATA, FILE_WRITE_EA, FILE_WRITE_ATTRIBUTES, DELETE, WRITE_DAC, WRITE_OWNER) is rejected with an error, preventing execution. HasStrictExecPermsAndOwnership delegates to HasStrictExecPerms; the uid parameter has no Windows equivalent and is ignored. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: add changelog fragment for Windows LPE security fix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(windows): fix sid.String() call — returns one value not two Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(test): update Windows DACL check to expect SYSTEM as distinct ACE The SystemSID fix (S-1-5-32-544 → S-1-5-18) means SYSTEM and Administrators are now separate entries in the ACL. Update the unprivileged install check to assert SYSTEM's presence and allow 5 unique SIDs instead of 4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(windows): add runtime.KeepAlive(sd) in HasStrictExecPerms Defensive guard: keeps the SECURITY_DESCRIPTOR alive through the ACE loop so that derived interior pointers (dacl, ace, sid) remain valid even if a future x/sys version backs the SD with Windows-heap memory subject to a GC finalizer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit eb324b0) # Conflicts: # pkg/utils/perm_windows.go * fix: resolve 8.19 Windows permissions backport conflict --------- Co-authored-by: Shaunak Kashyap <ycombinator@gmail.com>
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes a local privilege escalation (LPE) vulnerability on Windows when Elastic Agent is installed with
--unprivileged. Two changes:Removes group write from the default install permission mask (
0770→0750). The groupr-x(read+execute) is sufficient — group members do not need to write to the install directory.Implements
HasStrictExecPermson Windows, which was a no-op stub (return nil). The implementation reads each file's DACL viaGetNamedSecurityInfoand rejects execution if any non-privileged SID (not SYSTEM, not Administrators, not the file owner) holds write-capable access bits (GENERIC_WRITE,GENERIC_ALL,FILE_WRITE_DATA,FILE_APPEND_DATA,FILE_WRITE_EA,FILE_WRITE_ATTRIBUTES,DELETE,WRITE_DAC,WRITE_OWNER).Also fixes an incidental bug where
SystemSIDwas set toS-1-5-32-544(the Administrators group SID) rather thanS-1-5-18(the SYSTEM account SID).Why is it important?
On Windows unprivileged installs, the
elastic-agentgroup was granted Modify (write) access to component binaries (e.g.agentbeat.exe) because the default mask0770translates the group-write bit to Windows Modify permissions. Any local user in theelastic-agentgroup could replace a component binary. After a restart, the agent service would execute the tampered binary aselastic-agent-user, which holdsSeImpersonatePrivilege, enabling escalation toNT AUTHORITY\SYSTEMvia standard potato-class exploits.Checklist
I have made corresponding changes to the documentationI have made corresponding change to the default configuration files./changelog/fragmentsusing the changelog toolI have added an integration test or an E2E testDisruptive User Impact
The permission mask change (
0770→0750) removes write access for members of theelastic-agentgroup to files in the install directory. Group members were only ever intended to interact with the agent via its socket — not to write files. There is no expected disruption to legitimate use cases.How to test this PR locally
Requires a Windows (amd64) machine.
1. Build for Windows (amd64)
PLATFORMS=windows/amd64 mage package # Produces: build/distributions/elastic-agent-<version>-windows-x86_64.zip2. Install in unprivileged mode on the Windows machine
Open PowerShell as Administrator, then:
3. Verify the permission fix (Commit 1)
4. Verify
HasStrictExecPerms(Commit 3)Add a non-privileged user to the
elastic-agentgroup (to simulate the attack surface), grant Everyone write on a component binary, then confirm the agent refuses to start that component:Related issues