locked
Possible security problem with CreateDirectory API RRS feed

  • General discussion

  • I’ve discovered some confusing behavior with regard to the CreateDirectory API which may constitute a security issue.  In most cases, calls to CreateDirectory result in calls to ZwCreateFile with the following options: Directory, Synchronous IO Non-Alert, Open Reparse Point (you can see this in a Process Monitor capture).  However, under certain conditions, the “Open For Backup” option is also included, and this can cause the system to create the directory even when the user doesn’t have permission to create directories in that location.  Here are the steps to reproduce this behavior:

     

    1. Create a directory and give it a single deny ACE for Everyone so that no users have access to that directory.
    2. Launch a process and enable the SeTcbPrivilege so that it can impersonate users.
    3. Have the process attempt to create a subdirectory of the “noaccess” directory by calling CreateDirectory.  This will fail with an access denied error, as expected.
    4. Call LogonUser to obtain a token for an administrator, then call ImpersonateLoggedOnUser with that token to impersonate that user.
    5. Repeat step #3.  It will successfully create the subdirectory.

     

     

     

     

     

    Note that the process does not need the SeBackupPrivilege (or SeRestorePrivilege) to hit the condition where CreateDirectory utilizes the “backup semantics” flag.  I had thought that those privileges were required to use the backup semantics flag.  In addition, that flag does not seem to be used if the impersonated user is not a member of the Administrators group. 

     

    Is the behavior I have described intentional?  It seems like a security issue to allow a user (even an administrator) create a directory in a directory that explicitly denies that user access.  I understand that backup/restore applications are given extra leeway to access files and folders they wouldn’t otherwise be able to access, but as I’ve mentioned I can reproduce this behavior even without those privileges.

     

    The source code for a test application that demonstrates this issue is below:

    #include "windows.h"
    
    bool	EnablePrivilege(HANDLE process_token, wchar_t* privilege_name)
    {
    	LUID luidPrivilegeLUID;
    	if (!LookupPrivilegeValue( NULL, privilege_name, &luidPrivilegeLUID ) )
    	{
    		printf(  "LookupPrivilegeValue('%ls') failed (%ld)\n", privilege_name, (long)GetLastError());
    		return false;
    	}
    
    	TOKEN_PRIVILEGES tpTokenPrivilege;
    	tpTokenPrivilege.PrivilegeCount = 1;
    	tpTokenPrivilege.Privileges[0].Luid = luidPrivilegeLUID;
    	tpTokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    
    	AdjustTokenPrivileges( process_token, FALSE, &tpTokenPrivilege, sizeof TOKEN_PRIVILEGES, NULL, NULL );
    	if ( GetLastError() != NO_ERROR )
    	{
    		printf(  "AdjustTokenPrivileges('%ls') failed(%ld)\n", privilege_name, GetLastError() );
    		return false;
    	}
    	return true;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	if (argc != 4)
    	{
    		printf("Usage: CreateFolder <username> <password> <path>\n");
    	}
    	else
    	{
    		HANDLE	process_handle;
    		process_handle = GetCurrentProcess();
    		if ( process_handle == NULL )
    		{
    			printf(  "GetCurrentProcess() failed (%ld)\n", (long)GetLastError());
    			return false;
    		}
    
    		HANDLE	process_token;
    		if (!OpenProcessToken( process_handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &process_token ) )
    		{
    			printf(  "OpenProcessToken() failed (%ld)\n", (long)GetLastError());
    			return false;
    		}
    
    		if (!EnablePrivilege(process_token, SE_TCB_NAME))
    		{
    			return false;
    		}
    
    		HANDLE token = NULL;
    		if (LogonUser(argv[1], L"", argv[2], LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_DEFAULT, &token))
    		{
    			printf("Attemping to create folder prior to impersonating user.\n");
    			if (CreateDirectoryW(argv[3], NULL))
    			{
    				printf("CreateDirectory succeeded on first try - this isn't a good demonstration of the problem.\n");
    			}
    			else
    			{
    				printf("CreateDirectory failed with error %ld.\n", GetLastError());
    
    				printf("Impersonating user.\n");
    				if (ImpersonateLoggedOnUser(token))
    				{
    					printf("Impersonating user complete.\n");
    
    					if (CreateDirectoryW(argv[3], NULL))
    					{
    						printf("CreateDirectory succeeded.\n");
    					}
    					else
    					{
    						printf("CreateDirectory failed with error %ld.\n", GetLastError());
    					}
    				}
    				else
    				{
    					printf("ImpersonateLoggedOnUser failed with error %ld.\n", GetLastError());
    				}
    
    				CloseHandle(token);
    			}
    		}
    		else
    		{
    			printf("LogonUser failed with error %ld.\n", GetLastError());
    		}
    	}
    
    	return 0;
    }
    
    


    Monday, January 9, 2012 3:26 PM