
Um we have um Harriet Scoffield doing a um a talk now. >> So the need for speed exploiting race conditions. >> Um hi everyone. Thanks for coming along right near the end of a very long and warm day um to learn about race conditions. So I'm Harriet. Um, I work at KPMG in the cyber defense team as a penetration tester. I've been at KPMG for coming up to three years now. Um, I in terms of qualifications, I'm a Czech team member, so I am slightly qualified to be talking to you about this today. Um, how I got to this position, um, I studied computer science at university, um, and then joined KPMG on the grad
scheme. I'm also really really passionate about getting more women and girls into cyber and tech roles. Um, and my main personality trait is that I have a dog. So, I wish all my slides could look like this, but I don't think the organizers would be that pleased. >> Um, so what are we going to be talking about for the next 15 20 minutes? Um, we're going to go through a chaining of two different vulnerabilities that we've been seeing a lot more recently with our clients. Um, so we're going to start with username enumeration and then drill down a bit deeper into time based username enumeration and then we're going to look at race conditions and tie those both together to um brute
force and application login page. Then we're going to talk about what that actually means and how we can mitigate it. So username enumeration then basically just boils down to gaining a list of usernames that are valid for an application. So when you're interacting with an application, anything you do like submit a login request or access some data, you're sending a request to the application is doing its processing, whatever it does, and then it comes back with a response. So when you're looking for valid usernames, you can see a couple of discrepancies in the responses from the application um and use that to work out if the username that you've entered is valid or invalid. So there's a couple of things here that
you can look for. So time is what we're going to be focusing on in this talk. um but the time it takes for the application to respond when you enter a valid username versus an invalid username into the login box. Error messages. So if you enter a valid username but an invalid password and then you try and log in, it might say your password's incorrect. But if you enter an invalid username, it might say your username's incorrect. Then you've worked out that username is or isn't there. Response size. So it's kind of similar to time, but if the username is valid versus invalid, the response that the application gives you might differ in size. Even if it's only differing by one
bite, if it's differing consistently between valid and invalid usernames, then that's a way to work out a list of valid usernames. Uh registration. So, if the application has a sign up form and you try and enter an email that's already registered to the application and it gives you an error saying this email is already registered, then you've got a valid username. Similar to that, a forgotten password mechanism. If you enter a valid username into a forgot password mechanism, it might tell you, great, we've sent you a link um to reset your password. But if you enter an invalid username, it might say to you that username doesn't exist. So then you figured out invalid and
invalid usernames. And finally, lockout behavior. So if the application has a lockout um limit of five invalid attempts and you enter invalid passwords five times for a valid username, when you next try it might say you're locked out. But if it's an invalid username, it might not give you the lockout message. It might just keep saying invalid credentials, invalid credentials. So let's drill a bit deeper into timebased username enumeration. So as I've already said about 20 times, we've got two cases, a valid username and an invalid username. So let's look at the invalid username case first. So, we've got our login page, and this is the database that's somewhere on the back end of the
application. The passwords in real life should be hashed, but for simplicity, they're just there. Um, so we're going to try and login as user five. Obviously, user five, as we can see, is not in our database. What we do, we press submit. The application takes the details that we've sent to it. It looks through the database, doesn't find user five, so it comes back and tells us that our credentials are invalid. Super quick. Just checks the username field. Done. When we you have a valid username, let's bring back the login page in the database. So here we've got user three, but the password is invalid. So press submit again. The application goes to the database, looks down the
database, finds that user 3 is there. It then has to get the password that's stored for user 3, check it against the password that the users entered and then tell you invalid credentials. So compared to the first case, there's a lot more checks going on, which obviously takes more time. So let's see this in a real life example. Who is familiar with Burp Suite here? Okay, quite a few people. For those who aren't familiar with Burpuite, it's a sort of proxy tool. So, as I said, you're sending requests to the application. The application sends back responses. Burpuite sit in the middle. Um, and it can sort of look at and intercept the requests and responses as
they go past. So, we've got our two cases here. Um, the column all the way to the right is the time and it's in milliseconds. So you wouldn't notice it to the human eye, but as we're capturing it using this tool, it can tell us exactly how long it's taken. So in the top one with the orange highlights there, we've got our invalid username case. Um, we can see the time is 39 and 40 milliseconds. With our valid username case in the blue, the times are coming back as 190 and 160. So as you can see it's quite a significant difference because the application is doing all those extra checks checking the password and then
returning back. So if you supplement this with a bit of ointent for example if you're pentesting a company's login portal or something like that um go on their company LinkedIn scrape through the employees then you've got a list of usernames that might be in the application. chuck them all at the application, see what the response times are, and then you've got your list of valid usernames. Perfect. We've got our list of valid usernames. Here's the login page again, right? Let's brute force and try and find the passwords. Um, so we just chuck the usernames at the login page and cycle through with different passwords. See if we get any hits. Easy. Well, it might not work like that because the
application might enforce some mitigating controls such as account lockouts, so you're only going to have five attempts before you get locked out or a strong password policy. So, which makes it a lot less likely that we're actually going to guess the password in those five attempts. So, this is where race conditions come in. So race conditions occur when an application um accepts requests um at the same time and processes them at the same time without proper protections. That's a confusing sentence, but I've got another PowerPoint diagram. So we'll we'll we'll work out what it actually means. Let's bring back our login prompt. This time bring it back three times. We're in attacker. We've got three tabs and we're
going to try and brute force user 3's password. In this example, let's pretend that the lockout policy is one attempt. So, we send one invalid attempt and we're done. We don't have any second chances. If we were to send these one by one, we'd get locked out after the very first attempt because user 3's password is password three. So, after we send user three and password one, we'd be locked out. But we're done for. So when you send this to the application, as we've just seen, it's asking questions like, is the username correct? Is the password correct? Is the user locked out is the main one that we want to focus on. So as we said, if we sent these one by
one, the application would check if the user's locked out, which with our first request, it's not locked out yet, but the password's wrong, so it's going to lock us out. If we send them all at exactly the same time, the application is gonna ask at the same time for each of these if the user's locked out. So if we haven't sent any requests yet, we send all three of these at the same time. It reads the variable the account locked is false for all three of these requests and it processes them all. So we can end up sending all three of our requests at once and we'll get the responses for all of them and we'll see
that password 3 was successful. So basically just bypassed the account lockout policy because if we sent them individually we would have been locked out but because we sent them all at the same time and the application was vulnerable to race condition vulnerability we've got three. So, let's scale it up because sending free requests isn't that big. So, I've duplicated our login page as many times as I could be bothered. Um, and we're going to send it all at the same time. For all of these, it's going to ask, is the user locked out? It's going to read that it's not locked out. And then all of the requests are going to be successfully processed. And
hopefully if you've got one password right in all of your concurrent requests, you'd get a response back saying login successful or something. So let's actually see how to do it. Um coming back to Burpuite again, here's a login request. Um you I'm going to use my laser. Um I've put in test at test.com and then test as the password. So this request and response has been intercepted. Um so we can see what it consists of. We're going to send this to the repeater and then we're going to right click on our tab and press add to group and make a new tab group. It gives you this popup. You can call your tab group a nice name and give it a
nice color. Um I chose purple. So there you can see after we've done that we've got our um little tab group. We've got our tab um which is our login request and it's in our race conditions tab group. If you right click on your tab again, you can duplicate your tab. We're going to do 25 times. Most of the time if there's an account lockout policy, it might be about five attempts. Um so 20 sending 25 at the same time is going to be more than enough to see if we've gone past that five attempts or not. So what we're trying to see is we send these 25 at the same time and then we
try and log in again. And if it lets us log in again after sending 25 invalid requests, then we've bypassed the account lockout policy because we should be locked out after 25 invalid attempts. So duplicate 25 times. Now we've got 25 um tabs. And then we want to send it. Um, obviously don't actually send stuff unless you're allowed to. Um, but you press the little drop down arrow next to send and click send group in parallel. So again, that will send them all at the same time. And then there you go. If any of the passwords that you included um were correct, you can edit each tab if you want to try different passwords. Um,
then you'd get a response saying login successful. But what we're mainly trying to do is see if the application is vulnerable to race conditions. If we can send these 25 and still log in again afterwards and it's treated all of these 25 as one attempt. So back to our diagram. This is what we've just done but in Burpuite. So we've duplicated our tab um and then we sent it all at the same time. So what does that actually all mean? It's the end of the day. I've got a very rubbish short-term memory. So, let's recap this. Um, so right at the beginning, we identified that there was a timebased username enumeration vulnerability within the application.
So, we used this and we did a bit of OSIN and we've got a list of valid usernames for the application. We then identified that the application is vulnerable to a race condition vulnerability. So, we can send a load of requests and they'll all be processed at the same time. Those three steps is enough to prove that these vulnerabilities exist and it's enough to raise a finding in a pen penetration test. Going further sort of tiptoes into denial of service territory. So um if you're working on a client, don't send thousands of requests with different passwords because um they won't be happy. But um theoretically what you can do next is um actually exploit the race condition vulnerability
um and get a list of common passwords um and send thousands of tabs in the repeater or um using Turbo Intruder which is an extension in Burp. Um send thousands all with different passwords at the same time and see if any of them come back with a successful response. But obviously sending thousands of um requests at the same time is a bit um sketchy. And the final step then is if any of your requests were successful um you've identified valid login credentials to the application. So you've started completely unauthenticated. You've worked out valid usernames and then you've worked out valid password. So now you have authenticated access to the application which if there's more vulnerabilities in the application it
shows that you can start unauthenticated and then start exploiting more vulnerabilities in the application. So how do we actually stop this from happening? So for time based username enumeration, you just want to make sure that um requests and responses for valid usernames versus invalid usernames have roughly the same amount of time. So to do that, you can um just make sure that when the application goes through the login function, it's taking the same amount of time or you can just add some random time delays. Obviously, we're working in milliseconds, so um the user's not going to notice. it's not going to impact performance if you add a bit of extra time that it takes them to
log in. For race conditions, there's a couple of different things you can do just to ensure defense and depth. Um, so you can lock resources. So that account locked variable um only let one request use it at a time until it's completely finished processing and then a different request can use it. You can use transactions. So that bundles all of the different steps. For example, is the username correct? Is the password correct? Is the user locked out? Bundles it into one big transaction that has to completely finish before the next one can begin. Use atomic and indivisible operations. So that's kind of similar to transactions, but you're using operations that are like single operations and they have to be done all
or not at all. Rate limiting. Obviously, we're trying to send thousands of requests at once if we're rate limited. So, the application says you can only send one request per second. And we're not going to be able to send enough in a short amount of time that we're actually going to feasibly be able to guess the password. Discard redundant or conflicting requests. So, we're sending the same request, but just changing the password each time. Um, so they're obviously all redundant. only one of them is going to work if any of them. And finally, avoid mixing data from different storage places. So, we're checking if the username is correct. We're checking if the password's correct. If the application has to go to
different places to get that data, it leaves a bigger window that the race condition can be exploited. That's everything. If you're interested in race conditions, do check out the Portswiger Labs on it. um Portswiger create burpuite. So um they have a really good set of labs um and it's all completely free as well.
[Applause] >> Please don't ask me any hard questions. >> Yes, >> might be an easy one. In a real engagement, would you use some uh already leaked credentials to have more chance in guessing the right? >> So, usually when we're doing a pen test, the client has set up some accounts for us anyway. So, we're able to actually log into the application and test it like that. So, we know some valid usernames. So, we can we can just make up a random username and then compare it to the ones we already have, which is sort of cheating, but it's fine. What's your dog's name? >> Marco. >> Do you know how to change your password?
>> Is your password Marco? >> It's not 123. >> That's a serious question, Harry. So when you were talking about the mitigations of release conditions, um you have to balance that against scaling and the app's ability to deal with for example 10,000 people trying to log in on January the 3rd after a holiday when they've all forgotten their password. Yeah. >> Right. So design, >> it's a balance. Yeah. Yeah. But I mean with rate limiting and stuff like that, you can sort of work out what a reasonable amount of requests might be on a day-to-day basis for your application and then sort of limit it to a reasonable value. So >> yeah, makes sense. Thank you.
>> Question might be a little naive, but uh if you end up enumerating a username successfully. Uh well, you the account is locked anyway, right? As part of as part of your efforts. So what can you do with that username? >> Well, if you it depends what method of enumeration you're using. So if you're using time based username enumeration, you only need to send one request with the valid versus invalid username. So you're only sending one request to just see how long it takes the application to respond to you. So with just one request, you shouldn't be locked out. Um it's yeah when you're sending however many that meets the account lockout policy but yeah when you're validating
usernames by time based um you only need to send one request and then it does all the backend flow checking if the passwords the same or whatever um but because it's only been one you're not locked out. >> Thank you. >> Yes. >> How often do you find this? >> Um pretty often. Um it's the username enumeration especially is occurring more and more often as developers sort of streamline their code a lot more. So if they can return back invalid credentials fast because the username doesn't exist they will just return it fast whereas that's where the discrepancy comes in because if it's a valid username then you have to do all the extra checks. Um
so yeah it's becoming a lot more common. >> Thank you very much. >> Wonderful. Thank you.