.NET Membership audit preparation steps

Not so long ago one of our clients asked us to audit their custom .net CMS users password security. It’s not a secret that 99% of cases when we review .net user accounts and security we would see default SqlMembershipProvider implementation. It has the potential to be extremely secure. Opposite however is also true – you can make it a disaster allowing symmetric encryption, for example. Our case was pretty much default settings across the board, so our passwords were SHA1 hashed with random salts. Good enough, really.
Following up on Troy Hunt’s post we have got ourselves a fresh copy of Hashcat and a few dictionaries to play with.

This is where it all went wrong

To begin with, although EpiServer is running on .net platform, it seems to use slightly relaxed version of membership provider: their salts seem to be shorter than the default. Hashcat however seems to make a great fuss out of this difference:
[cc lang=”bash”]Z:\hashcat>hashcat64.exe -m 141 z:\hashes.txt dictionary.txt
hashcat (v4.2.1) starting…

Hashfile ‘z:\hashes.txt’ on line 1 ($epise…/JapfY=*j+gvb/c0mt28CHJssmwFvQ==): Token length exception
No hashes loaded.

Started: Thu Sep 01 07:48:39 2018
Stopped: Thu Sep 01 07:48:39 2018[/cc] So apparently Troy’s Episerver-specific routine does not work for any other ASP membership hashes. This seems to be a show stopper.

This is still .net membership though

We know where exactly Sql Memebership Provider is located, we can decompile it and have a peek at how exactly it works. I quoted the relevant function with some comments below:
[cc lang=”c#”]namespace System.Web.Security
{
public class SqlMembershipProvider : MembershipProvider
{
private string EncodePassword(string pass, int passwordFormat, string salt)
{
if (passwordFormat == 0) return pass; // this is plain text passwords. keep away from it
byte[] bytes = Encoding.Unicode.GetBytes(pass); // this is very important, we’ll get back to it later
byte[] numArray1 = Convert.FromBase64String(salt);
byte[] inArray;
if (passwordFormat == 1) // this is where the interesting stuff happens
{
HashAlgorithm hashAlgorithm = this.GetHashAlgorithm();//by default we’d get SHA1 here
if (hashAlgorithm is KeyedHashAlgorithm)
{
// removed this block for brevity as it makes no sense in our case as SHA1 does not require a key.
}
else
{
/* and so we end up with this little block of code that does all the magic.
it’s pretty easy to follow: concatenates salt+password (order matters!) and runs it through SHA1 */

byte[] buffer = new byte[numArray1.Length + bytes.Length];
Buffer.BlockCopy((Array)numArray1, 0, (Array)buffer, 0, numArray1.Length);
Buffer.BlockCopy((Array)bytes, 0, (Array)buffer, numArray1.Length, bytes.Length);
inArray = hashAlgorithm.ComputeHash(buffer);
}
}
else
{
// symmetric encryption. again, keep away
}
return Convert.ToBase64String(inArray);
}
}
}[/cc] Hashing implementation is not secret either, we can look through Hashcat’s extensive list of supported hashing modes and see if anything pops out.
We’re looking for something that does SHA1 over concatenated salt and password. At first glance we’ve got mode 120 | sha1($salt.$pass), but there’s a catch.
As I noted in the code comment, Encoding.Unicode actually represents UTF16, not UTF8 most of us are used to.
But that’s fine, because Hashcat has just the method for us: 140 | sha1($salt.utf16le($pass)). Fantastic!

A bit of housekeeping

The easiest way to prepare a hashlist in our case was to run a simple LINQPad script against membership database and save results in a file. We just need to keep in mind that salts are represented as hex-encoded strings, so we’ll need to let Hashcat know.
[cc lang=”csharp”]static class StringExtensions {
public static string ToHex(this string input) {
byte[] bytes = Convert.FromBase64String(input);
var result = BitConverter.ToString(bytes).Replace(“-“, “”).ToLower();
return result;
}
}
///

/// to make this work you’d need to set up a database connection in your LINQ Pad first
/// we assume Memberships and Users tables exist in the database we’re connected to. Tweak to your environment
///

void Main()
{
var f = File.CreateText(@”z:\hashes.txt”);
/// Memberships – main ASP.NET Membership table. can be called differently, so watch out for your use case
/// Users – table mapping memberships to user names. Again, can be called differently
foreach (var user in Memberships.Join(Users, m => m.UserId, u => u.UserId, (mm, uu) => new { uu.UserName, mm.Email, mm.Password, mm.PasswordSalt, uu.UserId }).OrderBy(x => x.UserName))
{
//choose either of these lines, to add users or not. We found *hash:salt* format to be less painful to work with in Hashcat
//f.WriteLine($”{user.UserName}:{user.Password.ToHex()}:{user.PasswordSalt.ToHex()}”);
f.WriteLine($”{user.Password.ToHex()}:{user.PasswordSalt.ToHex()}”);
}
f.Close();
}[/cc]

One Reply to “.NET Membership audit preparation steps”

Comments are closed.