One successful technique in social engineering is pretending to be someone or something you're not and hoping the security guard who's forgotten their reading glasses doesn't look too closely at your fake ID. Of course there's no hyperopic guard in the Windows OS, but we do have an ID card, the Access Token which proves our identity to the system and let us access secured resources.
The Windows kernel provides simple capabilities to identify fake Access Tokens, but sometimes the kernel or other kernel-mode drivers are too busy to use them correctly. If a fake token isn't spotted during a privileged operation local elevation of privilege or information disclosure vulnerabilities can be the result. This could allow an attacker to break out of an application sandbox, elevate to administrator privileges or even compromise the kernel itself.
This presentation is about finding and then exploiting the incorrect handling of tokens in the windows kernel as well as first and third party drivers. Examples of serious vulnerabilities such as CVE-2015-0002 and CVE-2015-0062 will be presented. It will provide clear exploitable patterns so that you can do your own security reviews for these issues. Finally I'll discuss some of the ways of exploiting these types of vulnerabilities to elevate local privileges.
2. James Forshaw @tiraniddo
Obligatory Background Slide
● Researcher in Google’s
Project Zero team
● Specialize in Windows
○ Especially local privilege
escalation
● Never met a logical
vulnerability I didn’t like
2
3. James Forshaw @tiraniddo
What I’m Going to Talk About
● Windows Access Security
○ Security Reference Monitor
○ Access Tokens
○ Impersonation
● Kernel Token Handling Vulnerability Classes and Descriptions
● Kernel Token Handling Vulnerability Exploitation
3
4. James Forshaw @tiraniddo
Windows Kernel
Windows Security Components
4
Security
Reference Monitor
Process
Resource
Group SID
SACL
DACL
Security Descriptor
Owner SID
Access
Token
User Calls System Call
Handlers
Kernel and
Driver Code
Resource Access Check
Se* API
9. James Forshaw @tiraniddo
Token Categories
9
Normal Token Linked Token
Filtered Token LowBox Token
UAC
NtFilterToken
NtCreateLowBoxToken
10. James Forshaw @tiraniddo
Important Token Fields
● Token User SID
● Token ID (unique 64 bit value assigned when token created)
● Parent Token ID (parent token ID when creating filtered tokens)
● Token Group SIDs
● Token Privileges
● Token Authentication ID (64 bit value ties it to a logon session)
● Token Flags (holds pre-tested privilege check values for speed)
● Restricted Token SIDs
● Lowbox Token SIDs
10
11. James Forshaw @tiraniddo
● Tokens in user mode accessed via handles
● Can specify certain access rights based on DACL access check
Important Token Access Rights
11
TOKEN_DUPLICATE Required to duplicate the token
TOKEN_ASSIGN_PRIMARY Required to assign the token to a new process
TOKEN_IMPERSONATE Required to impersonate the token
TOKEN_ADJUST_DEFAULT Required to set the integrity level of a token
TOKEN_ADJUST_PRIVILEGES Required to enable/disable privileges
14. James Forshaw @tiraniddo
Token Duplication
14
Primary Impersonation
Always Allowed
Can duplicate token’s between types if have TOKEN_DUPLICATE access on handle
Duplicated Token can be referenced with any set of access rights
15. James Forshaw @tiraniddo
Token Duplication
15
Primary Impersonation
Always Allowed
Only if level >= Impersonate
Can duplicate token’s between types if have TOKEN_DUPLICATE access on handle
Duplicated Token can be referenced with any set of access rights
16. James Forshaw @tiraniddo
Token Duplication
16
Primary Impersonation
New Token LUID for Each Token
Can duplicate token’s between types if have TOKEN_DUPLICATE access on handle
Duplicated Token can be referenced with any set of access rights
17. James Forshaw @tiraniddo
Setting an Impersonation Token
● Impersonation assigns a token to a thread, replaced the token used
in access checks for the majority of system calls
17
Direct Setting
SetThreadToken()
ImpersonateLoggedOnUser()
NtSetInformationThread(...)
Indirect Setting
ImpersonateNamedPipeClient()
RpcImpersonateClient()
CoImpersonateClient()
Kernel Setting
PsImpersonateClient()
SeImpersonateClient/Ex()
18. James Forshaw @tiraniddo
Impersonation Security
18
If Token Level
< Impersonate
If Process has
Impersonate
Privilege
ALLOWED
Restrict to
Identification
Level
If Process IL <
Token IL
If Process User
== Token User
PsImpersonateClient(...) ► SeTokenCanImpersonate(...)
19. James Forshaw @tiraniddo
NtAccessCheck versus SeAccessCheck
NtAccessCheck
● User mode system call
● Takes an impersonation token
handle
● Only verifies impersonation level
>= Identification
19
SeAccessCheck
● Kernel mode only
● Takes a subject context
● Verifies impersonation level >=
Impersonation
20. James Forshaw @tiraniddo
Identification Tokens == Fake IDs
20
● Would seem the biggest challenge is getting an impersonation
token as a normal user
● Want to get hold of local system, or potentially arbitrary user
22. James Forshaw @tiraniddo
LogonUser
● Can be used to get a token if the user’s password is known
● Returns a full impersonation token which is configured for the caller
to impersonate with no additional privileges
● No need to play games
22
HANDLE hToken;
LogonUser("UserName", ".", "Password",
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, &hToken);
ImpersonateLoggedOnUser(hToken);
23. James Forshaw @tiraniddo
UAC Linked Tokens
● If split token admin get linked token using GetTokenInformation
● Without TCB privilege you only get back an identification token
23
TOKEN_LINKED_TOKEN linked_token;
GetTokenInformation(hToken, TokenLinkedToken,
&linked_token, ...);
// Identification level token for the admin
HANDLE hToken = linked_token.LinkedToken;
24. James Forshaw @tiraniddo
Named Pipes
● Well known trick, get a system service to open a named pipe
● Call ImpersonateNamedPipeClient on pipe server handle
○ Normally needs pipe to be written to to get impersonation token so
send over SMB (localhostpipemynamedpipe)
● Get service like Anti-Virus to scan the path to capture token
24
HANDLE hPipe = CreateNamedPipe(".dummy", ...);
// Start Windows defender to scan localhostpipedummy
ConnectNamedPipe(hPipe, NULL);
ImpersonateNamedPipeClient(hPipe);
25. James Forshaw @tiraniddo
RPC/DCOM
● Many DCOM services over RPC support impersonation
● Plenty of scope for callbacks to be made as privileged user
25
// Set callback in system COM object (e.g. BITS)
HRESULT ComCallback() {
if(SUCCEEDED(CoImpersonateClient())) {
HANDLE hToken;
OpenThreadToken(GetCurrentThread(),
..., &hToken);
}
}
26. James Forshaw @tiraniddo
NTLM Negotiation
● LSASS exposes APIs to do network authentication
● Can get localhost NTLM authentication using redirected WebDAV
● Creates an impersonation level token even for a normal user
26
// Start fake WebDAV service on localhost
// Init a AV scan to localhostfakefake
BYTE* type1 = GetType1();
BYTE* challenge;
AcceptSecurityContext(hContext type1, &challenge);
BYTE* result = SetChallengeAndGetResult(challenge);
AcceptSecurityContext(hContext, result, ...);
QuerySecurityContextToken(hContext, &hToken);
27. James Forshaw @tiraniddo
Services For User (S4U)
● LsaLogonUser has an S4U mode
● At least on Windows 8.1 can get you a identification token for any
local and remote user without any privileges
● Can’t get local system/local service/network service tokens
● Can craft token source which might be useful for certain things
27
30. James Forshaw @tiraniddo
How the Kernel Code Interacts with Tokens
30
System Calls (returns a HANDLE)
● ZwCreateToken(Ex)
● ZwOpenProcessToken(Ex)
● ZwOpenThreadToken(Ex)
● ZwFilterToken
Process/Thread Manager Calls (returns a
ACCESS_TOKEN pointer)
● PsReferencePrimaryToken
● PsReferenceImpersonationToken
● PsReferenceEffectiveToken
● SeCaptureSubjectContext
Direct Access (returns a TOKEN pointer)
● EPROCESS::Token
● ETHREAD::ClientSecurity::ImpersonationToken
○ Only valid if ETHREAD::CrossThreadFlags bit
4 is set
IO Manager
● IRP_MJ_CREATE SecurityContext
31. James Forshaw @tiraniddo
Not Checking Impersonation Level
31
● When checking the token it’s important kernel code verifies
impersonation level
struct SECURITY_SUBJECT_CONTEXT {
PACCESS_TOKEN ClientToken;
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
PACCESS_TOKEN PrimaryToken;
PVOID ProcessAuditId;
} context;
#define SeQuerySubjectContextToken(c)
(c->ClientToken ? c->ClientToken : c->PrimaryToken)
SeCaptureSubjectContext(&context);
// Get impersonation token or primary token
PACCESS_TOKEN token = SeQuerySubjectContextToken(&context)
// Do something with token Bad!, no check on ImpersonationLevel
32. James Forshaw @tiraniddo
Variants
32
SECURITY_IMPERSONATION_LEVEL imp_level;
PACCESS_TOKEN token = PsReferenceImpersonationToken(
PsGetCurrentThread(),
..., &imp_level);
if (token == NULL) {
token = PsReferencePrimaryToken(PsGetCurrentProcess());
}
// Do something with token
SECURITY_IMPERSONATION_LEVEL imp_level;
TOKEN_TYPE token_type;
PACCESS_TOKEN token = PsReferenceEffectiveToken(
PsGetCurrentThread(),
&token_type,
..., &imp_level);
// Do something with token
33. James Forshaw @tiraniddo
Real Example CVE-2015-0002
33
BOOLEAN AhcVerifyAdminContext()
{
TOKEN_USER *user;
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
PACCESS_TOKEN token = PsReferenceImpersonationToken(
PsCurrentThread(), ...,
&ImpersonationLevel);
if (token == NULL) {
token = PsReferencePrimaryToken(PsCurrentProcess());
}
SeQueryInformationToken(token, TokenUser, &user);
if(RtlEqualSid(user->User->Sid, LocalSystemSid) || SeTokenIsAdmin(token)) {
return TRUE;
}
return FALSE;
}
Bad!, no check on ImpersonationLevel
35. James Forshaw @tiraniddo
Is SeTokenIsAdmin Also Bad?
● CVE-2015-0002 was Windows 8+ only
● Similar bug existed on Windows 7, but could also bypass
SeTokenIsAdmin as well
35
// Win7
BOOLEAN SeTokenIsAdmin(PACCESS_TOKEN Token) {
return SepSidInToken(Token, SeExports->AdminGroupSid);
}
// Win8+
BOOLEAN SeTokenIsAdmin(PACCESS_TOKEN Token) {
if (Token->TokenType == TokenImpersonation &&
Token->SecurityLevel < SecurityImpersonation)
return FALSE;
return SepSidInToken(Token, SeExports->AdminGroupSid);
}
36. James Forshaw @tiraniddo
Crafted Subject Context
36
PSECURITY_DESCRIPTOR sd = ...;
PACCESS_TOKEN primary_token =
PsReferenceEffectiveToken(...);
SECURITY_SUBJECT_CONTEXT context = {0};
context->PrimaryToken = primary_token;
if (SeAccessCheck(sd, &context, ...) {
// Do a privileged action
}
37. James Forshaw @tiraniddo
Crafted Subject Context
37
PSECURITY_DESCRIPTOR sd = ...;
PACCESS_TOKEN primary_token =
PsReferenceEffectiveToken(...);
SECURITY_SUBJECT_CONTEXT context = {0};
context->PrimaryToken = primary_token;
if (SeAccessCheck(sd, &context, ...) {
// Do a privileged action
}
BOOLEAN SeAccessCheck(...,
PSECURITY_SUBJECT_CONTEXT context, ...,
PNTSTATUS AccessStatus) {
// Some initialization
if (context.ClientToken
&& context.ImpersonationLevel < SecurityImpersonation) {
*AccessStatus = STATUS_ACCESS_DENIED;
return FALSE;
}
// Do access check with token
}
38. James Forshaw @tiraniddo
Real Example RtlpAllowsLowBoxAccess
38
NTSTATUS RtlpAllowsLowBoxAccess(wchar_t* atomname) {
SubjectSecurityContext.PrimaryToken = PsReferenceEffectiveToken(
PsGetCurrentThread(),
...);
SubjectSecurityContext.ProcessAuditId =
PsGetCurrentProcess()->UniqueProcessId;
BOOLEAN result = SeAccessCheckWithHint(
SeAtomSd,
0,
&SubjectSecurityContext,
...);
// Handle result
}
39. James Forshaw @tiraniddo
System Thread Impersonation
● Impersonation usually through SeImpersonateClient
● Can also use PsImpersonateClient
● If an explicit impersonation level is passed it will override what’s in
the token
39
VOID DoWorkThread(LPVOID arg) {
PACCESS_TOKEN token = arg;
PsImpersonateClient(PsCurrentThread(), token,
..., SecurityImpersonation);
}
PACCESS_TOKEN token = PsReferenceImpersonationToken(...);
PsCreateSystemThread(&hThread, ..., DoWorkThread, token);
40. James Forshaw @tiraniddo
Leaky Tokens
● Kernel code can capture access token objects just like any other
kernel object and manipulate it
● Bad code could return the token to user mode as a handle
○ Only requires you pass the access check
● This goes against the leakage of tokens through impersonation as
you can get hold of primary tokens from other processes
● Can be used to elevate privileges in restricted token scenarios
40
// Capture token in one process
PACCESS_TOKEN token = PsReferenceEffectiveToken();
CapturedAccessToken = token;
// Read in another
OpenCapturedAccessToken(PHANDLE TokenHandle, ACCESS_MASK Access) {
ObOpenObjectByPointer(CapturedAccessToken, Access, TokenHandle);
}
41. James Forshaw @tiraniddo
Real Example CVE-2015-0078
● Undocumented win32k system call NtUserGetClipboardToken
introduced in Windows 8.1
41
Win32k
Medium IL
Process 1
W
rite
to
Clipboard
Captured
Token
Low IL Process
Process 1
NtUserG
etClipboardToken
Open for read access
42. James Forshaw @tiraniddo
Why Is This a Problem?
● GENERIC_READ access gives TOKEN_DUPLICATE access right
● Duplicate to get a writable token then lower IL level
● As long as the same user allows us to assign the token.
42
TOKEN_MANDATORY_LABEL TokenMandatoryLabel;
TokenMandatoryLabel.Label.Attributes = SE_GROUP_INTEGRITY;
TokenMandatoryLabel.Label.Sid = MediumMandatoryLevelSid;
SetTokenInformation(hNewToken,
TokenIntegrityLevel,
&TokenMandatoryLabel,
sizeof(TOKEN_MANDATORY_LABEL));
44. James Forshaw @tiraniddo
Incorrect Token Duplication
● External duplication APIs, NtDuplicateToken and
SeDuplicateToken check for impersonation level < Impersonate
● Kernel code can also use internal duplication function
SepDuplicatetoken
● No checks done on the original token that it’s compatible for
duplication
44
45. James Forshaw @tiraniddo
Real Example CVE-2015-0078
● CreateProcessAsUser takes a token handle to assign as primary
token
● Documented as requiring a Primary Token
45
46. James Forshaw @tiraniddo
Digging into NtCreateUserProcess
46
NTSTATUS PspReferenceTokenForNewProcess(
HANDLE token,
KPROCESSOR_MODE AccessMode,
TOKEN **outtok)
{
result = ObReferenceObjectByHandle(token,
TOKEN_ASSIGN_PRIMARY,
SeTokenObjectType,
AccessMode, outtok);
return result;
}
Only requires TOKEN_ASSIGN_PRIMARY privileges
47. James Forshaw @tiraniddo
Token Creation
NSTATUS SeSubProcessToken(_EPROCESS *process,
_TOKEN *token,
_TOKEN **subtoken)
{
OBJECT_ATTRIBUTES objattr;
InitializeObjectAttributes(&objattr);
SepDuplicateToken(token, &objattr, TokenPrimary,
SecurityAnonymous, subtoken);
// Do rest of initialization
}
47
Explicitly Duplicate as
Primary
48. James Forshaw @tiraniddo
SeAssignPrimaryTokenPrivilege
● Limit to general exploitability, caller process must have privilege or
meet a set of criteria to assign token
● System services (such as IIS) have privilege so can be used as an
EoP there, but what about user processes?
48
BOOLEAN has_assign_privilege = FALSE;
if (SeSinglePrivilegeCheck(SeAssignPrimaryTokenPrivilege))
has_assign_privilege = TRUE;
BOOLEAN can_assign;
SeIsTokenAssignableToProcess(tokena, &can_assign);
if ( !can_assign && !has_assign_privilege )
return STATUS_PRIVILEGE_NOT_HELD;
49. James Forshaw @tiraniddo
SeIsTokenAssignableToProcess
49
Parent Token Sibling Token
Process
Token
Token ID
Assigned
Token
Parent
Token ID
Equal
CreateRestrictedToken
Process
Token
Parent
Token ID
Auth ID
Assigned
Token
Parent
Token ID
Auth ID
Equal
Equal
DuplicateToken
NtCreateLowBoxToken
OR
50. James Forshaw @tiraniddo
LowBox Token Hierarchy
● LowBox Tokens used in things like IE EPM are siblings of the main
user’s token.
● Using this issue can capture an identification token from main user
and use that to create a new process with elevated privileges
● Only restriction is must be at same IL, which for IE EPM is Low
50
52. James Forshaw @tiraniddo
TOCTOU Issues
● Some bugs need to change the token between one call and the
next
● Can be done from another thread
● Useful to bypass lower SeAccessCheck then subsequent additional
checks
52
void DoUserAction()
{
if (SeAccessCheck(...)) {
PACCESS_TOKEN token = PsReferenceEffectiveToken();
PSID sid = GetUserSid(token);
// Do operations as that use
}
}
53. James Forshaw @tiraniddo
IO Manager TOCTOU
● IO Manager Only Captures Security during IRP_MJ_CREATE
processing
● No subsequent IO calls are explicitly checked, so sometimes
drivers do it manually
53
void UseDriver()
{
// Security check made here
HANDLE hDevice = CreateFile(".DeviceName", ...);
ImpersonateLoggedOnUser(hFakeId);
// No further security checks
DeviceIoControl(hDevice, ...);
}
54. James Forshaw @tiraniddo
Real Example CVE-2015-0011
● MRXDAV driver implements kernel mode component of WebDAV
UNC path support
● Accessible through DeviceWebDavRedirector device, which does
a security check
● Functionality implemented in Device IO Control commands, which
does the following:
54
// Do Device IO Control
SeCaptureSubjectContext(&ctx)
SeQueryAuthenticationId(SeQuerySubjectContextToken(&ctx),
&luid);
if(luid == LocalSystemLUID)
is_local_system = true;
if (is_local_system)
// Do privileged operation
55. James Forshaw @tiraniddo
Vulnerable Zw Calls
● Calls to ZwOpenThreadToken and ZwOpenProcessToken are
deprecated, replaced with Ex Variants.
● This is because these calls were supposed to only be used from
user mode and not the kernel
○ Didn’t specify a handles flag option
○ Meant all handles were opened as user-mode handles
○ Potential for a user-mode process to get access to privileged handles
55
58. James Forshaw @tiraniddo
Windows 10 Changes (Build 10159)
58
If Token Level
< Impersonate
If Process has
Impersonate
Privilege
ALLOWED
Restrict to
Identification
Level
If Process IL <
Token IL
If Process User
== Token User
Elevation
Check
Capability
Check
59. James Forshaw @tiraniddo
Windows 10 Impersonation Capability
● New Group and Capability SIDs to add impersonation capability
○ Session Impersonation Group
○ Constrained Impersonation Group
○ Constrained Impersonation Capability
59
If Same Session and
Process Token has
Session Impersonation
Group
If LowBox Token and
Process Token has
Constrained
Impersonation Group
If LowBox Token and
Process Token has
Constrained
Impersonation Group
If same LowBox Package
SID
Also needs SepAllowSessionImpersonationCap
set in Kernel Flags
60. James Forshaw @tiraniddo
Windows 10 Elevated Token Impersonation
● Blocks impersonating an elevated token unless process token is
also elevated
● Must be enabled in SeCompatFlags kernel flag
● Blocks trick used to elevate privileges with
NtUserGetClipboardToken
60
if (SeTokenIsElevated(ImpersonationToken)) {
if ((SeCompatFlags & 1)
&& !SeTokenIsElevated(ProcessToken)) {
return STATUS_PRIVILEGE_NOT_HELD;
}
}