itm4n/PrivescCheck

Unquoted Path Check - C:\ is reported as a writable path

itm4n opened this issue Β· 13 comments

itm4n commented

In the "Unquoted path check", a path such as C:\Program Files\SomeService\service.exe is reported as being exploitable because the ACL indicates that BUILTIN\Users can create files in C:\. This is a false positive and needs to be filtered out.

Name                    : ServiceName
ImagePath               : C:\Program Files\SomeService\service.exe
ModfiablePath           : C:\
IndentityReference      : BUILTIN\Users
Permissions             : WriteData/AddFile
Status                  : Running
UserCanStart            : False
UserCanRestart          : False
itm4n commented

I did take this into account in a previous commit but my solution actually worked only on Windows 10.
I made sure that the user had the AddFile permission and it was ok because the ACL of the system drive's root folder on Windows 10 doesn't contain such ACE.

Though, on older versions of Windows, the ACL is different and users have the AddFile permission (although they can't actually create files).

So yeah, basically I have to manually exclude C:\ from the list when it's returned as a vulnerable path. :/

itm4n commented

I added a check to make sure the reported path is not the root of the system drive (usually C:\).
From now on, these false positives should always be filtered out.
I tested it on Windows Server 2012 and Windows Server 2008R2, it seems to work. :)

Hi!

While this solution filters out possible false positives, it would also (if I understand correctly) not report an actually writable C:\ directory (in the admittedly rare case where permissive ACLs have been set manually), which is a bigger problem than outputting false positives.

I'm guessing the issue is due to ACL propagation rules, where ACLs can be applied to only sub-items of the target directory and not on the directory itself (for instance the CreateFile entry for Users below):
Screenshot from 2021-02-18 11-40-53

Same info with icacls, which shows the related inheritance flags (relevant line is BUILTIN\Users:(CI)(IO)(WD), the pair (CI)(IO) limits the ACL to the child/grandchild folders):
Screenshot from 2021-02-18 11-41-34

The correct solution would be, in my opinion, to somehow retrieve the inheritance information/flags with PowerShell, and compare them with this joyous mess of possible pairings in order to determine whether they actually apply to the target folder.

I might have missed something making this irrelevant, if so please let me know πŸ™‚

itm4n commented

This fix was implemented to handle a very particular case. On Windows < 10, users had the AddFile right in C:\ but they could not really create files. As far as I know, it's because of a mitigation that was actually implemented to circumvent unquoted path exploits (such as C:\Program Files\blah.exe). I still don't know how this really works.
So, the script would report C:\ as being writable whereas it was not. PowerUp had the exact same issue by the way.

On Windows 10, this mitigation is probably still present although users no longer have the AddFile right as you pointed out.
This means, that even if the ACL would allow users to create files in C:\, they would not really be able to do so.

If you have a Windows 10 VM, you could create a snapshot and test this scenario. I'm actually quite curious about the result. πŸ™‚

Hmm, I'm not seeing this behaviour on my test VM (up-to-date 20H2 installed in version 20H2) where I manually set the CreateFile privilege on C:\ for Users:
Screenshot from 2021-02-18 18-46-16

(Non-privileged user, the ACLs on the C:\ directory allow Users to Read and Execute (RX) and Add File (WD) on the target directory, subdirectories, and files ((OI)(CI)), the non-privileged user still managed to copy C:\Windows\System32\cmd.exe to C:\Program.exe).

However I did have for some reason default ACLs granting Authenticated Users Modify access on the C:\ directory, I had to manually remove this ACL in order to test this. I don't know where that one came from. Moreover, once I actually tried to transform this into a privesc, I could not start the service (Error 1053: The Service did not Respond to the Start or Control Request in a Timely Fashion) with C:\Program.exe.

This is still pretty blurry to me because of the variables in play (is it Windows version-specific? Have I installed software that set this Modify ACL for Authenticated Users? Why didn't the service start after creating C:\Program.exe?), but based on this single observation it looks like this setup could result in a false negative by PrivescCheck.

So after further investigation and new Windows installations I was able to shed some light on these questions:

  • The ACLs of the C:\ directory seem to be edition-specific:

    • Windows 10 20H2 Pro N: An ACL allows Authenticated Users to Modify C:\ itself: Privesc possible by default
    • Windows 10 20H2 Enterprise: ACLs allow Authenticated Users to Modify subdirectories and files of C:\, and to CreateFolder on C:\ itself: Privesc not possible by default
    • Windows Server from 2012 R2 to 2019 1809: ACLs allow Users to CreateFile on subdirectories of C:\ only, and CreateFolder on C:\ itself and its subdirectories: Privesc not possible by default
  • Even though an error is shown, the illegitimate C:\program.exe is still being executed (on an up-to-date 10 20H2 Pro N), so it does not look like a mitigation is in place.

So from these test cases, enterprise-oriented editions seem to have restrictive default ACLs, while consumer-oriented editions do not? Interesting observation. Most importantly, PrivescCheck does not detect possible unquoted path privescs when non-privileged users are allowed to CreateFile in C:\ πŸ™‚

itm4n commented

Wow! Thank you for your work! 😲
Conclusion: it's a mess. πŸ˜„
As I won't check every possible version of Windows, I will probably revert back to the original implementation.
Another thing I could do is try to create a random file such as C:\foo.txt and see if an error is triggered.
Although I'm not a big fan of this kind of solution, it would be really effective in this scenario.
What do you think?

To be honest I'm not really a fan of writing a file to disk either, I like the idea of being able to run PrivescCheck in-memory and it leaving (as far as I know ?) no trace on the FS (apart from potential PS logging I guess).

The Get-Acl command does include InheritanceFlags and PropagationFlags in each entry of its Access attribute. It surely is possible to use this info to retrieve whether the ACL applies to the directory itself. I'll try on my end, but you're welcome to try too/improve on it as I don't have a lot of PS experience. πŸ™ƒ

In the meantime, I agree that the original implementation does look better for now. Maybe if you feel like it with a disclaimer/warning in the banner, hopefully temporarily until we find a correct solution. πŸ™‚

itm4n commented

OMG, I just had a "Eureka" moment. My assumption was based on something that someone told me a long time ago. But this was not correct and I never verified it by myself.
You're completely right. The PropagationFlags and InheritanceFlags values hold the information we need in order to handle such case properly. And actually, this is a broader issue as the same problem could also occur on other directories (although it's not that common).
This should be fixed directly in the Get-ModifiablePath function. I should make sure that, in the case of a Directory object, the ACE applies to the current folder by checking the value of PropagationFlags. If the ACE applies only to subdirectories I should just discard it.
I think that's it, that's the solution! πŸ™‚
I need to work on that this weekend, I'll keep you posted.

itm4n commented

I think I solved the issue globally with a few changes in Get-ModifiablePath.

As you know, this function is in two parts. First, it creates a candidate path list and then it iterates this list and checks the ACL of each file/folder. To do so, it invokes Get-Acl on each candidate path and then it iterates the list of ACEs.

So, first things first, I transformed the original ForEach-Object loop into a "proper" ForEach ($i in $array) { ... } loop. I did that because this allows the use of the break and continue keywords. You probably don't care but I found it interesting to mention. πŸ˜„

Here is the key part. I spent some time reading the documentation and experimenting on a few Virtual Machines. You were totally on the right track with the InheritanceFlags and PropagationFlags values. These are key values that determine how ACEs are applied to an object and its children.

  • The value of InheritanceFlags determines how the ACE is inherited by child objects, so it is of no use for us.
  • The value of PropagationFlags determines how the ACE propagates to the child objects and the object itself. This second value is really interesting.

To summarize, PropagationFlags can have the following values:

  • None (0): no propagation rule is defined, therefore the ACE applies to the object and its children.
  • NoPropagateInherit (1): the ACE is not propagated to child objects, therefore the ACE only applies to the object itself.
  • InheritOnly (2): the ACE is propagated only to child objects, therefore it does not apply to the object itself.

At first, I had quite a hard time getting my head around the PropagationFlags value because it should be considered "in reverse". For example, if the value is None, it does not mean that the ACE does not propagate. It's the opposite, it means that it applies to the object and its children.

Conclusion: I added a simple test case to check whether the value InheritOnly is set. If so, I simply ignore the ACE and I go to the next one.

if ($Ace.PropagationFlags -band ([System.Security.AccessControl.PropagationFlags]"InheritOnly").value__) {
    continue
}

It should be noted that, in the case of a file, these two values seem to always be set to 0, which makes sense.

Hopefully, the issue is fixed now. It would be really great if you could test on your VMs as well though. πŸ™‚

That was fast, well done! Your solution is way more elegant than what I would have come up with. πŸ™‚

Thank you for your work and for your detailled explanation, that is much appreciated. That issue was a good opportunity to learn about NTFS permissions.

I've tried on two VMs and it does work as expected, perfect!
Capture d’écran 2021-02-20 211719
(Left is actually vulnerable, Right is not as the only allowed action is CreateFolder.)

Cheers !

For information, update on this statement:

The ACLs of the C:\ directory seem to be edition-specific:

* Windows 10 20H2 **Pro N**: An ACL allows `Authenticated Users` to `Modify` `C:\` itself: Privesc **possible** by default
* Windows 10 20H2 **Enterprise**: ACLs allow `Authenticated Users` to `Modify` subdirectories and files of `C:\`, and to `CreateFolder` on `C:\` itself: Privesc **not possible** by default
* Windows Server from 2012 R2 to 2019 1809: ACLs allow `Users` to `CreateFile` on subdirectories of `C:\` only, and `CreateFolder` on `C:\` itself and its subdirectories: Privesc **not possible** by default

It turns out this issue seems to be specific to the first release of 20H2. 2004 did not have permissive ACLs, and a new version of the 20H2 iso has been released (Win10_20H2_v2_Language_x64.iso) which does not suffer from this issue (ACLs are the same as in 2004).

So I guess finding this specific privesc will be even more unlikely. πŸ™‚

itm4n commented

Yup, exactly. But this particular privesc has always been more or less unlikely anyway, even on older versions of Windows.

If you find other bugs, you know what to do. πŸ™‚
Your feedback is really great. With all the information you provide each time, fixing the issues is a lot easier.

In the meantime, I close this issue (again).