Bad Day So for the last few days I’ve been banging my head up against the same problem in the lab. We currently use Chillispot to run our access points for the Metakall user trials, which is grand, but it’s also fairly out of date – it hasn’t been actively developed on since 1997 – and it has some serious security issues. So with the new academic year looming, we’re moving over to its successor, Coova-Chilli.

Coova has a few neat features we like, and it’s also being actively maintained, which is important. So I took our testbed server, installed FreeRADIUS and MySQL and Apache and all the custom stuff we’ve written and generally got things set up, and then went to install the latest version of coova-chilli (1.0.14). There was an hour of swearing at iptables as usual (I really am coming to hate that program), and then I thought that’d be that.

But of course not. The Metakall client wouldn’t log in. At first I thought the iptables setup was wrong, but no, all’s well there. FreeRADIUS was fine, so it had to be something in Coova-Chilli. Ah-ha, says I, it’s that new-fangled JSON captive portal setup, that’s the problem, I’ll dig into that. A day and a half later, and I’ve gone through the JSON portal stuff and can’t find anything wrong.

Exasperated, I switch Coova back to the old Chillispot configuration files (which are compatible), bypassing all the new JSON stuff and still there’s a problem.

It’s at this point that I try to log in manually, which no-one’s done on the system since we got the client working. And I notice that usernames are passed perfectly to FreeRADIUS. And so are passwords… up until you feed in more than 15 characters to the password field. Ah-ha. Check the HTML forms… but no, the password field size is fine. I’m half-relieved at that, that I didn’t miss it while digging through the JSON stuff.

Okay, now we’re ticked off. As we’ve got the tarball extracted already, dive into src/ and start digging. Where does it read in the password field? Eventually found it, in src/redir.c and found it calls a function called hextochar2() on the query’s data in case the password stuff is encoded in hex in the URL. And that function can only cope with 15 characters at a time and acts wierd if more than 15 characters show up. Since the client always provides a password longer than that, FreeRADIUS was getting garbage all the time in the password field and it wasn’t down to the captive portal using CHAP or MS-CHAP like I’d thought was causing the hassle. It was a bug in Coova-Chilli itself.

*sigh*

Okay, google time. And coova.org has a few items in the wiki… but when you click on the links, they tell you that the site’s been moved to coova.org and you’re bounced to the homepage. There’s no google cache of the page and the wayback machine doesn’t help either. Annoying.

So sign up to the development list, ask a question about this and get a response in three minutes that it’s been fixed in SVN. Some days you wouldn’t know whether to curse or cry or both…

So SVN checkout and rebuild from scratch (neatly avoiding the whoopsie where I accidentally overwrite the config files with make install and lose another hour redoing old work) and try again, and yes, now 16 character passwords are handled properly!

…but 17-character passwords are truncated to 16 characters, as are longer passwords.

Some days, you do know whether to curse or cry…

Okay, back into the code and check redir_hextochar() again. Seems okay… Hmmm. Over there in redir_radius() we have this:

for (m=0; m < RADIUS_PWSIZE;)
  for (n=0; n < REDIR_MD5LEN; m++, n++)
    user_password[m] = conn->password[m] ^ chap_challenge[n];

It’s a neat use of variable scope to repeat its way through the chap_challange[] array if conn->password[] is longer than chap_challange[]. The problem though, is that that has to be duplicated on the webpage sending the password across, and when you check that (in this case the hotspotlogin.cgi legacy perl script), you find this:

  $hexchal  = pack "H32", $challenge;

...

  } elsif (defined($userpassword)) {
  # Encode plain text password with challenge
  # (which may or may not be uamsecret encoded)

  $pappassword = unpack "H32", ($password ^ $newchal);

  $logonUrl = "http://$uamip:$uamport/logon?username=$username&password=$pappassword";

There are two bugs here: firstly that calling pack() and unpack() with the template “H32″ means that no matter how long the list you pass in (in this case $challange and ($password ^ $newchal) ), the output is going to be 16 bytes every time, the rest is just discarded.

Changing the template to “H*” cures that, but exposes the second bug which is that if $password is longer than $newchal, the bitwise string XOR operator in perl just pads out $newchal with imaginary zeros to get the job done. So the tail of your output is the plaintext of the tail of your input – and when that gets over to redir_radius(), which does the job properly, it tries to XOR the result with the challange and gets back only the head of $password – the tail is mangled.

So we just duplicate the algorithm from redir.c and repeat $newchal until it’s long enough that that doesn’t happen, like so:

  $hexchal  = pack "H*", $challenge;

...

  } elsif (defined($userpassword)) {
  # Encode plain text password with challenge
  # (which may or may not be uamsecret encoded)
  while (length($newchal) < length($password)){
   $newchal .= $newchal;
  }
 $pappassword = unpack "H*", ($password ^ $newchal);

And that’s it. Tested, everything works, solution emailed to the coova development list and patch submitted.We’ll run on the patched svn version until 1.0.15 gets released.

Mind you, I’d feel a lot better if I’d found this last Thursday…

Here’s the patch.diff file for anyone interested, svn checkout revision 217 of Coova-Chilli and apply it:

Index: doc/hotspotlogin.cgi
===================================================================
--- doc/hotspotlogin.cgi        (revision 217)
+++ doc/hotspotlogin.cgi        (working copy)
@@ -150,7 +150,7 @@

 print "Content-type: text/html\n\n";

-    $hexchal  = pack "H32", $challenge;
+    $hexchal  = pack "H*", $challenge;

 if (defined $uamsecret) {
 $newchal  = md5($hexchal, $uamsecret);
@@ -170,8 +170,13 @@
 # Encode plain text password with challenge
 # (which may or may not be uamsecret encoded)

-       $pappassword = unpack "H32", ($password ^ $newchal);
+       # If challange isn't long enough, repeat it until it is
+       while (length($newchal) < length($password)){
+               $newchal .= $newchal;
+        }

+       $pappassword = unpack "H*", ($password ^ $newchal);
+
 $logonUrl = "http://$uamip:$uamport/logon?username=$username&password=$pappassword";

 } else {