Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to trick this WindowsIdentity code into using the wrong user?

Tags:

TL;DR Can the user token contained in a WindowsIdentity's Token property (say, someIdentity.Token) be spoofed such that:

var validated = new WindowsIdentity(someIdentity.Token); 

...will return an instance that claims to represent a user that has not, in fact, been authenticated, and yet has IsAuthenticated set true, valid .Name and .User properties, etc.?

Below I put a few boundaries on this; it's presumably impossible to completely spoof-proof.


The full story:

In this answer, Damien_The_Unbeliever cleverly demonstrated that some code of mine could be tricked into believing it had a valid authenticated user in a WindowsIdentity instance when it didn't. Long story short, my code was assuming that if Thread.CurrentPrincipal.Identity was an instance of WindowsIdentity and IsAuthorized was true, that it represented an authenticated user and I could rely on the SID in .User:

WindowsIdentity identity = Thread.CurrentPrincipal == null     ? null     : Thread.CurrentPrincipal.Identity as WindowsIdentity;  if (identity != null && identity.IsAuthenticated && !identity.IsAnonymous) {     // ...use and trust the SID in identity.User, the     // username in identity.Name, etc.... } 

(There's a reason this code is using the thread rather than WindowsIdentity.GetCurrent().)

His code to trick that (slightly modified):

var ident = WindowsIdentity.GetCurrent(); Thread.CurrentPrincipal = new WindowsPrincipal(ident); var fakeSid = new SecurityIdentifier("S-1-3-0"/* E.g., some SID you want to trick me into believing is the real user */); typeof(WindowsIdentity).GetField("m_user", BindingFlags.Instance | BindingFlags.NonPublic)     .SetValue(ident, fakeSid); 

And sure enough, if you do that then call my code above, my code gets fooled. Kudos Damien.

So in true arms race fashion, here's my revised code that catches the spoof and denies it:

WindowsIdentity identity = Thread.CurrentPrincipal == null     ? null     : Thread.CurrentPrincipal.Identity as WindowsIdentity;  if (identity != null && identity.IsAuthenticated && !identity.IsAnonymous) {     var validated = new WindowsIdentity(identity.Token);     if (!validated.User.Equals(identity.User) || !validated.IsAuthenticated || validated.IsAnonymous) {         // Something fishy is going on, don't trust it     } else {         // Good! Use the validated one         identity = validated;         // ...use and trust the SID in identity.User, the         // username in identity.Name, etc....     } } 

As you can see, that takes the Token from the provided identity and creates a new WindowsIdentity instance using that token. If the SIDs of the identities match, we continue, trusting the validated one. (The documentation for WindowsIdentity(IntPtr token) says that the initial value of IsAuthenticated will be false, but that's simply wrong in my tests assuming I've created it with a valid user token.)

The only way I can see that that could be tricked would be with a spoofed user token that nevertheless passes the validations Windows does with it. That seems unlikely to me. But then, this is an area of ignorance for me.


Boundaries:

I should note that I'm just shooting for a reasonable degree of single sign-on security here, doing my due dilience. If a malicious app has successfully started intercepting system calls / compromised Windows itself, well, there's just not a lot I'm going to be able to do about that. As Damien pointed out in comments on that other question, he could probably build a host container that completely ignored strong naming (and thus could give me a completely fake WindowsIdentity type). Fair enough. Perfection kills. I just don't want to leave doors open like the one Damien kindly demonstrated. If I released a system and it were hacked in the field that easily, I'd be pretty darned embarrassed about it. :-)

like image 226
T.J. Crowder Avatar asked Nov 06 '15 18:11

T.J. Crowder


People also ask

What is WindowsIdentity C#?

Returns a WindowsIdentity object that represents the Windows identity for either the thread or the process, depending on the value of the ifImpersonating parameter. GetCurrent(TokenAccessLevels) Returns a WindowsIdentity object that represents the current Windows user, using the specified desired token access level.

What is WindowsPrincipal?

The WindowsPrincipal class is primarily used to check the role of a Windows user. The WindowsPrincipal. IsInRole method overloads let you check the user role by using different role contexts.


1 Answers

To demonstrate this is doable, let's use a cool Visual Studio addon named "Microsoft Fakes" (the name itself means a lot...).

Fakes itself is tied to test features of Visual Studio, but it will prove the point. You can follow the standard tutorials to setup a project, and add a fakes assembly for System (in fact mscorlib + system)

This is your code in a library project (I've used exception everywhere because it's easier in testing condition...).

namespace ClassLibrary1 {     public class Class1     {         public static void MyCheck()         {             WindowsIdentity identity = Thread.CurrentPrincipal == null                 ? null                 : Thread.CurrentPrincipal.Identity as WindowsIdentity;              if (identity != null && identity.IsAuthenticated && !identity.IsAnonymous)             {                 var validated = new WindowsIdentity(identity.Token);                 if (!validated.User.Equals(identity.User) || !validated.IsAuthenticated || validated.IsAnonymous)                     throw new Exception("Something fishy is going on, don't trust it");                 else                     throw new Exception("Good! Use the validated one. name is:" + validated.Name);             }             else                 throw new Exception("not in");         }     } } 

This is the testing code in the test project:

namespace UnitTestProject1 {     [TestClass]     public class UnitTest1     {         [TestMethod]         public void TestMethod1()         {             using (ShimsContext.Create())             {                 System.Security.Principal.Fakes.ShimWindowsIdentity.AllInstances.NameGet = (i) =>                 {                     return "Simon the hacker";                 };                  WindowsIdentity wi = WindowsIdentity.GetCurrent(); // this is the real one "Simon".                 Thread.CurrentPrincipal = new WindowsPrincipal(wi);                  Class1.MyCheck();             }         }     } } 

This is the project layout in Visual Studio:

enter image description here

Also make sure you modify the mscorlib.fakes file that was automatically generated like this:

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true" TargetFrameworkVersion="v4.6">   <Assembly Name="mscorlib" />   <ShimGeneration>     <Clear />     <Add Namespace="System.Security.Principal" />   </ShimGeneration> </Fakes> 

It means I want the whole System.Security.Principal namespace to be shimed and I suggest you use framework 4.6 for both projects and add the corresponding TargetFrameworkVersion attribute.

Now, when you run the test, this is what you'll see:

enter image description here

Ok, in your specific scenario, I may not be able to use fakes, but the underlying technology it relies on just reroutes all APIs (it's lower than .NET in fact, it's called Detours) I believe and allows all these hackery.

To sum up: if it runs on my machine, I can hack it (unless I don't have physical access to my machine).

like image 170
Simon Mourier Avatar answered Oct 06 '22 02:10

Simon Mourier