CHAP login
| From: | Martin Jansen | Date: | Sat, 01 Oct 2005 14:02:40 +0000 |
| Subject: | CHAP login | ||
| Groups: | php.pear.webmaster | ||
| Request: | Send a blank email to pear-webmaster+get-4456@lists.php.net to get a copy of this message | ||
As already mentioned in another thread, I've started working on making the
login to the website more secure by using a CHAP system. The concept is
rather simple:
The client sends the MD5 hash of the user's password with a challenge string
appended to it. The server can then recalculate and compare the
"challenged" password with the password from the database plus the challenge
string, all without the password being transmitted in clear text.
I've attached a patch for this. Comments appreciated.
In order to kick the CHAP login off, we'll need to update a handful of
accounts that are still using DES-style passwords. The owners of the
accounts are already informed, and I hope to get a response from them
shortly.
Once this is done, I'd also like to get rid of support for DES-passwords in
the code, because virtually nobody is using them.
- Martin
Index: public_html/login.php =================================================================== RCS file: /repository/pearweb/public_html/login.php,v retrieving revision 1.26 diff -u -r1.26 login.php --- public_html/login.php 1 Oct 2005 10:16:01 -0000 1.26 +++ public_html/login.php 1 Oct 2005 14:01:20 -0000 @@ -42,17 +42,20 @@ exit; } -if ($_SERVER['REQUEST_METHOD'] == "POST" && (empty($_POST['PEAR_USER']) || empty($_POST['PEAR_PW']))) { +if ($_SERVER['REQUEST_METHOD'] == "POST" && (empty($_POST['PEAR_USER']))) { auth_reject(PEAR_AUTH_REALM, 'You must provide a username and a password.'); } -if (!empty($_POST['isMD5'])) { - $password = @$_POST['PEAR_PW']; +if (!empty($_POST['doCHAP'])) { + // In this case the password is md5'ed together with a challenge string + $password = @$_POST['challenge']; + $chap = true; } else { $password = md5(@$_POST['PEAR_PW']); + $chap = false; } -if (auth_verify(@$_POST['PEAR_USER'], $password)) { +if (auth_verify(@$_POST['PEAR_USER'], $password, $chap)) { if (!empty($_POST['PEAR_PERSIST'])) { $expire = 2147483647; } else { Index: include/pear-auth.php =================================================================== RCS file: /repository/pearweb/include/pear-auth.php,v retrieving revision 1.54 diff -u -r1.54 pear-auth.php --- include/pear-auth.php 1 Oct 2005 10:15:54 -0000 1.54 +++ include/pear-auth.php 1 Oct 2005 14:01:21 -0000 @@ -34,19 +34,24 @@ Header("WWW-authenticate: basic realm=\"$realm\""); report_error($message); } elseif ($format == 'html') { + require_once "Text/Password.php"; + $GLOBALS['ONLOAD'] = "document.login.PEAR_USER.focus();"; + $_SESSION['challenge'] = Text_Password::create(32, "unpronounceable", "alphanumeric"); if ($message) { report_error($message); } print "<script type=\"text/javascript\" src=\"/javascript/md5.js\"></script>\n"; print "<script type=\"text/javascript\">\n"; print "function doMD5(frm) {\n"; - print " frm.PEAR_PW.value = hex_md5(frm.PEAR_PW.value);\n"; - print " frm.isMD5.value = 1;\n"; + print " frm.challenge.value = hex_md5(hex_md5(frm.PEAR_PW.value) + '" . $_SESSION['challenge'] . "');\n"; + print " frm.PEAR_PW.value = '';\n"; + print " frm.doCHAP.value = 1;\n"; print "}\n"; print "</script>\n"; print "<form onsubmit=\"javascript:doMD5(document.forms['login'])\" name=\"login\" action=\"/login.php\" method=\"post\">\n"; - print "<input type=\"hidden\" name=\"isMD5\" value=\"0\" />\n"; + print "<input type=\"hidden\" name=\"doCHAP\" value=\"0\" />\n"; + print "<input type=\"hidden\" name=\"challenge\" value=\"\" />\n"; print '<table class="form-holder" cellspacing="1">' . "\n"; print " <tr>\n"; print ' <th class="form-label_left">'; @@ -97,7 +102,7 @@ exit; } -function auth_verify($user, $passwd) +function auth_verify($user, &$passwd, $chap = false) { global $dbh, $auth_user; @@ -120,16 +125,17 @@ } // handle new-style MD5-encrypted passwords case 32: { - // Check if the passwd is already md5()ed - if (preg_match('/^[a-z0-9]{32}$/', $passwd)) { - $crypted = $passwd; - } else { - $crypted = md5($passwd); - } - - if ($crypted == @$auth_user->password) { + // The password is already md5()'ed + if ($chap) { + if ($passwd == md5($auth_user->password . $_SESSION['challenge'])) { + $ok = true; + $passwd = $auth_user->password; + } + } else if ($passwd == $auth_user->password) { $ok = true; - } else { + } + + if (!$ok) { $error = "pear-auth: user `$user': invalid password (md5)"; } break;
Index: public_html/login.php =================================================================== RCS file: /repository/pearweb/public_html/login.php,v retrieving revision 1.26 diff -u -r1.26 login.php --- public_html/login.php 1 Oct 2005 10:16:01 -0000 1.26 +++ public_html/login.php 1 Oct 2005 14:01:20 -0000 @@ -42,17 +42,20 @@ exit; } -if ($_SERVER['REQUEST_METHOD'] == "POST" && (empty($_POST['PEAR_USER']) || empty($_POST['PEAR_PW']))) { +if ($_SERVER['REQUEST_METHOD'] == "POST" && (empty($_POST['PEAR_USER']))) { auth_reject(PEAR_AUTH_REALM, 'You must provide a username and a password.'); } -if (!empty($_POST['isMD5'])) { - $password = @$_POST['PEAR_PW']; +if (!empty($_POST['doCHAP'])) { + // In this case the password is md5'ed together with a challenge string + $password = @$_POST['challenge']; + $chap = true; } else { $password = md5(@$_POST['PEAR_PW']); + $chap = false; } -if (auth_verify(@$_POST['PEAR_USER'], $password)) { +if (auth_verify(@$_POST['PEAR_USER'], $password, $chap)) { if (!empty($_POST['PEAR_PERSIST'])) { $expire = 2147483647; } else { Index: include/pear-auth.php =================================================================== RCS file: /repository/pearweb/include/pear-auth.php,v retrieving revision 1.54 diff -u -r1.54 pear-auth.php --- include/pear-auth.php 1 Oct 2005 10:15:54 -0000 1.54 +++ include/pear-auth.php 1 Oct 2005 14:01:21 -0000 @@ -34,19 +34,24 @@ Header("WWW-authenticate: basic realm=\"$realm\""); report_error($message); } elseif ($format == 'html') { + require_once "Text/Password.php"; + $GLOBALS['ONLOAD'] = "document.login.PEAR_USER.focus();"; + $_SESSION['challenge'] = Text_Password::create(32, "unpronounceable", "alphanumeric"); if ($message) { report_error($message); } print "<script type=\"text/javascript\" src=\"/javascript/md5.js\"></script>\n"; print "<script type=\"text/javascript\">\n"; print "function doMD5(frm) {\n"; - print " frm.PEAR_PW.value = hex_md5(frm.PEAR_PW.value);\n"; - print " frm.isMD5.value = 1;\n"; + print " frm.challenge.value = hex_md5(hex_md5(frm.PEAR_PW.value) + '" . $_SESSION['challenge'] . "');\n"; + print " frm.PEAR_PW.value = '';\n"; + print " frm.doCHAP.value = 1;\n"; print "}\n"; print "</script>\n"; print "<form onsubmit=\"javascript:doMD5(document.forms['login'])\" name=\"login\" action=\"/login.php\" method=\"post\">\n"; - print "<input type=\"hidden\" name=\"isMD5\" value=\"0\" />\n"; + print "<input type=\"hidden\" name=\"doCHAP\" value=\"0\" />\n"; + print "<input type=\"hidden\" name=\"challenge\" value=\"\" />\n"; print '<table class="form-holder" cellspacing="1">' . "\n"; print " <tr>\n"; print ' <th class="form-label_left">'; @@ -97,7 +102,7 @@ exit; } -function auth_verify($user, $passwd) +function auth_verify($user, &$passwd, $chap = false) { global $dbh, $auth_user; @@ -120,16 +125,17 @@ } // handle new-style MD5-encrypted passwords case 32: { - // Check if the passwd is already md5()ed - if (preg_match('/^[a-z0-9]{32}$/', $passwd)) { - $crypted = $passwd; - } else { - $crypted = md5($passwd); - } - - if ($crypted == @$auth_user->password) { + // The password is already md5()'ed + if ($chap) { + if ($passwd == md5($auth_user->password . $_SESSION['challenge'])) { + $ok = true; + $passwd = $auth_user->password; + } + } else if ($passwd == $auth_user->password) { $ok = true; - } else { + } + + if (!$ok) { $error = "pear-auth: user `$user': invalid password (md5)"; } break;