Building a REST API. I’d like to add a 1 second delay in HandleURL before sending a response when the user tries login credentials. This is in order to mitigate brute-force password attacks.
But every way that I know to add such a delay, for example a loop until the system time has elapsed 1s, or SleepMBS(1), locks the entire web app up for that duration, even for other users.
It’d be better to employ a system that logs and tracks accesses to handle DDoS and brute force attacks, such as GraffitiFirewall rather than trying to artificially introduce pauses.
If you don’t want to follow Anthony’s advice you can try using timers or threads. Doing the method you described such as using SleepMBS or running a loop will lock up the web app for sure.
You can always use Timer.CallLater to delay running a bit of code. Or you can sleep a thread. I use both depending on wether or not it makes sense to use a thread.
In this type of scenario, you’d need either DelayMBS or App.DoEvents. Both are not recommended in a Web app. Locking up a Web app (or one of its session threads) to add an artificial delay is just a bad idea.
I don’t want to rate-limit or introduce DDOS protection as the bulk of my traffic will come from the same organization and IP address. But the service does need to be available on the public internet, hence the need for some sort of pause. These other approaches also introduce more cpu overhead than is really necessary for this type of thing.
The problem with timers is that a session object is never created so I have no persistence outside of the HandleURL method. Once HandleURL has finished executing I need to have constructed the response.
You’re going to hit issues on both sides if you introduce a pause using DoEvents, sleeping the thread, DelayMBS, or SleepMBS. These are not safe for use in Web apps (especially in this context). You may cause unexpected issues on your server-side such as crashes, and the client-side might simply have a lower timeout value than you expect and assume a failure. You’re welcome to try those methods and see if they work for you, but I can practically guarantee that you’ll introduce failures. If not immediately, then at some point down the road, and they’ll be a pain to debug.
As for the timer, you’re correct. There’s no means of implementing a timer here.
I’m not trying to lead you down the wrong path when I say you should implement a different solution.
You could implement a system where the client requests data then the server processes and returns the result via POST to a URL at some later point. That’s a valid mechanism that has its place, but you won’t be able to do this if the client is a desktop application, for example. Or the initial request is made, then the client checks periodically for a valid response, but that’s going to increase hits on your server as well.
Thanks for the note. I wholeheartedly acknowledge that you’re not trying to lead me down the wrong path. Sleeping the thread also sleeps the webserver so that is a no-go approach.
I’m already a subscriber to GraffitiSuite Web so it’s not an issue of cost. But since there’s no persistent state, I can’t tell one client from another other than by IP address, and since I know in advance shared IPs will be used, I don’t want to block legitimate traffic just because one person in an org forgot to update their password in a script or something.
Going to play around with a few approaches but right now I’m really unsure if there is a way to do this safely.
I just don’t see how rate limiting is an option unless set to an extremely high value since I can’t tell clients apart. As for artificial delays, none of us can really say if we’ve used an API that added one, as we would just assume any delay is simply the execution speed of the API. 1 second is an arbitrary value I pulled out of nowhere for testing, but I could easily reduce it to 150ms and who’s to say that kind of delay isn’t due to ping or database speed? I can say for certain that login dialogs implement such an artificial delay, even logging into a Mac.
Thinking about this a bit more, perhaps a better approach would be to rate limit only the login api per login.
So if the wrong password was submitted, require a 1s timeout before accepting another login attempt on that username. That’s one way to identify users other than IP.
Our back and forths got me there, thanks everyone!
How can I prevent a malicious actor from blocking a valid user from logging in by submitting the incorrect password in a loop from the same external IP as a valid user?
For example, assume all this activity is coming from a University. If I know my professor’s email address and I want to stymie their work, I try to login with their email address an incorrect password every second, so when my professor attempts to log in from the same IP they are met with the rate limit?
You can build an unlock mechanism that requires them to change their password and/or username. I would probably build an app specific password system for the API so they can login and invalidate old credentials easily or generate new username/password key/value pairs for their access to the API that is untethered from their email address.
Your system could send an email to the professor notifying them that there are malicious attempts that is using his/her email account for attack purposes , providing the IP address and some other suitable info for details.
These info can even be used to track down the malicious attack, or for more stringent measures, can even be used to drop the packet altogether within the network to mitigate the attack. Your university firewall should be able to be trained to learn this attack and execute counter measures.
You should start with a good simple flexible security framework ( rate limiting + firewall), slowly increasing the complexity of the framework as necessary.
Given that HandleURL needs to immediately return with the data to be sent, I think there’s no way to directly add a delay (w/o locking up the app).
But what if HandleURL returned a redirect response (307 temporary redirect perhaps?) which redirects to a simple PHP page which adds a 1 (or 10 or 30 ) second delay.
I think it’s possible that a malicious bot would follow the redirect, and then be delayed, before coming back to try your API again?
Your passwords should be stored using a system such as PBKDF2, which is an algorithm that can be tuned to be slow and cannot be parallelized. Find out how many iterations on average a password takes to hash in 250ms on the server, and use that number in your implementation. This will limit API-driven brute force attempts to 4 per second, but won’t really be noticed by humans.
However, should your database fall into malicious hands, hashes that take 250ms to compute on your server will likely take much quicker on a high performance system that somebody has built for this. On top of that, brute force attacks aren’t really a thing these days. Attackers will often use lists of known usernames and passwords from other services since users frequently reuse passwords. Their hit rate is something like 95% if I recall correctly. It’s pretty absurd. So there’s no reason to bother with a brute force attack. They’ll catch the vast majority of users due to reuse and not care about the rest.
With that in mind, I recommend instead checking passwords against haveibeenpwned.com and warn the user that their password is already leaked if you find a match. This is much smarter than any “password strength” meter.
Thank you to everyone who has replied with some great ideas.
@Thom_McGrath, thanks for the lead on the haveibeenpwned API. I’ve implemented it in my password requirements for when users are setting their passwords. If it helps anyone else, here is my method for checking a prospective password against the list of known breached passwords:
Private Function IsPasswordCompromised(password as String) As Boolean
// Checks if a password appears in known data breaches using the haveibeenpwned.com k-anonymity API
//
// Parameters:
// password - Password to check
//
// Returns:
// Boolean - True if password found in breaches, False if not found or on error
//
// Notes:
// - Uses k-anonymity to avoid sending full password hash
// - Only first 5 characters of hash are sent to API
// - Times out after 4 seconds
// - Returns false on any error (network, timeout, etc)
If password.Trim = "" Then
Return True
End If
// Calculate SHA1 hash of password
Var hash As String = EncodeHex(Crypto.SHA1(password))
hash = hash.Uppercase
// Get first 5 chars of hash for API query
Var prefix As String = hash.Left(5)
Var suffix As String = hash.Mid(6, hash.Length - 5) // Start FROM position 5, not AT position 5
// Set up URL request
Var url As String = "https://api.pwnedpasswords.com/range/" + prefix
Var conn As New URLConnection
conn.RequestHeader("Add-Padding") = "true" // Helps prevent timing attacks
conn.RequestHeader("User-Agent") = "SecurityCheck/1.0"
Var timeout As Integer = 4
Try
// Make request
Var response As String = conn.SendSync("GET", url, timeout)
// Check response code
If conn.HTTPStatusCode <> 200 Then
Return False
End If
// Split response into lines
Var lines() As String = response.ReplaceLineEndings(EndOfLine).Split(EndOfLine)
// Search for our hash suffix in the response
For Each line As String In lines
If line.Trim <> "" Then
Var parts() As String = line.Split(":")
If parts(0).Trim = suffix Then
// Found a match - password has been compromised
Return True
End If
End If
Next
// No match found
Return False
Catch ex As RuntimeException
// Return false on any error
Return False
End Try
End Function