← Back to context

Comment by pyman

3 months ago

This is a great post, it captures the true essence of an engineer. It is funny, intriguing, and inspirational. Congrats! You are a hacker at heart.

When I went to the US for 3 months I joined PureGym and they gave me a PIN number. I cancelled my membership after that, and one day Chrome told me my PureGym PIN had been compromised. 2 years later, I went to the US again, rejoined, and received the same PIN. Massive red flag.

I was also intrigued by the app, the token and PIN, and remember finding a security flaw in the system that activates the hydro massage chairs. It accepts your PIN or any PIN, with no security at all.

> Chrome told me my PureGym PIN had been compromised

This is likely a false positive, if chrome is using haveibeenpwned API.

e.g. A pin of 87623103

Hashes to 558B4C37F6E3FF9A5E1115C66CEF0703E3F2ADEE

We get the range from HaveIBeenPwned:

https://api.pwnedpasswords.com/range/558B4

And search for C37F6E3FF9A5E1115C66CEF0703E3F2ADEE

And see it's "Compromised" and seen 3 times before.

  • In case anyone else was wondering, not all 8 digit pins are "compromised", although many are, and of course an 8 digit pin has limited security in any automatable scenario.

    To get an example that was already in the haveibeenpwned dataset, I wrote a quick script:

      var httpClient = new System.Net.Http.HttpClient();
      httpClient.BaseAddress = new Uri("https://api.pwnedpasswords.com/");
    
      while (true)
      {
       var password = string.Join("", Enumerable.Range(0, 8).Select(e => Random.Shared.Next(0, 10)));
    
       var hash = Convert.ToHexString(System.Security.Cryptography.SHA1.HashData(Encoding.UTF8.GetBytes(password)));
    
       var passwordRange = await httpClient.GetAsync($"range/{hash.Substring(0, 5)}");
    
       passwordRange.EnsureSuccessStatusCode();
    
       var allhashes = await passwordRange.Content.ReadAsStringAsync();
    
       var splitHashes = allhashes.Split(Environment.NewLine);
       
       var compromised = splitHashes.SingleOrDefault(h => h.StartsWith(hash.Substring(5)));
       
       if (compromised != null)
       {
        Console.WriteLine($"Password {password} Compromised! Found {compromised.Split(':')[1]} time(s)");
        Console.WriteLine($"Hash: {hash}");
        return;
       }
       await System.Threading.Tasks.Task.Delay(1_000);
      }
    
    

    The "most compromised" I've seen so far is "17385382", in the DB an astonishing 119 times. It would only take a few hours to iterate through all pins and collect stats for all pins.

  • I had this constantly the last couple of days. I've been doing some UI mockups in Claude and it includes a password field, and either it puts in a placeholder of like 1234 or I type asdf to test the field. Then as soon as I do anything else Chrome has a fit because "my" password has (obviously) been "pwned."

I've received the same PIN from an entirely different gym chain, albeit one using the same door system.

As you say, a massive red flag indicating it's not using a lot of sources of entropy.

  • What worries me the most is that if the ACS can't issue new PINs, there's no way to replace them. If a single PIN is shared or compromised, anyone with it can walk in undetected until the whole system is replaced. And if the entire PIN list is exposed, all hell breaks loose.