Valid HTML 4.01 Transitional Valid CSS Valid SVG 1.0

Me, myself & IT

Tempest in the ‘TEMP’ Directory

Purpose
Reason
Introduction
Vulnerability
Example 1
Example 2
Example 3
Example 4
Detection
Instrumentation
Examination
MSRC Case 62886
Fix
Demonstration
Mitigations
Alternative 1
Alternative 2
Alternative 3

Purpose

Document an easy to exploit trivial vulnerability of Microsoft® Windows NT due to insufficient isolation of privileged processes running under the NT AUTHORITY\SYSTEM alias LocalSystem account.

Reason

While Microsoft runs its WHQL alias Windows Hardware Certification Program to qualify and certify hardware drivers, offering the Windows Hardware Certification Kit (for Window 7, Window 8, Window 8.1, Window Server 2008 R2, Window Server 2012, Window Server 2012 R2) and the Windows Hardware Lab Kit (for Windows 10, Window Server 2016, Window Server 2019, Windows 11, Window Server 2022) to perform their evaluation, a corresponding and complementary Windows Software Quality Laboratory or Windows Software Certification Program, accompanied by a Windows Software Compatibility Kit or a Windows Software Lab Kit, is but missing, resulting in insecurity and miserability of not just hardware drivers shipped by Microsoft as well as third parties, i.e. IHVs, ISVs and OEMs.

Introduction

Since Windows 2000 alias Windows NT 5.0 every user account has its own private %TEMP%\ alias %LOCALAPPDATA%\Temp\ alias %USERPROFILE%\AppData\Local\Temp\ alias %SystemDrive%\Users\%USERNAME%\AppData\Local\Temp\ directory, which is not accessible for other (unprivileged) users.

The user-specific environment variables TEMP and TMP are set with the following registry entries:

REGEDIT4

[HKEY_CURRENT_USER\Environment]
"TEMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"
"TMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"
For the builtin user accounts NT AUTHORITY\SYSTEM alias LocalSystem, NT AUTHORITY\LOCAL SERVICE alias LocalService, and NT AUTHORITY\NETWORK SERVICE alias NetworkService, the user-specific environment variables TEMP and TMP are set with the following registry entries:
REGEDIT4

[HKEY_USERS\S-1-5-18\Environment]
"TEMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"
"TMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"

[HKEY_USERS\S-1-5-19\Environment]
"TEMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"
"TMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"

[HKEY_USERS\S-1-5-20\Environment]
"TEMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"
"TMP"=expand:"%USERPROFILE%\\AppData\\Local\\Temp"
Note: HKEY_USERS\S-1-5-18 is a symbolic link to the registry key HKEY_USERS\.DEFAULT.

The user profiles for the NT AUTHORITY\LOCAL SERVICE alias LocalService and the NT AUTHORITY\NETWORK SERVICE alias NetworkService accounts are stored in the directories %SystemRoot%\ServiceProfiles\LocalService\ and %SystemRoot%\ServiceProfiles\NetworkService\ respectively, which too are not accessible for other (unprivileged) users.

On 32-bit editions of Windows NT the user profile for the NT AUTHORITY\SYSTEM alias LocalSystem account is stored in the directory %SystemRoot%\System32\Config\SystemProfile\, which also is not accessible for other (unprivileged) users.
On 64-bit editions of Windows NT, thanks to the File System Redirector, the user profile for the NT AUTHORITY\SYSTEM alias LocalSystem account is stored in the disjoint directories %SystemRoot%\System32\Config\SystemProfile\ and %SystemRoot%\SysWoW64\Config\SystemProfile\, which are not accessible for other (unprivileged) users.

Note: the (sub)directory %SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\, on 64-bit editions of Windows NT also %SystemRoot%\SysWoW64\Config\SystemProfile\AppData\Local\Temp\, which corresponds to %USERPROFILE%\AppData\Local\Temp\, but doesn’t exist, i.e. the user-specific environment variables TEMP and TMP set for the NT AUTHORITY\SYSTEM alias LocalSystem account have an invalid (dangling) value!

Only for the NT AUTHORITY\SYSTEM alias LocalSystem account the user-specific environment variables TEMP and TMP are but ignored and the system-specific environment variables evaluated instead, which are set with the following registry entries:

REGEDIT4

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment]
"TEMP"=expand:"%SystemRoot%\\TEMP"
"TMP"=expand:"%SystemRoot%\\TEMP"
Note: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet is a symbolic link to the registry key HKEY_LOCAL_MACHINE\SYSTEM\ControlSet‹digit›‹digit›‹digit›, typically HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001.

Due to its inheritable DACL D:PAI(A;CI;0x100026;;;BU)(A;;FA;;;BA)(A;OICIIO;GA;;;BA)(A;;FA;;;SY)(A;OICIIO;GA;;;SY)(A;OICIIO;GA;;;CO) the directory %SystemRoot%\Temp\ is writable for unprivileged users who can create arbitrary files and subdirectories with full access permission for themselves as well as NT AUTHORITY\SYSTEM alias LocalSystem and BUILTIN\Administrators there.

The resulting well-known weaknesses are classified as CWE-378: Creation of Temporary File With Insecure Permissions, CWE-379: Creation of Temporary File in Directory with Incorrect Permissions, CWE-426: Untrusted Search Path and CWE-427: Uncontrolled Search Path Element.

Vulnerability

Processes running under the privileged NT AUTHORITY\SYSTEM alias LocalSystem account use the directory %SystemRoot%\Temp\ as their %TEMP%\ directory, thus defeating their isolation from unprivileged processes.

Setup programs, most notably self-extractors alias wrappers, unpack their payload, which typically includes executable files, to the processes’ %TEMP%\ alias %SystemRoot%\Temp\ directory and run the extracted executable files there.

When stored in a directory other than the system directory %SystemRoot%\System32\ and executed there, programs susceptible to CAPEC-471: Search Order Hijacking load system DLLs and system programs from their application directory instead from the system directory %SystemRoot%\System32\: as documented in the MSDN articles Dynamic-Link Library Security, Dynamic-Link Library Search Order, LoadLibrary() and CreateProcess(), the application directory is searched first per default.

By planting files in the directory %SystemRoot%\Temp\ which are executed from vulnerable programs run there any time later, unprivileged users can elevate their privileges to those of NT AUTHORITY\SYSTEM alias LocalSystem and BUILTIN\Administrators.

Example 1

Skype uses its own proprietary update mechanism instead of Microsoft Update: in version 7, the program %ProgramFiles%\Skype\Updater\Updater.exe is run periodically under the NT AUTHORITY\SYSTEM alias LocalSystem user account. When an update is available, %ProgramFiles%\Skype\Updater\Updater.exe copies or extracts another executable as %TEMP%\SKY‹abcd›.tmp alias %SystemRoot%\Temp\SKY‹abcd›.tmp and executes it using the command line
"%SystemRoot%\Temp\SKY‹abcd›.tmp" /QUIET
This executable is susceptible to CAPEC-471: Search Order Hijacking, it loads multiple system DLLs from its application directory %SystemRoot%\Temp\ instead from Windows’ system directory %SystemRoot%\System32\.

For the full story see Skype – or “Redmond, You’ve got a Problem!”.

Example 2

Deployment and unattended installation of application software is typically performed via an agent or service running with administrative privileges on client computers, for example the built-in Group Policies, as documented in the MSKB article 816102.

For just one case where this still allows local users to escalate their privileges see ADV170017.

Example 3

With Windows 10 20H1 Microsoft moved the installation of (updated) drivers available online from Device Manager to Windows Update.

Contrary to previous versions, where driver installation initiated from Driver Manager runs under an administrator account, driver installation now runs under the NT AUTHORITY\SYSTEM alias LocalSystem user account.

Processes running under a normal user (or administrator) account use its (private) %LOCALAPPDATA%\Temp\ alias %USERPROFILE%\AppData\Local\Temp\ directory, which is not accessible for other (unprivileged) users, as %TEMP%\ directory, while processes running under the NT AUTHORITY\SYSTEM alias LocalSystem account use the (public) %SystemRoot%\Temp\ directory.

Quite some driver packages available on Windows Update contain besides their primary (kernel) drivers, which are typically installed via .inf scripts, also so-called satellites, i.e. additional programs and/or DLLs, which provide interfaces to configure and control the driver and its hardware. These satellites are typically installed by separate setup programs that are run during driver installation.

Note: satellites (really: arbitrary applications) can of course be installed via .inf scripts too, but seldom are.

These setup programs are typically self-extractors which use the %TEMP%\ directory to unpack and run their payload. Instead to create a properly secured subdirectory there, some self-extractors place their payload in the %TEMP%\ directory itself. Many, if not most payloads are but susceptible to CAPEC-471: Search Order Hijacking and execute files planted by unprivileged users in the %TEMP%\ alias %SystemRoot%\Temp\ directory with administrative privileges and access rights.

Example 4

Vulnerable (setup) programs like those in example 3, when run via computer startup scripts.

Detection

Instrumentation

To instrument an installed system for detection, create hardlinks of all 32-bit system DLLs (for example) in the %SystemRoot%\Temp\ directory and configure the advanced logging feature of Software Restriction Policies to track (not only) their execution.

Run the following VBScript elevated, i.e. with administrative privileges, in the 32-bit execution environment:

Rem Copyright © 2004-2024, Stefan Kanthak <stefan‍.‍kanthak‍@‍nexgo‍.‍de>

Option Explicit

Const strCommandLine = "C:\Windows\System32\Cmd.exe /D /K For %? In (*.acm *.ax *.cpl *.dll *.drv *.ocx WBEM\*.dll) Do @MkLink /H C:\Windows\Temp\%~nx? %?"
Const strCurrentDirectory = "C:\Windows\System32"

With GetObject("WinMgmts:{impersonationLevel=Impersonate, (Backup, Restore)}!\\.\Root\CIMv2")
	Dim objProcessStartup
	Set objProcessStartup = .Get("Win32_ProcessStartup").SpawnInstance_
	With objProcessStartup
	'	.CreateFlags = 8	' Detached_Process
	'	.EnvironmentVariables = Array("NoDefaultCurrentDirectoryInExePath=*", _
	'	                              "SYSTEMDRIVE=C:", _
	'	                              "SYSTEMROOT=C:\Windows", _
	'	                              "TEMP=C:\Windows\Temp")
		.ErrorMode = 2		' Fail_Critical_Errors
		.FillAttribute = 240	' Black on White
		.PriorityClass = 32	' Normal
		.ShowWindow = 1		' SW_NORMAL
		.Title = vbNull
		.WinstationDesktop = vbNull
	'	.X = 0
		.XCountChars = 80
	'	.XSize = 640
	'	.Y = 240
		.YCountChars = 50
	'	.YSize = 480
	End With

	Dim intReturn, intProcessID
	intReturn = .Get("Win32_Process").Create(strCommandLine, strCurrentDirectory, objProcessStartup, intProcessID)
	If intReturn <> 0 Then
		WScript.Echo "Error " & intReturn
	Else
		WScript.Echo "Process " & intProcessID & " created"
	End If
End With

With WScript.CreateObject("WScript.Shell")
	.RegWrite "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers\AuthentiCodeEnabled", 0, "REG_DWORD"
	.RegWrite "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers\DefaultLevel", 262144, "REG_DWORD"
'	.RegWrite "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers\ExecutableTypes", vbNull, "REG_MULTI_SZ"
	.RegWrite "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers\LogFileName", "C:\Windows\System32\LogFiles\SAFER.log", "REG_SZ"
	.RegWrite "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers\PolicyScope", 0, "REG_DWORD"
	.RegWrite "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers\TransparentEnabled", 2, "REG_DWORD"
End With
Alternative: run the following batch script in Windows PE after SETUP.exe /NoReboot, with the drive letter of the target volume as its only argument:
Rem Copyright © 2004-2024, Stefan Kanthak <stefan‍.‍kanthak‍@‍nexgo‍.‍de>

If "%~1" == "" Goto :USAGE
If Not "%1" == "%*" Goto :USAGE
If /I Not "%~1" == "%~d1" Goto :USAGE
If "%~d1" == "%SystemDrive%" Goto :USAGE
If Not Exist "%~d1\Windows\System32\Config\SOFTWARE" Goto :USAGE

"%SystemRoot%\System32\Mode.com" CON: LINES=9999
If Exist "%~d1\Windows\SysWoW64" (
For %%? In ("%~d1\Windows\SysWOW64\*.acm"
            "%~d1\Windows\SysWOW64\*.ax"
            "%~d1\Windows\SysWOW64\*.cpl"
            "%~d1\Windows\SysWOW64\*.dll"
            "%~d1\Windows\SysWOW64\*.drv"
            "%~d1\Windows\SysWOW64\*.ocx"
            "%~d1\Windows\SysWOW64\WBEM\*.dll") Do @MkLink /H "%~d1\Windows\Temp\%%~nx?" "%%?"
) Else (
For %%? In ("%~d1\Windows\System32\*.acm"
            "%~d1\Windows\System32\*.ax"
            "%~d1\Windows\System32\*.cpl"
            "%~d1\Windows\System32\*.dll"
            "%~d1\Windows\System32\*.drv"
            "%~d1\Windows\System32\*.ocx"
            "%~d1\Windows\System32\WBEM\*.dll") Do @MkLink /H "%~d1\Windows\Temp\%%~nx?" "%%?"
)
"%SystemRoot%\System32\Reg.exe" LOAD   "HKEY_USERS\SOFTWARE" "%~d1\Windows\System32\Config\SOFTWARE"
"%SystemRoot%\System32\Reg.exe" QUERY  "HKEY_USERS\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers" /S
"%SystemRoot%\System32\Reg.exe" ADD    "HKEY_USERS\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers" /V "AuthentiCodeEnabled" /T REG_DWORD /D 0 /F
"%SystemRoot%\System32\Reg.exe" ADD    "HKEY_USERS\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers" /V "DefaultLevel" /T REG_DWORD /D 262144 /F
"%SystemRoot%\System32\Reg.exe" ADD    "HKEY_USERS\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers" /V "ExecutableTypes" /T REG_MULTI_SZ /D "" /F
"%SystemRoot%\System32\Reg.exe" ADD    "HKEY_USERS\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers" /V "LogFileName" /T REG_SZ /D "C:\Windows\System32\LogFiles\SAFER.log" /F
"%SystemRoot%\System32\Reg.exe" ADD    "HKEY_USERS\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers" /V "PolicyScope" /T REG_DWORD /D 0 /F
"%SystemRoot%\System32\Reg.exe" ADD    "HKEY_USERS\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers" /V "TransparentEnabled" /T REG_DWORD /D 2 /F
"%SystemRoot%\System32\Reg.exe" UNLOAD "HKEY_USERS\SOFTWARE"
Exit /B

:USAGE
Echo Usage: %~nx0 ^<drive letter^>:
Exit /B
Note: since (the hardlinks of) the system DLLs are writable only for the TrustedInstaller account or with SeRestorePrivilege enabled, this instrumentation inhibits attacks via (malicious) fake system DLLs and mitigates the vulnerability!

Note: some hardlinks will become orphans over time and thus undergo bit rot, they are not updated by (security) updates; orphaned hardlinks can be deleted and recreated any time!

Examination

Run the following command line to list the pathnames of files executed in and below the %SystemRoot%\Temp\ directory:
REM Copyright © 2004-2024, Stefan Kanthak <stefan‍.‍kanthak‍@‍nexgo‍.‍de>

FIND.EXE /I "%SystemRoot%\Temp\" "%SystemRoot%\System32\LogFiles\SAFER.log"

MSRC Case 62886

I reported a vulnerability detected during installation of a graphic display driver via Windows Update to the driver’s vendor and the MSRC, where case number 62886 was assigned:

After exchanging several rounds of mail discussing the case they replied with the following statements:

Because this vulnerability exists in a third-party driver installer, Microsoft cannot fix the vulnerability itself. I had contacted NVIDIA in January and was told that the driver in question is out of support.

As for %TEMP% / %TMP% for the SYSTEM account, this is a known issue. We’re experimenting with ways to mitigate this issue in future versions of Windows (https://aka.ms/flighthub). Unfortunately, these mitigations are infeasible on existing Windows versions because they introduce application compatibility issues.

I had asked for more time because I wanted to check if we could implement a limited (i.e., not system-wide) mitigation for the TEMP issue that would not involve updating the third-party installer. After further discussion, my colleagues and I concluded that even a limited mitigation would still introduce application compatibility issues and would not even address all instances of the TEMP issue across all third-party drivers (e.g., third-party drivers that had hardcoded C:\Windows\Temp, though we don’t believe that that’s the case with this specific driver).

Moreover, any TEMP mitigation would not address any memory corruption bugs—like the ones you noted in your original report—in this or other third-party drivers.

We understand that vulnerable drivers are a problem. While we don’t have an immediate solution for this case, MSRC 62886, or other vulnerable third-party drivers (or vulnerable third-party driver installers), we encourage customers to report vulnerable drivers here:

https://www.microsoft.com/en-us/wdsi/driversubmission

Thanks again for reporting this issue, answering our questions, sharing an advance copy of your blog post, and giving us extra time to do some additional analysis.

Fix

To prevent tampering of temporary files, create a properly secured (sub)directory accessible only from processes running with the same or higher integrity level under the current user account, i.e. with the mandatory label S:AI(ML;CIOI;NRNWNX;;;S-1-16-‹integrity level›) and the protected inheritable DACL D:PAI(A;CIOI;0x1301FF;;;OW) that overrides the implicit WDAC alias WRITE_DAC and WO alias WRITE_OWNER access rights granted to the object’s owner first, then create files (and subdirectories) therein, inheriting their parent’s access rights.

Demonstration

Perform the following six simple steps to build the console application TEMPEST.COM from the source presented hereafter and execute it under several user accounts:
  1. Create the text file TEMPEST.C with the following content in an arbitrary, preferable empty directory:

    // Copyright © 2009-2024, Stefan Kanthak <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>
    
    // * The software is provided "as is" without any warranty, neither express
    //   nor implied.
    // * In no event will the author be held liable for any damage(s) arising
    //   from the use of the software.
    // * Redistribution of the software is allowed only in unmodified form.
    // * Permission is granted to use the software solely for personal private
    //   and non-commercial purposes.
    // * An individuals use of the software in his or her capacity or function
    //   as an agent, (independent) contractor, employee, member or officer of
    //   a business, corporation or organization (commercial or non-commercial)
    //   does not qualify as personal private and non-commercial purpose.
    // * Without written approval from the author the software must not be used
    //   for a business, for commercial, corporate, governmental, military or
    //   organizational purposes of any kind, or in a commercial, corporate,
    //   governmental, military or organizational environment of any kind.
    
    #define STRICT
    #define UNICODE
    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    #include <sddl.h>
    #include <aclapi.h>
    
    __declspec(safebuffers)
    BOOL	CDECL	PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
    {
    	WCHAR	szOutput[1024];
    	DWORD	dwOutput;
    	DWORD	dwConsole;
    
    	va_list	vaInput;
    	va_start(vaInput, lpFormat);
    
    	dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
    
    	va_end(vaInput);
    
    	if (dwOutput == 0)
    		return FALSE;
    
    	if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
    		return FALSE;
    
    	return dwConsole == dwOutput;
    }
    
    typedef	struct	_ace
    {
    	ACE_HEADER	Header;
    	ACCESS_MASK	Mask;
    	SID		Trustee;
    } ACE;
    
    static	struct	_acl
    {
    	ACL	acl;
    	ACE	ace;
    } dacl = {{ACL_REVISION, 0, sizeof(dacl), 1, 0},
    	// (A;CIOI;0x1301FF;;;OW)
              {{ACCESS_ALLOWED_ACE_TYPE, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, sizeof(ACE)},
                SYNCHRONIZE | READ_CONTROL | DELETE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD | FILE_TRAVERSE | FILE_WRITE_EA | FILE_READ_EA | FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE | FILE_LIST_DIRECTORY,
                {SID_REVISION, 1, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}},
      sacl = {{ACL_REVISION, 0, sizeof(sacl), 1, 0},
    	// (ML;CIOI;NRNWNX;;;ME)
              {{SYSTEM_MANDATORY_LABEL_ACE_TYPE, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, sizeof(ACE)},
                SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
                {SID_REVISION, 1, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}};
    
    const	SECURITY_DESCRIPTOR	sd = {SECURITY_DESCRIPTOR_REVISION,
    				      0,
    #if 0
    				      SE_DACL_PRESENT | SE_DACL_PROTECTED | SE_SACL_PRESENT | SE_SACL_PROTECTED,
    #else
    				      SE_DACL_PRESENT | SE_DACL_PROTECTED | SE_SACL_PRESENT,
    #endif
    				      (SID *) NULL,
    				      (SID *) NULL,
    				      &sacl.acl,
    				      &dacl.acl};
    
    const	SECURITY_ATTRIBUTES	sa = {sizeof(sa),
    				      &sd,
    				      FALSE};
    
    __declspec(noreturn)
    VOID	CDECL	wmainCRTStartup(VOID)
    {
    	struct	_token_mandatory_label
    	{
    		SID_AND_ATTRIBUTES	Label;
    		SID			Sid;
    	} tml;
    
    	SECURITY_DESCRIPTOR	*lpSD;
    
    	LPWSTR	lpSDDL;
    	DWORD	dwError = ERROR_SUCCESS;
    	DWORD	dwBuffer;
    	WCHAR	szBuffer[MAX_PATH + 2];
    	WCHAR	szUnique[MAX_PATH];
    	UINT	uiUnique = ((GetCurrentProcessId() + GetCurrentThreadId()) >> 1) & 65535;
    	HANDLE	hProcess = GetCurrentProcess();
    	HANDLE	hToken;
    	HANDLE	hConsole = GetStdHandle(STD_ERROR_HANDLE);
    
    	if (hConsole == INVALID_HANDLE_VALUE)
    		dwError = GetLastError();
    	else
    	{
    		if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
    			PrintConsole(hConsole,
    			             L"%ls() returned error %lu\n",
    			             L"OpenProcessToken", dwError = GetLastError());
    		else
    		{
    			if (!GetTokenInformation(hToken,
    			                         TokenIntegrityLevel,
    			                         &tml,
    			                         sizeof(tml),
    			                         &dwBuffer))
    				PrintConsole(hConsole,
    				             L"%ls() returned error %lu for information class %lu\n",
    				             L"GetTokenInformation", dwError = GetLastError(), TokenIntegrityLevel);
    			else
    			{
    				if (!CopySid(sizeof(sacl.ace.Trustee), &sacl.ace.Trustee, tml.Label.Sid))
    					PrintConsole(hConsole,
    					             L"%ls() returned error %lu\n",
    					             L"CopySid", dwError = GetLastError());
    				else
    				{
    					dwBuffer = GetTempPath(sizeof(szBuffer) / sizeof(*szBuffer), szBuffer);
    
    					if (dwBuffer == 0)
    						PrintConsole(hConsole,
    						             L"%ls() returned error %lu\n",
    						             L"GetTempPath", dwError = GetLastError());
    					else
    					{
    						PrintConsole(hConsole,
    						             L"%ls() returned pathname \'%ls\' of %lu characters\n",
    						             L"GetTempPath", szBuffer, dwBuffer);
    
    						do
    						{
    							if (GetTempFileName(szBuffer, L"tmp", uiUnique, szUnique) == 0)
    								PrintConsole(hConsole,
    								             L"%ls() returned error %lu\n",
    								             L"GetTempFileName", dwError = GetLastError());
    							else
    								if (!CreateDirectory(szUnique, &sa))
    								{
    									PrintConsole(hConsole,
    									             L"%ls() returned error %lu for pathname \'%ls\'\n",
    									             L"CreateDirectory", dwError = GetLastError(), szUnique);
    
    									if (dwError == ERROR_ALREADY_EXISTS)
    										continue;
    								}
    								else
    								{
    									dwError = GetNamedSecurityInfo(szUnique,
    									                               SE_FILE_OBJECT,
    									                               OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
    									                               (SID **) NULL,
    									                               (SID **) NULL,
    									                               (ACL **) NULL,
    									                               (ACL **) NULL,
    									                               &lpSD);
    
    									if (dwError != ERROR_SUCCESS)
    										PrintConsole(hConsole,
    										             L"%ls() returned error %lu\n",
    										             L"GetNamedSecurityInfo", dwError);
    									else
    										if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
    										                                                         SDDL_REVISION_1,
    										                                                         OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
    										                                                         &lpSDDL,
    										                                                         (LPDWORD) NULL))
    											PrintConsole(hConsole,
    											             L"%ls() returned error %lu\n",
    											             L"ConvertSecurityDescriptorToStringSecurityDescriptor", dwError = GetLastError());
    										else
    										{
    											PrintConsole(hConsole,
    											             L"Subdirectory \'%ls\' created with security descriptor \'%ls\'\n",
    											             szUnique, lpSDDL);
    
    											if (LocalFree(lpSDDL) != NULL)
    												PrintConsole(hConsole,
    												             L"%ls() returned error %lu\n",
    												             L"LocalFree", dwError = GetLastError());
    										}
    
    									if (GetTempFileName(szUnique, L"tmp", 0, szBuffer) == 0)
    										PrintConsole(hConsole,
    										             L"%ls() returned error %lu\n",
    										             L"GetTempFileName", dwError = GetLastError());
    									else
    									{
    										dwError = GetNamedSecurityInfo(szBuffer,
    										                               SE_FILE_OBJECT,
    										                               OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
    										                               (SID **) NULL,
    										                               (SID **) NULL,
    										                               (ACL **) NULL,
    										                               (ACL **) NULL,
    										                               &lpSD);
    
    										if (dwError != ERROR_SUCCESS)
    											PrintConsole(hConsole,
    											             L"%ls() returned error %lu\n",
    											             L"GetNamedSecurityInfo", dwError);
    										else
    											if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
    											                                                         SDDL_REVISION_1,
    											                                                         OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
    											                                                         &lpSDDL,
    											                                                         (LPDWORD) NULL))
    												PrintConsole(hConsole,
    												             L"%ls() returned error %lu\n",
    												             L"ConvertSecurityDescriptorToStringSecurityDescriptor", dwError = GetLastError());
    											else
    											{
    												PrintConsole(hConsole,
    												             L"File \'%ls\' created with security descriptor \'%ls\'\n",
    												             szBuffer, lpSDDL);
    
    												if (LocalFree(lpSDDL) != NULL)
    													PrintConsole(hConsole,
    													             L"%ls() returned error %lu\n",
    													             L"LocalFree", dwError = GetLastError());
    											}
    
    										if (!DeleteFile(szBuffer))
    											PrintConsole(hConsole,
    											             L"%ls() returned error %lu\n",
    											             L"DeleteFile", dwError = GetLastError());
    									}
    
    									if (!RemoveDirectory(szUnique))
    										PrintConsole(hConsole,
    										             L"%ls() returned error %lu\n",
    										             L"RemoveDirectory", dwError = GetLastError());
    								}
    							break;
    						}
    						while (--uiUnique > 0);
    					}
    				}
    			}
    
    			if (!CloseHandle(hToken))
    					PrintConsole(hConsole,
    					             L"%ls() returned error %lu\n",
    					             L"CloseHandle", GetLastError());
    		}
    
    		if (!CloseHandle(hConsole))
    			PrintConsole(hConsole,
    			             L"%ls() returned error %lu\n",
    			             L"CloseHandle", GetLastError());
    	}
    
    	ExitProcess(dwError);
    }
  2. Run the following four command lines to compile the source file TEMPEST.C created in step 1., link the compiled object file TEMPEST.OBJ and cleanup afterwards:

    SET CL=/GAFS /Gy /O1isy /W4 /Zl
    SET LINK=/ENTRY:wmainCRTStartup /LARGEADDRESSAWARE /NOCOFFGRPINFO /NODEFAULTLIB /OSVERSION:6.0 /RELEASE /SUBSYSTEM:CONSOLE /SWAPRUN:CD,NET /VERSION:0.815
    CL.EXE /FeTEMPEST.COM TEMPEST.C KERNEL32.LIB USER32.LIB
    ERASE TEMPEST.OBJ
    For details and reference see the MSDN articles Compiler Options and Linker Options.

    Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.

    Note: TEMPEST.COM is a pure Win32 console application and builds without the MSVCRT libraries.

    Note: the command lines can be copied and pasted as block into a Command Processor window.

    Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    TEMPEST.C
    TEMPEST.C(85) : warning C4090: 'initializing' : different 'const' qualifiers
    TEMPEST.C(154) : warning C4090: 'function' : different 'const' qualifiers
    
    Microsoft (R) Incremental Linker Version 10.00.40219.386
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    …
  3. Execute the console application TEMPEST.COM built in step 2. under a standard user account or unelevated under a protected administrator account:

    .\TEMPEST.COM
    GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of 35 characters
    Subdirectory 'C:\Users\Stefan\AppData\Local\Temp\tmp1992.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;OICI;0x1301ff;;;OW)S:AI(ML;OICI;NWNRNX;;;ME)'
    File 'C:\Users\Stefan\AppData\Local\Temp\tmp1992.tmp\tmpB9AB.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:(A;;0x1301ff;;;OW)S:AI(ML;ID;NWNRNX;;;ME)'
  4. Execute the console application TEMPEST.COM built in step 2. elevated under a protected administrator account:

    .\TEMPEST.COM
    GetTempPath() returned pathname 'C:\Users\Stefan\AppData\Local\Temp\' of 35 characters
    Subdirectory 'C:\Users\Stefan\AppData\Local\Temp\tmp2A86.tmp' created with security descriptor 'O:BAG:S-1-5-21-820728443-44925810-1835867902-513D:P(A;OICI;0x1301ff;;;OW)S:AI(ML;OICI;NWNRNX;;;HI)'
    File 'C:\Users\Stefan\AppData\Local\Temp\tmp2A86.tmp\tmpB9AB.tmp' created with security descriptor 'O:BAG:S-1-5-21-820728443-44925810-1835867902-513D:(A;;0x1301ff;;;OW)S:AI(ML;ID;NWNRNX;;;HI)'
  5. Execute the console application TEMPEST.COM built in step 2. under the BUILTIN\Administrator account:

    .\TEMPEST.COM
    GetTempPath() returned pathname 'C:\Users\Administrator\AppData\Local\Temp\' of 42 characters
    Subdirectory 'C:\Users\Administrator\AppData\Local\Temp\tmp3CDA.tmp' created with security descriptor 'O:BAG:S-1-5-21-3150931553-3643200234-2488609525-513D:P(A;OICI;0x1301ff;;;OW)S:AI(ML;OICI;NWNRNX;;;HI)'
    File 'C:\Users\Administrator\AppData\Local\Temp\tmp3CDA.tmp\tmp4B12.tmp' created with security descriptor 'O:BAG:S-1-5-21-3150931553-3643200234-2488609525-513D:(A;;0x1301ff;;;OW)S:AI(ML;ID;NWNRNX;;;HI)'
  6. Execute the console application TEMPEST.COM built in step 2. under the NT AUTHORITY\SYSTEM alias LocalSystem account:

    .\TEMPEST.COM
    GetTempPath() returned pathname 'C:\Windows\Temp\' of 16 characters
    Subdirectory 'C:\Windows\Temp\tmp4894.tmp' created with security descriptor 'O:SYG:SYD:P(A;OICI;0x1301ff;;;OW)S:AI(ML;OICI;NWNRNX;;;SI)'
    File 'C:\Windows\Temp\tmp4894.tmp\tmp6E0C.tmp' created with security descriptor 'O:SYG:SYD:(A;;0x1301ff;;;OW)S:AI(ML;ID;NWNRNX;;;SI)'

Mitigations

Never create or execute files directly in the %SystemRoot%\Temp\ directory; always create a properly secured subdirectory which other (less privileged) users can’t access first, then create your files there!

Since this doesn’t help with existing self-extractors which ignore this basic rule of isolation and privilege separation, better use the following alternative mitigation.

Alternative 1

Create the missing subdirectory %SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\, on 64-bit systems also %SystemRoot%\SysWoW64\Config\SystemProfile\AppData\Local\Temp\, owned by the NT AUTHORITY\SYSTEM alias LocalSystem user account, and set the system-specific environment variables TEMP and TMP to the value %USERPROFILE%\AppData\Local\Temp.

Use the following command lines in the batch script %SystemRoot%\Setup\Scripts\SetupComplete.cmd to apply these changes automatically during Windows Setup:

Rem Copyright © 2009-2024, Stefan Kanthak <stefan‍.‍kanthak‍@‍nexgo‍.‍de>

MkDir "%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp"
If Exist "%SystemRoot%\SysWoW64\Config\SystemProfile" MkDir "%SystemRoot%\SysWoW64\Config\SystemProfile\AppData\Local\Temp"

"%SystemRoot%\System32\SetX.exe" TEMP "%%USERPROFILE%%\AppData\Local\Temp" /M
"%SystemRoot%\System32\SetX.exe" TMP "%%USERPROFILE%%\AppData\Local\Temp" /M
Exit /B
Note: this mitigation also stops many other attacks and thus prevents vulnerabilities like Microsoft Windows Defender Elevation of Privilege Vulnerability alias CVE-2020-1170 from being exploited in the first place!

Caveat: on 64-bit systems, the disjoint directories may cause surprising behaviour!

Alternative 2

Add the ACEs (D;OIIO;WP;;;WD) and (D;CIOIIO;WD;;;OW) to the DACL of the %SystemRoot%\Temp\ directory: the first ACE denies execute permission for all files created there, the second ACE denies the respective owner of files and subdirectories the (otherwise implied) permission to change their DACL:
ICACLs.exe "%SystemRoot%\Temp" /Deny *S-1-1-0:(OI)(IO)(X) *S-1-3-4:(CI)(OI)(IO)(WDAC) /C /Q /T

Also set the attributes hidden and system to prevent File Explorer from granting full access to any UAC controlled administrator account, as documented in the MSKB article 950934.

Use the following command lines in the batch script %SystemRoot%\Setup\Scripts\SetupComplete.cmd to apply these changes automatically during Windows Setup:

Rem Copyright © 2009-2024, Stefan Kanthak <stefan‍.‍kanthak‍@‍nexgo‍.‍de>

"%SystemRoot%\System32\CACLs.exe" "%SystemRoot%\Temp" /S:"D:PAR(D;CIOIIO;WD;;;OW)(D;OIIO;WP;;;WD)(A;CI;0x100026;;;BU)(A;;FA;;;BA)(A;OICIIO;GA;;;BA)(A;;FA;;;SY)(A;OICIIO;GA;;;SY)(A;OICIIO;GA;;;CO)"
"%SystemRoot%\System32\Attrib.exe" +H +R +S "%SystemRoot%\Temp" /M
Exit /B

Alternative 3

See detection above!

Note: the instrumentation prevents only attacks via fake system DLLs; it does not prevent similar attacks via fake system programs like Cmd.exe etc., which may be called from batch scripts running in the %SystemRoot%\Temp\ directory via unqualified filenames.

Contact and Feedback

If you miss anything here, have additions, comments, corrections, criticism or questions, want to give feedback, hints or tipps, report broken links, bugs, deficiencies, errors, inaccuracies, misrepresentations, omissions, shortcomings, vulnerabilities or weaknesses, …: don’t hesitate to contact me and feel free to ask, comment, criticise, flame, notify or report!

Use the X.509 certificate to send S/MIME encrypted mail.

Note: email in weird format and without a proper sender name is likely to be discarded!

I dislike HTML (and even weirder formats too) in email, I prefer to receive plain text.
I also expect to see your full (real) name as sender, not your nickname.
I abhor top posts and expect inline quotes in replies.

Terms and Conditions

By using this site, you signify your agreement to these terms and conditions. If you do not agree to these terms and conditions, do not use this site!

Data Protection Declaration

This web page records no (personal) data and stores no cookies in the web browser.

The web service is operated and provided by

Telekom Deutschland GmbH
Business Center
D-64306 Darmstadt
Germany
<‍hosting‍@‍telekom‍.‍de‍>
+49 800 5252033

The web service provider stores a session cookie in the web browser and records every visit of this web site with the following data in an access log on their server(s):


Copyright © 1995–2024 • Stefan Kanthak • <‍stefan‍.‍kanthak‍@‍nexgo‍.‍de‍>