Home > Articles > Programming > Windows Programming

A .NET Developer's Guide to Windows Security: Understanding User Profiles

  • Print
  • + Share This
Understanding just what user profiles are, and how they interact with the Windows registry system is key to keeping a secure system. Making sure that your programs store settings in the user-specific settings trees, rather than the program directory, will help keep passwords and other vital information secure.
This chapter is from the book

Have you ever noticed that the first time a particular user logs on to a machine it takes a little while longer for the shell (typically explorer.exe) to start up? You can hear the disk drive whirring and clunking—obviously something is going on. Subsequent logons are much faster. What's happening is this: A profile is being created on the machine for the user.

A user profile consists of a home directory for the user, along with some standard subdirectories and files that allow the operating system to store per-user settings. If you're sitting in front of a computer, bring up Explorer and surf to the Documents and Settings folder, which is on the drive where the operating system was installed. You should see subdirectories for all user principals that have ever logged on interactively to the machine. If you view hidden folders (which I recommend for this experiment), you'll see one called Default User. It's this folder that's being copied when you first log on with a new user account. This is the seed for all new user profiles.

If you drill down into your own user profile, you'll see a couple of hidden files, called NTUSER.DAT and NTUSER.DAT.LOG, which make up the registry hive for your user profile. Bring up the registry editor and look under HKEY_USERS to see what I mean. The operating system dynamically loads the subkeys under HKEY_USERS as users log on and off interactively. To see this happen, bring up a command prompt and run the following command using a local account on your machine (I'll assume you're using a user account named Alice):

runas /u:Alice cmd
   

You'll be prompted for a password, and once you enter it you'll see a new command prompt that's running under an interactive logon for Alice. Refresh the registry editor, and you'll see a couple of new keys under HKEY_USERS. These keys point into the NTUSER.DAT file in Alice's home directory. Close this new command prompt and refresh the registry editor again. You should see that those keys have now disappeared. The profile has been unloaded.

HKEY_CURRENT_USER is a very interesting key. The operating system dynamically maps it onto one of the subkeys under HKEY_USERS based on the security context you're running in when you open it. Thus, if I were to run the following code from Alice's command prompt, I would be reading from her registry hive.

using System;
using Microsoft.Win32;

class ReadFromUserProfile {
  static void Main() {
    // this opens HKEY_CURRENT_USER
    RegistryKey hkcu = Registry.CurrentUser;

    foreach (string keyName in hkcu.GetSubKeyNames()) {
      Console.WriteLine(keyName);
    }
  }
}

On the other hand, if I were to run this same code from a command prompt as myself, I would be reading from the registry hive in my own user profile and looking at an entirely different set of data. Note that the mapping of HKEY_CURRENT_USER is affected by impersonation (Item 31), so if a process running as Bob uses a thread impersonating Alice to open HKEY_CURRENT_USER, Alice's hive will be opened, not Bob's. Of course, this assumes that Alice's profile has been loaded, as you'll see later in this item.

Now, you might wonder why I'm spending so much time on how the registry works, given that it's being deemphasized in .NET. What you've got to keep in mind is that the operating system still relies quite a bit on the registry for tracking per-user settings. So, when you do something as simple as set the background color of a form to SystemColors. Window, under the covers you're actually doing a registry lookup under HKEY_CURRENT_USER to find out what window color the current user prefers. If you've ever worked with certificate stores, [1] you should be aware that they are stored on a per-user basis (unless you specifically request a machine-wide store). This sort of thing is important to know when you're writing server code because you often need to ensure that you actually have a user profile loaded to be able to access your certificates. But more on loading user profiles later!

When writing desktop applications, it's critical that you store your settings under the user profile as opposed to your program's installation directory. If you try to store data anywhere under the Program Files directory tree, your program will break when run by a nonprivileged user (Item 8). The DACL (Item 43) on Program Files allows only privileged users to write to that directory tree. Go see for yourself! But in Alice's user profile, she's the owner of all the files and subdirectories. So no matter which user is running your program, you'll always be able to store settings under that user's profile. As I describe in Item 9, Isolated Storage works out of the user profile, so it's a great way to store application settings and it even works in most partial-trust scenarios!

Here's another experiment: Bring up a command prompt running under your own account (just start one from Explorer) and run the following command from there:

set u
   

This will display all environment variables that start with the letter "u." Do the same thing from Alice's command prompt and notice how the environment variables are different there. The environment variable USERPROFILE is really important, as it points applications to the appropriate place to store per-user settings. You'll never need to look at this variable directly because the programs you write will use .NET Framework classes to figure out where various user profile directories are located. To see how this works, compile the following C# console application and run it in your two command prompts to see how the output changes depending on the user running it.

using System;

class WhereIsMyUserProfile {
  static void Main() {
    string myDocuments = Environment.GetFolderPath(
      Environment.SpecialFolder.Personal);
    string desktop = Environment.GetFolderPath(
      Environment.SpecialFolder.DesktopDirectory);
    string localAppData = Environment.GetFolderPath(
      Environment.SpecialFolder.LocalApplicationData);

    Console.WriteLine("My Documents: {0}", myDocuments);
    Console.WriteLine("Desktop: {0}", desktop);
    Console.WriteLine("Local App Data: {0}", localAppData);
  }
}

I show an even more compelling example for Windows Forms programmers in Item 9.

There's a special user profile called All Users, where you should store state shared by all users of your application (note that Isolated Storage never uses this shared profile, so you can't use Isolated Storage to store shared state for an application). Be very careful with shared state. Remember that all users of the machine are granted read-write permissions to the All Users folder. This means that a malicious user can write malformed data to one of your shared files, causing your application to malfunction the next time some other, innocent user runs it. Frankly, though, you should consider all data coming from any of your users to be untrusted, regardless of which user profile it comes from (Item 1). You don't ever want to crash or, even worse, lose control of your application because some user tweaked one of the files in her user profile!

The last point I want to make has to do with daemons (Item 27). You see, loading a user profile (setting up the environment, loading a new registry hive under HKEY_USERS, etc.) takes a nontrivial amount of time. For example, compare the time it takes to run the following two commands (first close any programs you might already be running as Alice to ensure that her user profile is unloaded).

runas /u:Alice /noprofile cmd
   runas /u:Alice cmd
   

If you use the first command, you will see that no registry hive is loaded. Although this allows the new program to launch quicker, it also means that the program won't have access to Alice's user profile. In fact, run the following command from the noprofile command prompt for Alice:

set u
   

The USERPROFILE points to the Default User folder. It would be a mistake to try writing to this profile because it's not the right one for Alice. In fact, this is the profile used by SYSTEM. If Alice isn't an administrator, she won't be allowed to write to this profile (look at the DACL on the Default User home directory to see what I mean, and note that it's a hidden folder).

Normal desktop applications won't need to worry about this because Windows will always load a user profile for a user who logs on inter actively via winlogon.exe. But some daemons won't have their user profiles loaded for them, as I mention in Item 27, and this can cause you major headaches if you're not prepared for it. The most notable case is for COM servers because the COM SCM doesn't load profiles for a COM server configured to run as a particular user.

It's possible to load a user profile programmatically using the Win32 API LoadUserProfile, but this is a privileged operation so a well-configured daemon that runs with least privilege (Item 4) won't be able to load its own profile. For that reason, when working with operating system functionality such as the certificate stores I mentioned earlier, or even the DPAPI (Item 70), you'll need to use machine-level as opposed to user-level functionality if you know you won't have a profile loaded.

  • + Share This
  • 🔖 Save To Your Account