Unquoted Path Check - C:\ is reported as a writable path
itm4n opened this issue Β· 13 comments
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
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. :/
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):
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):
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 π
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
:
(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
toModify
C:\
itself: Privesc possible by default - Windows 10 20H2 Enterprise: ACLs allow
Authenticated Users
toModify
subdirectories and files ofC:\
, and toCreateFolder
onC:\
itself: Privesc not possible by default - Windows Server from 2012 R2 to 2019 1809: ACLs allow
Users
toCreateFile
on subdirectories ofC:\
only, andCreateFolder
onC:\
itself and its subdirectories: Privesc not possible by default
- Windows 10 20H2 Pro N: An ACL allows
-
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:\
π
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. π
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.
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!
(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. π
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).