Ah, routers. The one piece of networking hardware that seems inescapable; even the most tech-illiterate among us needs to interface with their router at least once, to set up their home network so they can access the internet. Router technology has changed a lot over the years, including how you interface with the admin portal: instead of having to navigate to a specific IP address, some of them have you navigate to a URL that is intercepted by the router and redirected to the admin interface, making it easier for laymen to recall. But routers have their share of odd problems. I recently had to buy a new one because the one I was using was incompatible with my company's VPN for cryptic reasons even helpdesk had no real understanding of.
Today's submission concerns a Vodafone router. Our submitter was setting up a network for a friend, and to make things easier, they set up a low-security password initially so they could type it repeatedly without worrying about messing it up. Once the network was set up, however, they wanted to change it to something long and cryptic to prevent against brute-force attacks. They generated a password like 6Y9^}Ky.SK50ZR84.p,5u$380(G;m;bI%NZG%zHd?lOStqRzS}Z?t;8qSg;[gy@
and plugged it in, only to be told it was a "weak" password.
What? Length and variance both seem quite sufficient for the task—after all, it's not like there's roving bands of street gangs hacking into everyone's wifi routers and mucking about with the settings. There's no need for 300-character passwords.
Curious, our submitter opened the Javascript source for the change password page to see what checks they failed, since the UI wasn't helping much:
function CheckPasswordStrength(password) {
var password_strength = document.getElementById("password_strength");
var flag = 0;
var len = password.length;
var arr = [];
var sum = 0;
var cnt = 0;
//TextBox left blank.
if (password.length == 0) {
password_strength.innerHTML = "";
}
var passwordComplexity = $.validator.methods.passwordAtleast.call();
var passwordLength = $.validator.methods.passwordLength.call(); //rule a
var passwordAuhtorizedCharacter = $.validator.methods.PasswordAuhtorizedCharacter.call();
for (var i=0; i < len; i++) {
if ((password.split(password[i]).length-1) > 1)
cnt++;
if (i == 0)
arr.push({pasChar:password[0],val:4});
else if (i < 8)
arr.push({pasChar:password[i],val:2});
else if (i < 21)
arr.push({pasChar:password[i],val:1.5});
else if (i > 20)
arr.push({pasChar:password[i],val:1});
}
if (passwordComplexity == true && len > 8) {
arr.push({pasChar:password[i],val:6});
}
for (var i=0; i<arr.length; i++)
sum = sum + arr[i].val;
if ((sum < 18) || (passwordAuhtorizedCharacter == false) || (cnt >= (len/2)))
flag = 1;
else if ((sum >= 18) && (sum <= 30))
flag = 2;
else if (sum > 30)
flag = 3;
//Display status.
var strength = "";
var appliedClass = "";
switch (flag) {
case 0:
case 1:
strength = translator.getTranslate("PAGE_DEVICE_PASSWORD_POPUP_PASSWORD_STRENGTH_WEAK");
$(".weak").show();
$(".good").hide();
$(".strong").hide();
appliedClass = "passwordWeak";
break;
case 2:
strength = translator.getTranslate("PAGE_DEVICE_PASSWORD_POPUP_PASSWORD_STRENGTH_STRONG");
$(".good").show();
$(".weak").hide();
$(".strong").hide();
appliedClass = "passwordGood";
break;
case 3:
strength = translator.getTranslate("PAGE_DEVICE_PASSWORD_POPUP_PASSWORD_STRENGTH_VERY_STRONG");
$(".strong").show();
$(".weak").hide();
$(".good").hide();
appliedClass = "passwordStrong";
break;
}
password_strength.innerHTML = strength;
$("#password_level").removeClass("passwordWeak passwordGood passwordStrong");
$("#password_level").addClass(appliedClass);
}```
So this code is combining the input, output, and algorithm all in one place: easy to write, easy to read. Each character in the password adds a given weight to the total, with earlier characters having a heavier weight than later ones. The sum of the weights is transformed into a classification flag: 1 is bad (does not pass the UI), 2 is okay-ish and 3 (meaning a sum of 20 or higher) is great.
The password our submitter entered has a weight of over 80, so it ought to pass—and yet, it does not. (cnt >= (len/2))
is the condition that's failing. cnt
is incremented every time it sees a character more than once in a password, meaning if there's too many repeated characters, the password will fail. This is probably meant to stop passwords like aaaaaaaaaaaaaaaaaaaa
from passing muster just based on length alone. And yet, given that the only valid characters are the Latin alphabet, Arabic numerals, and some few special characters, the chance to randomly hit the same character more than once goes up dramatically the longer the password is. So the longer your password is, the more likely it fails this check.
The submitter looked at the code and considered setting cnt
to 1 in the debugger just to pass the check. The temptation was strong, but ultimately, they just cut the password in half and submitted it that way. After all, we're not yet in a post-apocalyptic cyberpunk universe where the aforementioned street gangs are looking to pick off easy targets. It's probably fine.
Probably.