4

I design a 'secure' login system. Let's focus in the brute force prevention. In my Android client I have a counter that counts the number of times that the user tries to login, with false credentials. When it reaches 3 fail tentatives, I disable the login button and the password field, forcing the user to restart the app, and thus blocking the brute force attack. Is this the right thing to do?

Or should I put a counter in the server? But in the server it can lead to DoS, if I receive a lot of brute force attempts at the same time, right?

The idea is stop the infinite number of inputs, so in my opinion this can be done in the application side, in my case in the Android client.

I'm open to suggestions.

  • 35
    You think serious attackers are going to use your app to log in? Of course they won't. I'm going to download your app, decompile it, comment out all the bits not related to logging in, run it on several servers and make 10000 login attempts per second, if I want to. – immibis Nov 24 '17 at 10:57
  • 1
    @S.L.Barth, yes mate. I accept your answer before he reply. Thank you both, i will pass the validation to the server. – rew1nd Nov 24 '17 at 12:35
  • 3
    "i will reach your server and comment the lines that are blocking my attack" is WAY WAY WAY more difficult that removing a limit from the app. You do not rely on "app`s code obscurity" for your safety. – Caterpillaraoz Nov 24 '17 at 14:09
  • 2
    I think just adding label "Please do not try to log in more than three times" would be better. Way easier to implement and about the same effective as login throttling in client. – el.pescado Nov 24 '17 at 14:48
  • 2
    @rew1nd "The same way, that if i want with more time, i will reach your server and comment the lines that are blocking my attack." - at that point, you've got a major security breach, and the attackers will have ruined your production data and stolen all the password hashes for offline cracking. Server-side checks are something that you can rely on for brute force prevention, you just have to make sure to implement them correctly. – Ethan Kaminski Nov 24 '17 at 15:41
23

Client side measures are only a partial (and mostly cosmetic) solution, this can only limit non-serious attempts. Any serious attempt will either hit your server directly because a login URL/API was detected, or will run your client through an intercepting proxy to capture the details required to create a brute force run.

Robust defences generally require you to assume that the attacker knows everything about the system except the secret key (Kerckhoff's Principle) so you should start from that position.

Mitigating measures include:

  • make it hard/impossible for an attacker to determine valid user names, invalid passwords or locked out accounts (basically minimal feedback, i.e. no "invalid username" or "password too long" messages, though issue a unique incident code if this is important for user support). As noted in the comment by My1, open registration may be a weak point here.
  • make it hard to determine anything other than success or failure, a failed login should take the same time as a successful one — this usually means making both artificially long (200ms is a starting point)
  • make the login API multi-step using a nonce or some time- or session-based token to complicate automated attacks, and prevent distributed attacks; or add some client-side proof of work (client side password hashing is one option, but needs careful consideration)
  • make sure the password space is sufficiently large, encourage (and support!) the use of a password manager
  • use account lockout and source IP lockout with exponentially increasing lockout times (e.g. double the lockout time on each failure, this should avoid annoying a user who gets a password wrong a couple of times). As noted in the comments, this is an opportunity for DOS, so consider the tradeoffs carefully
  • implement adaptive rate-control and malicious activity detection (this is generally harder than any of the other solutions as it requires additional state and logic on the server side)
  • consider adding an "under attack" mode of operation that can be enabled as required, ideally this will have minimal impact on users

From CAPEC-112 Brute Force:

The key factor in this attack is the attackers' ability to explore the possible secret space rapidly. While the defender cannot control the resources available to an attacker, they can control the size of the secret space. The defender must rely on making sure that the time and resources necessary to do so will exceed the value of the information.

See also CAPEC-49 Password Brute Forcing

  • 1
    "make it hard/impossible for an attacker to determine valid user names, invalid passwords or locked out accounts" this might be adding to security on the login but usually the registration is open to this as it is checked whether the name is available or not, and in the end it usually only annoys the user. in a closed community though, it can really help. – My1 Nov 24 '17 at 13:40
  • 3
    "use account lockout and source IP lockout" This can cause a denial of service against the user accounts involved. It's probably better to use a CAPTCHA or otherwise proof-of-work for the client when too many invalid logins have been attempted, either for a given account since the most recent successful login, or in general (e.g. from any particular IP address or network). – a CVn Nov 24 '17 at 15:04
  • You don't have to extend a successful login...the important point is to make all failed logins take the same amount of time regardless of the reason for failure such that it's not possible to determine bad username vs bad password vs locked account, etc. – alex.forencich Nov 25 '17 at 19:46
7

The customary way of preventing a brute-force attack is to add a little delay on the server.

As has been pointed out in a comment by user immibis, an attacker does not need to use your app to perform a DoS or DDoS. All the attacker needs is the endpoint, then they can flood it. And they can find the endpoint by reverse engineering the .apk file, or by just monitoring the outgoing traffic.

So defense must be on the server side. By adding a little delay after every failed login attempt (e.g. 1 second), it becomes impractical for an attacker to try out millions of possible username/password combinations.

If you wish to protect your server from DDoS attacks in general, you either need a strong server farm, or use a service like CloudFlare.

  • 8
    Careful! If you blindly add a delay on the server, you might run out of OS threads or other limited resources on the server side, turning the brute force login attack into a denial of service attack. – a CVn Nov 24 '17 at 13:39
0

One important aspect is whether there is any sort of key material stored on the device that can be used to authenticate the device or if a user can simply take a brand new device, download the app, and log in.

If there is such key material on the device it is possible for the server to apply rate limits per device. Other ways of applying rate limits server side (such as rate limits per IP or per username) are potential DoS attack vectors. That is true regardless of whether the rate limit is implemented by having the server perform CPU heavy calculations as part of password validation or by temporary lockouts.

When you are in control of both client and server code you can design a protocol in which the CPU heavy part of validation is done client side, but the server still does a salted hash to ensure that you still have the benefits of server side validation. Obviously if you implement such a thing from scratch there is a risk of vulnerabilities introduced by flaws in design or implementation.

The advantage of doing the CPU intensive part client side is that it mostly eliminates the DoS attack vector. A drawback of doing the CPU intensive part client side is that the client may be limited in CPU resources.

Combining the above approaches is possible. On first login you could have the client do hashing for multiple seconds for example by sending a salt to the client and have it do many rounds of hashing, finally the server does the last round of hashing with a different salt. If the password is accepted the device is send a token that will allow the client to authenticate to the same account with a secondary much cheaper hash in the future. The server can enforce a limit on how many failed login attempts with the token are permitted before the token is expired and the client has go back to the slower hash.

Again I have to warn that there are plenty of ways to introduce security flaws in a design like this, so you have to way that risk against the risk of brute force attacks.

0

It may be possible to combine device tarpitting and proof of work:

If every device had to connect using a self-signed key (~4096bit) as the device identity, and that was used for rate-limiting:

Either

  • each device spends excessive time generating new keys; thus delaying password attempts
  • many logins occur with same client key; guaranteed to be a single malicious devices, thus can be blocked without risk of service DOS.
  • some botnet style sharing of keys vs. logins occurs; limited by the power of devices available to generate new keys --- how valuable a target is your service vs. how long can you make users wait for first login/certificate-key generation?

Or just make it someone-else's problem - use a public 2-factor-authentication system.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.