18

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...

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • In what context are you asking this? You're talking about both ASP.NET and Desktop apps. It may be obvious, but [when running on a "pwned" system, all bets are off - a malicious user can inject and modify in your process in whatever way they want](http://stackoverflow.com/questions/8357469/how-can-i-protect-my-private-funcs-against-reflection-executing). – CodeCaster Nov 06 '15 at 10:25
  • @CodeCaster: If Windows itself is properly compromised, that's out of my control. But if (for instance) an app can just create a `WindowsIdentity` instance, assign it to the current thread, and then call my assembly and trick me into believing they're someone they're not, that would be on me failing due diligence. Basically, I'm trying to achieve reasonable best practice SSO. I didn't question whether I could trust the APIs above until that user flagged it up on my other question (he may just be being paranoid), and then I got worried about what I don't know, which in this area is a lot. :-) – T.J. Crowder Nov 06 '15 at 10:29

1 Answers1

11

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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • @Damien_The_Unbeliever: The reflection one does it, in my non-domain environment and my proper AD environment. **Wow that's scary.** After setting that `m_user` field, not only does `.User` return the user we set, but `.Name` returns their domain username; so `WindowsIdentifier` seems to have been properly hacked, in a trivial way. Thank you for your time and help. – T.J. Crowder Nov 06 '15 at 16:14
  • @Damien_The_Unbeliever: **However**, `new WindowsIdentity(identityFromThread.Token)` **reveals the spoof** (and works correctly in my two non-malicious use cases, at least in initial testing). I think this answer is right for my actual question (questions can't be moving targets!), because I included code which you've successfully tricked. I am, of course, off to find out whether this, too, can be tricked... – T.J. Crowder Nov 06 '15 at 16:54
  • ...and so we have [this question](http://stackoverflow.com/questions/33573773/is-it-possible-to-trick-this-windowsidentity-code-into-using-the-wrong-user). :-) – T.J. Crowder Nov 06 '15 at 18:58
  • (BTW, I hope you didn't mind the edit, but of course if you did, there's that Rollback link. :-) ) – T.J. Crowder Nov 06 '15 at 19:20
  • @T.J.Crowder - this is just what I can do *within* the bounds of .net code. Of course, in full trust, I can invoke unsafe code. In which case I'm sure it's possible to subvert your next scheme. As I've said, code within full trust can lie. It's *never* been an objective (nor, I believe, possible) that code can protect itself from malicious callers. – Damien_The_Unbeliever Nov 06 '15 at 22:43
  • Of course it's been an objective. Every time you call a .Net assembly that deals with anything secure, it's an objective of the person writing that assembly to avoid being lied to. You did a *fantastic* job showing how my previous code could be lied to. I'm not at all sure, though, that inventing user tokens out of thin air is as readily feasible. :-) – T.J. Crowder Nov 06 '15 at 22:54
  • @T.J.Crowder - no, it's *not* been an objective, if, as it seems here, the scenario is that there's actively hostile code that you've invited inside the same process. There's sandboxing to help deal with that, but for those sort of scenarios, it's the *host* that can attempt to protect itself from the code its hosting. You've set yourself up with an impossible scenario where you're writing the code that's going to be hosted and trying to protect yourself from a malicious host. – Damien_The_Unbeliever Nov 07 '15 at 07:17
  • @ Damien_The_Unbeliever: How is that different from, say, .Net's System.dll? (For clarity: This is a genuine question, I'm still really unsure of my ground in this whole area of .Net) – T.J. Crowder Nov 07 '15 at 07:25
  • @T.J.Crowder - my process, my rules. Who says that you're talking to the System.dll that Microsoft (or Xamarin) supplies? I'm not going to, for the sake of a question, but I know I could implement a custom host that ignores strong naming. – Damien_The_Unbeliever Nov 07 '15 at 07:28
  • @ Damien_The_Unbeliever: I meant, how is what I'm trying to do in my DLL different from what Microsoft does in System.dll? I understand and agree that with enough effort, one could create a process that could trick just about anything. My goal is to make that hard, to do my due diligence around the identity I've been asked to use. (BTW, I'm not planning to auto-discover it anymore; I'm just going to accept a `WindowsIdentity` instance. But I'm not going to blindly trust it, because of hacks like the one you demonstrated.) – T.J. Crowder Nov 07 '15 at 07:32