Our Blog

Pass-the-hash WiFi

Reading time ~5 min

Thanks to a tweet Dominic responded to, I saw someone mention Passing-the-hash when I think they actually meant relay. The terminology can be confusing for sure, however, it made me realise that I had never Passed-the-hash with a Wi-Fi network.

So having learnt my lesson from previous projects I first made sure this was possible for NT -> MSCHAP by looking at the RFC.

8.1.  GenerateNTResponse()

   GenerateNTResponse(
   IN  16-octet              AuthenticatorChallenge,
   IN  16-octet              PeerChallenge,
   IN  0-to-256-char         UserName,

   IN  0-to-256-unicode-char Password,
   OUT 24-octet              Response )
   {
      8-octet  Challenge
      16-octet PasswordHash

      ChallengeHash( PeerChallenge, AuthenticatorChallenge, UserName,
                     giving Challenge)

      NtPasswordHash( Password, giving PasswordHash )
      ChallengeResponse( Challenge, PasswordHash, giving Response )
   }

Looks like you can! As you can see in the above, the ChallengeResponse is created using the NT hash and not the password. I then checked wpa_supplicant to see if this was not a feature already, and it turns out it is! Looking at the wpa_supplicant configuration file it says:

password: Password string for EAP. This field can include either the plaintext password (using ASCII or hex string) or a NtPasswordHash (16-byte MD4 hash of password) in hash:<32 hex digits> format. NtPasswordHash can only be used when the password is for MSCHAPv2 or MSCHAP (EAP-MSCHAPv2, EAP-TTLS/MSCHAPv2, EAP-TTLS/MSCHAP, LEAP). EAP-PSK (128-bit PSK), EAP-PAX (128-bit PSK), and EAP-SAKE (256-bit PSK) is also configured using this field. For EAP-GPSK, this is a variable length PSK. ext: format can be used to indicate that the password is stored in external storage.

So to Pass-the-hash as a client when you use the password field in your wpa_supplicant.conf, just add a hash: in front and you can use that to authenticate.

network={
        ssid="example"
        scan_ssid=1
        key_mgmt=WPA-EAP
        eap=PEAP
        identity="harold"
        password="hash:e19ccf75ee54e06b06a5907af13cef42"
        ca_cert="/etc/cert/ca.pem"
        phase1="peaplabel=0"
        phase2="auth=MSCHAPV2"
}

This becomes useful when the machine account authenticates to the Wi-Fi rather than the user. This gives you the option of using the machine hash which would typically not be crackable.

Now if you have compromised some hashes and they are using PEAP for their Wi-Fi you can connect easy peasy.

I am Corporate HotSpot

I also wondered if we could do the reverse. Lets say we have dumped the domains passwords and would like to trick people into connecting to our Wi-Fi so that we can provide them with free internet?

Spock’s Evil Twin Passing-the-hash

As a quick reminder why this is an interesting vector, the reason we would want to do this is due to the mutual authentication requirement for MSCHAP. While your device is authenticating against an AP, it also checks the response from the AP to ensure it knows the password as well. So if you are unable to prove you know the password, users will not connect unless their device is specifically ignoring the verification (as was the case for CVE-2019-6203).

Anyways, turns out you can do it from the other side as well as the AP only needs the NT hash as can be seen in the below pseudo code from the RFC:

8.7.  GenerateAuthenticatorResponse()

   GenerateAuthenticatorResponse(
   IN  0-to-256-unicode-char Password,
   IN  24-octet              NT-Response,
   IN  16-octet              PeerChallenge,
   IN  16-octet              AuthenticatorChallenge,
   IN  0-to-256-char         UserName,
   OUT 42-octet              AuthenticatorResponse )
   {
      16-octet              PasswordHash
      16-octet              PasswordHashHash
      8-octet               Challenge

      /*
       * "Magic" constants used in response generation
       */

      Magic1[39] =
         {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
          0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
          0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
          0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};

      Magic2[41] =
         {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
          0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
          0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
          0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
          0x6E};

      /*
       * Hash the password with MD4
       */

      NtPasswordHash( Password, giving PasswordHash )

      /*
       * Now hash the hash
       */

      HashNtPasswordHash( PasswordHash, giving PasswordHashHash)

      SHAInit(Context)
      SHAUpdate(Context, PasswordHashHash, 16)
      SHAUpdate(Context, NTResponse, 24)
      SHAUpdate(Context, Magic1, 39)
      SHAFinal(Context, Digest)

      ChallengeHash( PeerChallenge, AuthenticatorChallenge, UserName,
                     giving Challenge)

      SHAInit(Context)
      SHAUpdate(Context, Digest, 20)
      SHAUpdate(Context, Challenge, 8)
      SHAUpdate(Context, Magic2, 41)
      SHAFinal(Context, Digest)

      /*
       * Encode the value of 'Digest' as "S=" followed by
       * 40 ASCII hexadecimal digits and return it in
       * AuthenticatorResponse.
       * For example,
       *   "S=0123456789ABCDEF0123456789ABCDEF01234567"
       */

   }

Once again we just skip the part where we convert the password to an NT hash and just use the NT hash in the response generation. Hostapd supports this and the format looks like below:

# Phase 2 (tunnelled within EAP-PEAP or EAP-TTLS) users
"test user" MSCHAPV2 hash:000102030405060708090a0b0c0d0e0f [2]

Now you have a hotspot that all domain users can connect to, and you may be able to trick user devices into fully connecting so you can give them all the Internet.