Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this use of System.Security.Principal.WindowsIdentity reasonably secure?

Tags:

c#

.net

security

Is System.Security.Principal.WindowsIdentity reasonably secure from being hacked such that an instance I get from Thread.CurrentPrincipal's Identity or WindowsIdentity.GetCurrent() which has true for IsAuthenticated gives my assembly false identity information? Nothing, of course, is completely tamper-proof, but given Microsoft's commitment to and reliance on .Net, I would expect critical APIs like this to be Locked Down Hard and difficult to tamper with. Is that a valid assumption on my part?

My goal here is to provide reasonable best-practices SSO in my assembly. If Windows itself is compromised, that's out of my control, but if (for instance) it's a straightforward matter for an app linking with my assembly to feed me false information, that would be on me for failing to do due diligence. This is a big area of ignorance for me.

To be clear, I'm looking for hard information, not off-the cuff opinions. So, published exploits, or demonstrated use of a WindowsIdentity constructor in a way that would trick my code, etc. Or on the "that's a valid assumption" side, solid articles backing it up, known uses relying on it, etc. I haven't had a lot of luck finding them, but I've included what I've found so far below under the divider.

Here's how I intend to use WindowsIdentity:

using System.Security.Principal;
using System.Threading;
// ...

// I only want Windows-authenticated users
WindowsIdentity identity = Thread.CurrentPrincipal == null
    ? null
    : Thread.CurrentPrincipal.Identity as WindowsIdentity;
SecurityIdentifier sid;

// I can't imagine how an authenticated account would be anonymous, but...
if (identity != null && identity.IsAuthenticated && !identity.IsAnonymous) {
    // SSO success from thread identity
    sid = identity.User;
    // ...check that that SID is allowed to use our system...
} else {
    identity = WindowsIdentity.GetCurrent();
    if (identity != null && identity.IsAuthenticated && !identity.IsAnonymous) {
        // SSO success from current Windows user
        sid = identity.User;
        // ...check that that SID is allowed to use our system...
    } else {
        // SSO fail
    }
}

This is in a DLL assembly — sadly we're stuck on .Net 3.5 — that provides a public API to resources that may be restricted by user rights. It might be used in desktop apps, or in an ASP.Net IIS app with Windows authentication (ASP.Net sets a WindowsIdentity instance on Thread.CurrentPrincipal.Identity when using Windows auth; we don't support other kinds of IIS auth presently).

Can I reasonably trust a SID from a WindowsIdentity instance from those sources claiming to be authenticated like that?

It didn't occur to me to wonder if that was okay (doh!) until in this question user lc. raised a concern that the assembly would be susceptible to being tricked by a malicious app that linked with it and "faked" that information. He didn't have any specific evidence to point to for why that might be a significant concern, though, hence this question.


What (little) I've found so far:

  • This answer makes the claim

    You can trust that the current WindowsIdentity is who it says it is, insofar as you can trust any given piece of data in your application.

  • The book Hacking the Code claims that ASP.Net requires a WindowsIdentity to be associated with a request when doing file authorizaton checking, which if true seems like a fairly solid basis for saying Microsoft, at least, considers it good enough.

  • I can find plenty of examples of people happily using the WindowsIdentity information in their code, but most of them don't ask the question of whether they're secure. There's an implication, but...

like image 792
T.J. Crowder Avatar asked Nov 06 '15 09:11

T.J. Crowder


1 Answers

You can't trust the one from Thread.CurrentPrincipal, no. There's nothing to stop code running in full trust from spoofing it.

I was able to spoof it in my environment like this:

var admin = new WindowsIdentity(@"Administrator");
var princ = new WindowsPrincipal(admin);
System.Threading.Thread.CurrentPrincipal = princ;

...before invoking your code. On my machine, the created WindowsIdentity object has IsAuthenticated as true and IsAnonymous false, and so, of course, your code extracts my domain administrator's SID.

That doesn't work in all environments, but this should, provided that the running code has enough permissions to use reflection:

var ident = WindowsIdentity.GetCurrent();
Thread.CurrentPrincipal = new WindowsPrincipal(ident);
var userSid = ident.User;

var fakeSid = new SecurityIdentifier("S-1-3-0");

typeof (WindowsIdentity).GetField("m_user",
  BindingFlags.Instance | BindingFlags.NonPublic).SetValue(ident, fakeSid);

(Again, done before calling your code.)


Basically, there's nothing to stop two pieces of code running under Full Trust within the same process from lying to each other.

like image 77
Damien_The_Unbeliever Avatar answered Oct 01 '22 17:10

Damien_The_Unbeliever