Hugoware

The product of a web developer with a little too much caffeine

Posts Tagged ‘Security

A Personable Form Of Impersonation

leave a comment »

Have you tried impersonating a different user when executing code in your ASP.NET sites? Yeah, you can use a bunch of options in the web and machine config files but those might not give you enough control. Whats more you still have to contend with the way IIS handles credentials with the worker process then connecting user… er… or was that connecting user then… ah… never mind…

Executing a block of code with a specific set of credentials is a very handy way to have precise control over your application. Recently, I was working on copying files from an ASP.NET website to a remote UNC path. I needed to perform the transfer using different credentials for different servers… yeah, it was ugly but required…

Below is the code I finally ended up with…

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text.RegularExpressions;

namespace Samples {

    /// <summary>
    /// Allows you to execute code with an alternate set of credentials
    /// </summary>
    public class ImpersonationContext : IDisposable {

        #region Imported Methods

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool RevertToSelf();

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern int DuplicateToken(
            IntPtr token, 
            int impersonationLevel, 
            ref IntPtr newToken
            );

        [DllImport("advapi32.dll")]
        private static extern int LogonUserA(
            string username,
            string domain,
            string password,
            int logonType,
            int logonProvider,
            ref IntPtr token
            );

        #endregion

        #region Constants

        private const int INTERACTIVE_LOGON = 2;
        private const int DEFAULT_PROVIDER = 0;

        private const string REGEX_GROUP_USERNAME = "username";
        private const string REGEX_GROUP_DOMAIN = "domain";
        private const string REGEX_EXTRACT_USER_INFO =
            @"^(?<domain>[^\\]+)\\(?<username>.*)$|^(?<username>[^@]+)@(?<domain>.*)$";

        private const string EXCEPTION_COULD_NOT_IMPERSONATE = 
            "Could not impersonate user '{0}'.";
        private const string EXCEPTION_COULD_NOT_PARSE_USERNAME = 
            "Cannot determine username and domain from '{0}'";

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new Impersonation context
        /// </summary>
        public ImpersonationContext(string fullUsername, string password) {
            this.SetCredentials(fullUsername, password);
        }

        /// <summary>
        /// Creates a new Impersonation context
        /// </summary>
        public ImpersonationContext(string username, string domain, string password) {
            this.SetCredentials(username, domain, password);
        }

        #endregion

        #region Static Creation

        /// <summary>
        /// Executes a set of code using the credentials provided
        /// </summary>
        public static void Execute(string fullUsername, string password, Action action) {
            using (ImpersonationContext context = new ImpersonationContext(fullUsername, password)) {
                context.Execute(action);
            }
        }

        /// <summary>
        /// Executes a set of code using the credentials provided
        /// </summary>
        public static void Execute(string username, string domain, string password, Action action) {
            using (ImpersonationContext context = new ImpersonationContext(username, domain, password)) {
                context.Execute(action);
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// The username for this connection
        /// </summary>
        public string Username { get; private set; }

        /// <summary>
        /// The domain name for this user
        /// </summary>
        public string Domain { get; private set; }

        /// <summary>
        /// The identity of the executing account
        /// </summary>
        public WindowsIdentity Identity { get; private set; }

        //connection details
        private string _Password;
        private WindowsImpersonationContext _Context;

        #endregion

        #region Private Methods

        /// <summary>
        /// Begins to impersonate the provided credentials
        /// </summary>
        public bool BeginImpersonation() {

            //create the token containers
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;

            if (ImpersonationContext.RevertToSelf()) {

                //attempt the login
                int success = ImpersonationContext.LogonUserA(
                    this.Username,
                    this.Domain,
                    this._Password,
                    INTERACTIVE_LOGON,
                    DEFAULT_PROVIDER,
                    ref token
                    );

                //if this worked, perform the impersonation
                if (success != 0) {

                    int duplicate = ImpersonationContext.DuplicateToken(token, 2, ref tokenDuplicate);
                    if (duplicate != 0) {

                        //assign the identity to use
                        //this.Identity = new WindowsIdentity(tokenDuplicate);
                        this._Context = WindowsIdentity.Impersonate(tokenDuplicate);
                        if (this._Context != null) {
                            ImpersonationContext.CloseHandle(token);
                            ImpersonationContext.CloseHandle(tokenDuplicate);
                            return true;
                        }

                    }
                }
            }

            //close the tokens if required
            if (token != IntPtr.Zero) { ImpersonationContext.CloseHandle(token); }
            if (tokenDuplicate != IntPtr.Zero) { ImpersonationContext.CloseHandle(tokenDuplicate); }

            //return this failed
            return false;
        }

        /// <summary>
        /// Ends impersonating the current request
        /// </summary>
        public void EndImpersonation() {
            if (this._Context is WindowsImpersonationContext) { this._Context.Undo(); }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Accepts a full domain and assigns it for this connection
        /// </summary>
        public void SetCredentials(string fullUsername, string password) {

            //extract the user information
            Match user = Regex.Match(fullUsername, REGEX_EXTRACT_USER_INFO);
            if (!user.Success) {
                string message = string.Format(EXCEPTION_COULD_NOT_PARSE_USERNAME, fullUsername);
                throw new ArgumentException(message);
            }

            //extract the values
            string username = user.Groups[REGEX_GROUP_USERNAME].Value;
            string domain = user.Groups[REGEX_GROUP_DOMAIN].Value;

            //update the credentials
            this.SetCredentials(username, domain, password);

        }

        /// <summary>
        /// Changes the credentials for this connection to use
        /// </summary>
        public void SetCredentials(string username, string domain, string password) {
            this.Username = username;
            this.Domain = domain;
            this._Password = password;
        }

        /// <summary>
        /// Executes the action using the credentials provided
        /// </summary>
        public void Execute(Action action) {

            //perform the requested action
            if (this.BeginImpersonation()) {
                try {
                    action();
                }
                finally {
                    this.EndImpersonation();
                }
            }
            //since this couldn't login, give up
            else {
                string message = string.Format(EXCEPTION_COULD_NOT_IMPERSONATE, this.Username);
                throw new OperationCanceledException(message);
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs any cleanup work
        /// </summary>
        public void Dispose() {
            this.EndImpersonation();
            if (this._Context is WindowsImpersonationContext) { this._Context.Dispose(); }
        }

        #endregion

    }

}

Nothing like a lot of code to help fill in the blanks of a blog post, huh?

After reading a dozen blog post, tutorials and wikis, this is what I finally came up with – a nice simple way to execute a block of code without needing to worry about a lot of setup options. How about a few examples?

//who is running the code now
string who = WindowsIdentity.GetCurrent().Name;

//the credentials to use 
string username = "domain\\hugoware";
string password = "passw0rd";

//create a context inside of a using statement
//or simply create it and dispose it yourself
using(ImpersonationContext context = new ImpersonationContext(username, password)) {
    
    //execute using a anonymous method
    context.Execute(() => {
        who = WindowsIdentity.GetCurrent().Name;
    });

    //or manage it yourself
    context.BeginImpersonation();
    who = WindowsIdentity.GetCurrent().Name;
    context.EndImpersonation(); //optional if inside of a 'using'

}

//or use static methods
ImpersonationContext.Execute(username, password, () => {
    who = WindowsIdentity.GetCurrent().Name;
});

At this point I’m not exactly sure what this means for code that isn’t exactly straight forward, for example Threading or executing code when the credentials don’t have any access to the machine. I’ll follow up later as I find out more.

Written by hugoware

March 25, 2010 at 9:38 pm

Dude, For Real — Encrypt Your Web.Config

with 5 comments

After I released my web.config encryption utility I expected the world to transform into a Utopia of protected web.config files and happy developers. However, shortly after the tool was released I actually received some disagreement about the usefulness of web.config encryption.

Based on some other comments I received I got to thinking — I not sure that some people realize how possible it is to lose a web.config from a simple programming mistake.

But The Web.Config Is Safe — Right?

Sure, your web.config is safe by normal means. Just try it – find an ASP.NET website and just try to browse to their web.config file — See! it’s safe!!

True, your web.config is safe – but what about a programming mistake? Those never happen, do they? Are you sure?

One of my favorite examples is the file download. Sometimes we want to serve up content as if it is a download instead of showing it in the browser. That said, here is an ASP.NET MVC example of why you ought to go on ahead and encrypt that web.config file just to be on the safe side.

//MVC Action to download the correct file From our Content directory
public ActionResult GetFile(string name) {
    string path = this.Server.MapPath("~/Content/" + name);
    byte[] file = System.IO.File.ReadAllBytes(path);
    return this.File(file, "html/text");            
}

Seems reasonable enough – Other than error handling, do you see anything that looks out of place with this code? We map to the correct directory, we get the bytes for our file and return them to the visitor — You can even try it out.

/Home/GetFile?name=File.txt
bug-1

Cool, see how our file downloaded – Works great! But let’s be a little sneaky and play with the URL at the top. How about we do something like…

/Home/GetFile?name=../web.config

bug-2

Did you just get a sudden feeling of dread? Did you just shout ‘Oh Snap!’ loud enough that all your peers are staring at you? What do you suppose is in this file we just downloaded? I’ll give you three guesses, but I’m taking two of them away…

bug-3

It’s not hard to miss something — after all that’s why it’s a bug, because if we thought of it then it wouldn’t be there to begin with. Web.config encryption == cheap insurance.

Prying Eyes

I got this comment the other day and it was absolutely brilliant — Rob Thijssen wrote…

Encrypting configs in enterprise applications is definitely worth the time. Many companies allow contractors access to source code repositories that contain unencrypted configs that contain credentials which can be used to gain access to sensitive information. I have seen implementations where credentials were available to hundreds of developers that could give any one of them access to thousands of credit card details…

And he’s absolutely right. Do you want just anyone passing through the directory to have access to read the sensitive data inside your web.config? Just because they didn’t have hack into your server doesn’t mean they need to be reading the passwords to your SQL servers.

Dude, For Real — Just Do It

Web.config encryption only takes a couple moments and provides much more security than a clear-text file. It may not be enough to thwart a hacker that has full access to your entire server, but if you ever have that ‘uh oh — someone just downloaded my web.config’ moment, then at least you know you’re covered.

Written by hugoware

July 22, 2009 at 10:16 pm

Is Encrypting Your Web.config A Waste Of Time?

with 3 comments

In my last blog post I wrote about a utility has created to make it easier to encrypt web.config files hoping it would encourage more developers to protect their data. If you’ve ever tried doing it manually before, it isn’t really a very convenient process.

Additionally, I posted the blog entry on CodeProject — and received rather interesting response.

This article is pointless.
– Quote any high security site that uses an encrypted config file!
– If somebody has physical access to your config file/web server, you are as well doomed

Really? Is encrypting your web.config pointless. I don’t think so. In fact, I replied back with the following.

Encrypting a web.config file isn’t to defend against your server being compromised, but even if it were then I don’t think you understand exactly how aspnet_regiis works. The keys are written and locked down so that only members of the Administrators group can even read it, any lesser privileged accounts still can’t decrypt your web.config. Since typically website worker processes are running as NETWORK SERVICE, then unless you did something REALLY silly, your web.config should still be safe.

Even though that isn’t bullet-proof security, think about this scenario — You’ve have some junior developer right out of college working on a project that allows people to go out and download documents off your web server. He wants to send back the file as a download so he writes the bytes to the response stream and makes a change to the content-disposition and viola – freshly served documents all with a nice and neat little download dialog box.

But if your developer left a tiny little bug in the app and it was possible to download “../web.config” — What would you prefer to be served up? Encrypted or unencrypted?

In my opinion, an encrypted web.config file is 100% better than no encryption at all. Logging onto the server and running aspnet_regiis was very inconvenient way to get this done – this tool was made just try and help people get it done without needing to invest a lot of time into it.

But this really got me wondering, is this really a common opinion in the development community? Is encrypting your web.config really a waste of time? I don’t really think that encrypting your web.config file is the solution to all your problems – but it is some really cheap insurance that you can take out on sensitive file.

So what do you think? Is encrypting a web.config worth the time?

Written by hugoware

July 20, 2009 at 6:28 am

Encrypt Your Web.config, Please

with 29 comments

If you follow me on Twitter you may notice me talk about #BadVendor from time to time. Actually, they were recently upgraded to #EpicFailVendor when I discovered they weren’t cleaning strings before passing them into SQL queries. Needless to say, everyday has been a little more shocking than the next.

For the most part all of these systems are things I can’t make changes to — either it’s compiled code or I just don’t have the authority to go in and make the fixes, but there is something that I can do — encrypt their web.config files.

Making Encrypting Easier

Encrypting normally involves logging onto the server in question, locating a few mildly obscure pieces of information and then running aspnet_regiis. It’s not that hard but it isn’t point and click easy as well.

I wanted to make it easier to update these files without needing to locate all the information each time so I wrote a little application to make the whole process a bit easier. The utility uses credentials you supply to log into your servers via WMI and locate the required information and then encrypt your files without needing to pull up a command prompt.

I’m not really a WinForms guy and WMI is certainly not my specialty, but this program came together pretty quickly and seems to be fairly decent. It’s certainly not bug free and could use a round of refactoring to make it nicer, so any feedback is appreciated.

How It Works

The first step is to provide your credentials to the server you want to log into. If you choose to run the tool on the actual server itself then you can leave all those fields blank (since WMI won’t use them for local connections anyways). If you aren’t an admin for that server or at least and account with some elevated credentials then this may not work for you.

nkript.screen1

Once you successfully connect to the server, a list of the sites on the server will be loaded along with any virtual directories (since they could potentially contain a web.config file). At this point you can simply browse around and find the web.config you’re wanting to encrypt.

nkript.screen2

It’s worth noting that if there aren’t any web.config (that name specifically) found inside the directory then it won’t be listed. If you happened to have something named web.temp.config then it won’t show up on this list.

At this point the program is going to do a little painful WMI magic and connect out to your server and load the web.config file into the view. The config file will be parsed and all the root nodes will be listed as available to be encrypted.

nkript.screen3

There are apparently some rules about what can or cannot be encrypted, so if the actual aspnet_regiis call fails, you’ll just end up with the same file as before, but you don’t get an explicit message as to why (still trying to find out how I can access messages like that in a semi-reliable WMI fashion).

There isn’t much configuration for this application. The default settings are used to perform the encryption and decryption of the web.config files, so if you are wanting to add some features on you are more than welcome to add them in. I’d love to hear about your changes so I can add them to this version.

It’s not hard to encrypt your web.config files and keep your sensitive information safe. The command line tool aspnet_regiis offers a lot of great functions to further protect your data. Hopefully, this tool allows you to get your work done even faster.

Now if you’ll excuse me, I need to share this tool with #EpicFailVendor. I dunno about the rest of you you but enough is enough! I’ve had it with these monkey fighting vendors not encrypting their their Monday to Friday web.configs!

Mandatory Disclaimer: This program is certified as ‘Works On My Machine’ – The author makes no warranties about how it might behave in your environment (but most likely you have nothing to worry about).

Downloads

Download Nkrypt.exe (Web.config Encryption Tool)

Download Nkrypt.zip (Source Code)

.

After posting this article I got an interesting response from another person that web.config encryption is ‘pointless’ — I thought it was interesting enough to do a follow up blog post about it.

Written by hugoware

July 16, 2009 at 6:20 am