Windows permissions cheat-sheet

Fri, 21 October 2022 :: #windows

I wouldn't use Windows if I wouldn't be forced to use it. Unfortunately, trying to evade it is like trying to evade snails after the rain -- whatever you do, snails ain't interested in it. So the best course of action is to embrace it: keep your friends close, but your enemies closer, as they say.

One recent problem that I had was to build a specific Access Control List for a directory structure on a network share. I'm really biased when it comes to Windows; I really dislike its GUI-oriented configuration, mostly because I often don't understand it. Sometimes in order to decode the actual configuration model is nearly damn impossible when it's encrypted by the GUI. It's a lesser problem for an old Windows user who remembers that some particular dialog box is from Windows 3.11, another is from Windows95, but another one is new -- it was introduced in the shiny Windows 10, but before Microsoft switched the GUI toolkits again, so we can expect it to go away in Windows 11. I don't subscribe to such problems. I'm also getting salty, so I'll leave it there.

Anyway, I wanted to build a directory structure like this:

  • D:\SHARE

This would be a directory that nobody else should have access to. This directory should have subdirectories in it. Each of the directories should have their own ACL lists specifying what user is allowed to access a specific directory. So, for example:

  • D:\SHARE\BobDir

This should be only accessible by Bob.

  • D:\SHARE\AliceDir

And this should be only accessible by Alice. Bob shouldn't be able to enter it.

At the same time, both Bob nor Alice shouldn't be able to access D:\SHARE.

Such configuration is possible to be created in the console by using two commands: net share and icacls.

Create a network share

Creation of a network share is done by the net share command. Running it without the arguments will list all the shares that are configured on your machine:

PS$ net share

Share name   Resource                        Remark

C$           C:\                             Default share
D$           D:\                             Default share
E$           E:\                             Default share
IPC$                                         Remote IPC
ADMIN$       C:\WINDOWS                      Remote Admin
share        d:\share
The command completed successfully.

Ignore the shares ending with the $ character. I bet even MVPs don't understand why they're really there. If you're curious, then you can read about them here.

In order to create a share, just run the net share command like this:

PS$ net share sharename=D:\directory /grant:everyone,read

The sharename is the name of your share, which will be later displayed to the user which is connecting to your machine via CIFS. The d:\directory is -- as expected -- the directory which you would like to share. The /grant option is interesting, becuase it's the first layer of user permission setting. Here we allow everyone to access the share; you can limit what users can access it right here, but also you can specify the "everyone" rule, and configure the ACL list later by using icacls.

Viewing ACLs

It seems that nobody knows why the default ACL editor, icacls, is named this way. However, I have some suggestions:

  • I See ACLs,
  • I Control ACLs.

On Windows there is also a deprecated command called cacls, which might mean "control ACLs". After checking out its help page I've noticed that the main difference between cacls and icacls is that cacls is missing options related to ACL inheritance, so actually icacls could also mean something like this:

  • Inheritance-aware Control tool for Access Control Lists.

So anyway, by using icacls we can build up a list with usernames and bind some permissions to those usernames. This will be the second layer of security, so if you have any permission-denied problems during your adventues, please first re-check if those problems aren't really inside the first layer (the net share layer of permission setting, described above).

PS D:\SHARE$ icacls .
. BUILTIN\Administrators:(I)(F)
  NT AUTHORITY\Authenticated Users:(I)(M)
  NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(IO)(M)

Successfully processed 1 files; Failed processing 0 files

First thing you need to realize is that this list doesn't have a proof strong enough to judge that the icacls designer was using LSD. It's not enough, though it's damn close.

Each line is one rule. These strange keywords between ( and ) are actually flags, and they describe the rule itself. There are 3 classes of flags.

Allow/deny flags

There are just two flags here. The (DENY) flag, if exists, means that this rule describes a DENY rule instead of ALLOW rule. Example:

  • (DENY)(R) -- this rule means that we explicitly forbid read access for this particular user.

There is also the (N) flag, which denies access completely for this particular user.

Inheritance control flags

There is just one. It's either specified, or missing:

  • (I) means that the rule is inherited from above (in this case, from the D:\ directory).

    If the (I) is there, it means this rule can't be removed, because the rule is not actually here. It's in the parent container. If we want to get rid of it, we need to disable rule inheritance for this directory altogether. This will remove all (I) rules. So it's either all or nothing. Becase Windows is such a wonderful system, it won't warn you when you try to remove an (I) rule:

    PS$ icacls . /remove "BUILTIN\Users"
    processed file: .
    Successfully processed 1 files; Failed processing 0 files

    I guess the testers at Microsoft don't test console tools.

  • (OI) means that this rule will be propagated automatically to new files inside this directory (shortcut for Object Inherit),

  • (CI) means that this rule will be propagated automatically to new directories inside this directory (shortcut for Container Inherit),

  • (IO) is a special flag that means that this rule actually shouldn't be applied to this directory, but rather to all subdirectories or subfiles inside this directory (some hardcore stuff right there -- shortcut for Inherit Only)

  • If the rule doesn't have (OI), (CI) nor (IO), it means that it applies only to this directory, and won't be propagated automatically to subfiles nor subdirectories.

Those flags are most commonly used in combos:

  • (OI)(CI) -- this rule applies to current directory, and it will be propagated to subdirectories and files,
  • (OI)(CI)(IO) -- this rule DOESN'T apply to current directory, but will apply to subdirectories and files,
  • (CI)(IO) -- this rule DOESN'T apply to current directory, but will apply to subdirectories,
  • (OI)(IO) -- interpretation of this rule is left as an excersise to the reader ;).

Permission control flags

These flags actually describe the user permissions: what users can do.

  • (F) means that the rule gives full rights to the user,
  • (M) means modification access. Not sure if the user can delete the resource; I haven't checked it.
  • (RX) means read-and-execute access. For directories, execution means ability to list the directory contents.
  • (R) means read-only access. For directories, it means that the directory listing is disallowed.
  • (W) means write-only access.
  • There are also other, advanced rights, but it's too easy to mess things up with those. If you want to experiment, check out the icacls manual here.

Building the ACL

More of less that's it, we can proceed with building our list or ACEs.

In my particular case, the first layer of security related to the net share command was to allow everyone to the share. The first layer contains a pretty primitive tools for specifying access, so best would be to completely bypass it by allowing everyone to enter, and just rely on ACLs to filter people out.

First step is to disable access to everyone except myself to the root directory of the share. It's possible to do it by disabling inheritance on the D:\SHARE directory and by adding an explicit rule to allow access from my account:

PS$ icacls d:\share /grant "antek:(CI)(OI)F" /inheritance:r

This will effectively mark the d:\share directory as impossible to read by anyone that is not me.

Next, create two directories for Bob and Alice:

PS$ mkdir d:\share\alice
PS$ mkdir d:\share\bob

and set the necessary read and traverse rights:

PS$ icacls d:\share\alice /grant "alice:(CI)(OI)RX"
PS$ icacls d:\share\bob /grant "bob:(CI)(OI)RX"

And that's really it. Bob can access the share by entering directly to the \\antek\share\bob share, and Alice can enter directly the \\antek\share\alice directory. Bob can't enter Alice's directory, and Alice can't enter Bob's directory. While the owner of the machine can enter all directories.

The silly thing is that it's harder to configure that by using the GUI, and it's pretty clear how ACLs work when spending some time on the command line docs. One interesting thing for future research would be to abuse the ACL files generated by the /save command. Those files can be "restored" by using the /restore command. Is it possible to create ACLs that would be impossible to build using the GUI or icacls command? The default order of rule evaluation is:

  • Explicit deny,
  • Explicit grant,
  • Inherit deny,
  • Inherit grant,

but is this order enforced by the ACL subsystem, or just by the tools used to build ACLs?

Who knows!