Hacking (Old) Hacker News: Fun with Weak Passwords and Arc

When I was conducting my cloud password security research, I also looked at Hacker News. It's not a cloud application, but it does have pretty common password security qualities. It's also interesting because it's written in Arc (a Lisp dialect) and the code is available (for the old version from 2009).

When you create a Hacker News account you can create passwords which are 4 characters long without any restrictions on the password complexity. This mean that you can have a password that looks like 0000 or 1111. Sure, not everybody will use passwords like that, but there's a good chance that quite a few users will have pretty simple passwords. Even technical people are still people; people choose the easiest possible passwords (when they can), making it easy to conduct online password attacks.

What's interesting is that when you change your password, you are required to have at least 8 characters (still without any complexity requirements). The Arc source code shows that the length requirement used to be 4 characters. Time to look at the code to see what else might be there...

Here's the login code from app.arc:

(def good-login (user pw ip)
  (let record (list (seconds) ip user)
    (if (and user pw (aand (shash pw) (is it (hpasswords* user))))
        (do (unless (user->cookie* user) (cook-user user))
            (enq-limit record good-logins*)
            user)
        (do (enq-limit record bad-logins*)
            nil))))

The interesting part is (aand (shash pw) (is it (hpasswords* user))), where the password parameter is hashed and then compared to the expected hash for the user. What makes it interesting is the is operator that determines if two values in Arc are equal. Let's find out what is really means...

is is defined in ac.scm as:

(xdef is (lambda args (pairwise ar-is2 args)))

ar-is2 is the function that implements the interesting parts we care about:

(define (ar-is2 a b)
  (tnil (or (eqv? a b)
            (and (string? a) (string? b) (string=? a b))
            (and (ar-false? a) (ar-false? b)))))

The important part here is this bit of Scheme code (string=? a b) that checks if two strings are equal.

Here's how MzScheme defines its string compare operator "string=?":

GEN_STRING_COMP(string_eq, "string=?", mz_char_strcmp, ==, 0, 1)
...
static int mz_char_strcmp(const char *who, const mzchar *str1, int l1, const mzchar *str2, int l2, 
        int use_locale, int size_shortcut)
{
  int endres;

  if (size_shortcut && (l1 != l2))
    return 1;
  //... (cut unused locale compare)
  if (l1 > l2) {
    l1 = l2;
    endres = 1;
  } else {
    if (l2 > l1)
      endres = -1;
    else
      endres = 0;
  }

  while (l1--) {
    unsigned int a, b;

    a = *(str1++);
    b = *(str2++);

    a = a - b;
    if (a)
      return a;
  }

  return endres;
}

This is a standard string compare implementation that returns as soon as the current characters don't match. This means that the password checking code is vulnerable to timing attacks (in theory). It's harder to exploit because the code doesn't compare raw/cleartext passwords. Instead, the code compares password hashes, so there's an extra step where you have to generate hash collisions. Depending on the hash algorithm generating hash collisions could be time-consuming.

What are those hashes and what algorithm is used to generate them? The answer is in the shash function in app.arc:

(def shash (str)
  (let fname (+ "/tmp/shash" (rand-string 10))
    (w/outfile f fname (disp str f))
    (let res (tostring (system (+ "openssl dgst -sha1 <" fname)))
      (do1 (cut res 0 (- (len res) 1))
           (rmfile fname)))))

shash creates a SHA1 hash (without a "salt") and that hash is used to verify the user identity. Compared to MD5 generating SHA1 hash collisions is much more difficult (and not very practical at this point in time).

The publicly available code is from 2009 and the current password management and processing code has been improved since then (Paul Graham was nice enough to confirm it :-)). It sounds like the current unreleased version might be using a pure Scheme implementation of bcrypt. It would be great to see the new version, given that there aren't a lot of pure Scheme bcrypt implementations out there. In the meantime, you can explore your inner hacker and deploy the old code yourself.

To run Arc 3.0:

To run Arc 3.1:

Hacker News is not alone when it comes to having no password complexity requirements. Quite a few companies have the same password qualities (see "The Sad State of Password Security in the Cloud" to learn more about it). Unlike Hacker News, where you don't gain much by compromising users (unless they reuse the password in other, more valuable internet sites or cloud apps), other companies do have valuable information that should be protected a little better.