Security, and particularly around authentication, authorization, and auditing, is my favorite part of software development. It’s the stuff that not just lets us be safe, but rather, the reason I like it so much is that it’s by far the broadest part of software development. It requires us to understand the full breadth of the field, from hardware security components like TPM (Trusted Platform Module) chips to IETF standards-based protocols that not only make things safer but open the door to creating simpler, better, and more integrated systems. Historically it may not have always been the case, and security was at odds with other fields like performance and usability. Those problems have long been addressed now, once we realized that thinking of systems as having behavior emergent from the interaction of many systems and focusing on the end problem we’re trying to solve, instead of trying to fit the problem into an isolated individual system.
This new way of thinking gave way to new fields such as Systems Engineering, where the focus moves to focus on discovering the real problems that need to be resolved and identifying the most probable and highest impact failures that can occur. The domain of security, and organizations like (ISC)², OWASP and NIST have recognized and pushed the application of this understanding very well over the years, and standards have changed and become better.
One concrete example of this I think is NIST’s update to NIST 800-171 to remove periodic password change requirements, and drop the password complexity requirements in favor of screening new passwords against a list of commonly used or compromised passwords.
We figured out that we need to understand the user and make security decisions that work around them, thus the field of UX (User Experience) became very relevant to security. Here are some ideas I’ve collected so far to add into an authentication server:
Account locking was developed in order to prevent password brute-forcing or password guessing. The idea is simple and effective; lock an account for 30 minutes if the user enters their password wrong 5 times or some variant of this. This basically makes it infeasible for an attacker, but its strength depends on the implementation, and there are questions like if we reset the lock count every time the user enters the correct password. If the password is used by a service account that reauthenticates every minute for example and the count is reset then, then our attack vector is to try 4 passwords every minute, thus avoiding locking. This is still pretty good actually, but what surprised me was that we haven’t seen a virus/botnet/attack that just goes around and locks everyone’s account continuously.
Instead of locking the account, we could gradually increase the time it takes to verify a password by introducing a random delay. Random delays already play a valuable role in password verification, because, without them, an attacker could use the certain timing techniques to guess at the password (although modern salting + hashing techniques mostly solved this). The first login attempt would be basically instant, but after the first bad attempt, the second one would add a 1-second delay, then the 3rd would add a 5-second delay, 4th would be a 15-second delay, and so on. If the user really mistyped or forgot their password, they would still have an ok experience. The delay would still be there for the good password attempt as well, and the delay would go down in time as there are fewer attempts. This could make guessing passwords quite expensive for the attacker, and a better experience for the user.
Another option could be to borrow from bitcoin and use proof-of-work. The server could publish a pattern the hash needs to match, which dictates the difficulty, then generate a hash based on the password that matches the pattern, and send it along with the password. I’m also exploring a technique using POP (Proof of Key Possession) as a way to avoid sending the password or a fixed hash over the wire at all.
Password topologies are basically the (geographic) topology of the password and common substitutions. The most common one being a password that starts with a 6-7 letter dictionary word, the first character uppercase and has a 1 at the end. If special characters are also required, o becomes 0, a becomes @, or an ! is added to the end. My suggestion is to keep a running counter of the most used topologies over time (these will be ever-growing) and show the user where their password sits on a standard distribution bar chart of the top 50 topologies.
Another enhancement would be to compare the password against public leaked credential databases like https://haveibeenpwned.com/ and warn the user if they’re using a common password or one that has been found in a leaked database. Basically another graph indicating how common or how easy their password would be to crack, and suggestions for making it stronger. My suggestion is usually to stay away from character replacements in short passwords, and instead use a sentence. Sentences are easy for people to remember and natural to type while being quite a challenge for anyone to crack.
The less time the user has to type their password, the fewer chances there are of it being intercepted. From key loggers to MITM (man-in-the-middle) attacks to shoulder surfing, there are plenty of threats. Instead, Microsoft came up with a great solution for Windows 10: use a pin to unlock the computer. If you enter the pin incorrectly too many times, you now need to enter your password. The pin could work on the web as well, and we might even be able to make the browser itself a second-factor authentication device.
During initial or new device auth, the user could be prompted to MFA as well, and enroll their browser in PIN authentication. On the server, some cryptographic material would associate the pin with the password (still working on the details), and the browser would keep a public/private key pair. When the PIN is entered, it’s signed and sent to the server for validation. If a bad PIN was entered enough times (maybe 5), then the associated browser becomes disassociated with the account, and now needs to prompt the user for a password.
Send the user around some partner services, or describe the user (email, headers hash, IP address, etc) in a standard format, which other partner services could vouch for having seen normal activity from the user recently. If the user trying to log into your super-sensitive system has recently logged into their bank account from the same computer and posted a tweet recently from that same computer, chances are higher of them being who they say they are, as opposed to a brand new computer popping up in a random location trying to transfer funds out of the account on the first login.
Techniques like account locking are also not effective against attacks like brute-force when the password used across multiple submissions is the same, but the username changes. One technique I originally came up with involved keeping a counter in a short-lived cache with a password hash using a different algorithm than the one used in the password store for extra measure. However, other similar attacks like credential stuffing get around that. In credential stuffing, a hacker has a database of username + password combinations that they stole from another site, and will just try combinations in hopes that a user reused the same password on multiple sites. Over a 17-month period, from November 2017 through the end of March 2019, security and content delivery company Akamai detected 55 billion credential stuffing attacks across dozens of verticals.
As with any cybersecurity defense, it’s a battle of wits and time. The first thing we need to do is to detect the attack. We can get an idea of an attack happening by monitoring failed password attempts. Then we can use multiple fingerprinting techniques at the same time, and track attempts per fingerprint vector, and flag each fingerprint vector as a potential attack spot. We can link fingerprint hashes to each other, and if we detect an attack pattern on one, use the associated ones to block across all of them. This way, if the attacker is stealthy and continuously uses a different IP, or presents a random user-agent or list of headers, we can still identify them.
Additionally, a captcha, sequence of requests (user has to GET the login page before they can submit it), and multi-factor authentication are really good defenses.
Blocking accounts with leaked passwords and forcing users to change their passwords is an expensive solution, but depending on what we’re protecting, might be worthwhile.