As is tradition with my blog posts, let’s start off a definition of what HTTP pipelining is all about.
“HTTP pipelining is a feature of HTTP/1.1 which allows multiple HTTP requests to be sent over a single TCP connection without waiting for the corresponding responses. HTTP/1.1 requires servers to respond to pipelined requests correctly, with non-pipelined but valid responses even if [the] server does not support HTTP pipelining. Despite this requirement, many legacy HTTP/1.1 servers do not support pipelining correctly, forcing most HTTP clients to not use HTTP pipelining.” (taken from https://en.wikipedia.org/wiki/HTTP_pipelining)
And from an F5 technical article because I love that they used the term “shoving” to describe this: “In technical terms, HTTP pipelining is initiated by the browser by opening a connection to the server and then sending multiple requests to the server without waiting for a response. Once the requests are all sent then the browser starts listening for responses. The reason this is considered an acceleration technique is that by shoving all the requests at the server at once you essentially save the RTT (Round Trip Time) on the connection waiting for a response after each request is sent.” (taken from https://community.f5.com/t5/technical-articles/http-pipelining-a-security-risk-without-real-performance/ta-p/286621)
Are you all listening at the back? Ok, let me setup the scene here and then we’ll invite our pipelining friend back to the party when needed.
Moving onto password reset tokens. You know the score on these. You’ve forgotten your password, so you select “forgot password” from an application, likely put in your e-mail address (or username, etc. etc.) and it (a good implementation) will e-mail a unique “random” time limited one-time password link (a token) to the registered e-mail address on file. You need to imagine me doing air quotes with my fingers on the “random” bit there. (Work with me.) As the owner of this e-mail address, you check your e-mail, follow the link (which contains the token) and you get to set a new password, without needing to know the previous. You then log in and get on with your day. Note how I said a good implementation will do this in the above. There are many ways this can be done badly.
Bad password reset mechanisms will invoke a server side action straight away to issue the account with a new (temporary) password, sent over cleartext e-mail to the user’s registered e-mail – using the new password (in effect) as the token, allowing them to login and then the server forces them to change it. This is bad times and leads to a vulnerability we call “Password Reset DoS” down in pentester partyland. You have to assume that bad people are requesting password resets for other users and instead go down the ‘issue password reset links with a token’ route instead. Assume everyone is bad until the link (with a valid token) is followed and it is only at this point that they are verified as being a legit good person, who owns that e-mail address.
Other bad password token reset mechanisms will issue a password reset link with a predictable token value, a static value, one which does not expire and one which can be used again and again. The very very very bad reset password links will also have a user-defined username/id parameter present with no kind of Hash-based Message Authentication Code (HMAC) integrity check present or lacking any server side validation, opening the doors to a whole lot of pain (and compromise) – and that’s another blog post.
I’m going to setup a scenario whereby the password reset mechanism is not the worst, on the face of it, it looks pretty good. However, the more we dig into it we can find some minor issues and if we bring our friend HTTP pipelining back to the hacker party, we can combine them altogether for arbitrary account take overs.
First rule of pentester club is that in order to break anything you first need to understand how it works. It is then you’re able to formulate an attack plan after visualising it, which helps a lot from our often black box perspective. As a pentester from this perspective, we don’t get all the details right and we have to make assumptions, but that’s ok. “Give me six hours to chop down a tree and I will spend the first four sharpening the axe”, which I believe Abraham Lincoln is quoted as saying.
Imagine the web application before you now, the bright glow from the monitor showing the forgotten password page lighting up your room, asking for an e-mail address. You’ve got an account already, you just want to learn how this thing rolls, so you put in your e-mail address and click that forgot password button.
*YOU’VE GOT MAIL* (cue Outlook new e-mail ding sound… is it even a ding?)
“Please visit https://127.0.0.1/resetpassword/36525646 to reset your password.”
You put your e-mail address in again, because you’re a pentester, because this is life. I joke… but you do it again because you need to compare against your baseline for deviations, much like a scientist does with their control group samples.
*DING*
“Please visit https://127.0.0.1/resetpassword/1657177506 to reset your password.”
We do it again and again, and again, because that’s just how we roll and because we’re all about the bigger sample size for better analysis. 50 will do for now, just so we can eyeball.
If we extract the token from all 50, this a list of them in the order in which we received them.
365256460
1657177506
1986055580
741532205
527890360
442059229
987251296
717599616
29792672
825171448
1798342323
498299249
1320462756
800784560
107292283
1986043443
537545646
910649865
1767536999
296777708
929634488
867032357
980003670
1313317313
530880191
1300024965
464686643
1181592875
693890782
829894330
1718925158
1468835528
837921297
1336689766
362190361
808466195
206029262
433891936
1158511253
1455609935
1893679034
799600364
1521862935
863837241
966780233
257827295
1272555532
494910317
197257454
118248652
If we bring “sort -n” out of the tool box, this is those 50 tokens arranged by size, smallest to largest.
29792672
107292283
118248652
197257454
206029262
257827295
296777708
362190361
365256460
433891936
442059229
464686643
494910317
498299249
527890360
530880191
537545646
693890782
717599616
741532205
799600364
800784560
808466195
825171448
829894330
837921297
863837241
867032357
910649865
929634488
966780233
980003670
987251296
1158511253
1181592875
1272555532
1300024965
1313317313
1320462756
1336689766
1455609935
1468835528
1521862935
1657177506
1718925158
1767536999
1798342323
1893679034
1986043443
1986055580
The smallest number is 29792672 (we’ll call it 29 million something). The largest is 1986055580 (we’ll call it 1 billion something). Now that’s quite a range I’m sure you will agree.
Now putting our pentester hat/hoody/mask back on, we can knock something up locally to represent what we think is happening in old server land. We don’t know the inner thing happening from our black box perspective, but based on our observations of the output token (the range differences), the following simple code with respect of srand() will give us the same outcome, using min and max ranges as 10000000 and 2000000000.
Figure 1. Code to generate random values between 10000000 and 2000000000 to simulate the server logic.
Now we may be wrong (and we probably are) on what exactly is going on with respect of initiating the seed value – they may be using time, they may be using a static value, they may be using something else. However, like I said earlier, that’s ok, we’re just looking to see if we’ve got something viable to attack here.
Remember earlier when I said that a few small/minor weaknesses with this password reset mechanism will allow this attack? Well ponder this… in this application, each time we ask for a new reset password token link from the server, the old one still remains valid. (You’re supposed to gasp at this!) That’s point number one. Point number two is that each of these tokens are valid for 10 hours. (Shock horror, audience gasps again!)
It is at this point I take my inspiration from another technique, heap spraying, cue Wikipedia: “In computer security, heap spraying is a technique used in exploits to facilitate arbitrary code execution. The part of the source code of an exploit that implements this technique is called a heap spray. In general, code that sprays the heap attempts to put a certain sequence of bytes at a predetermined location in the memory of a target process by having it allocate (large) blocks on the process's heap and fill the bytes in these blocks with the right values.” (taken from https://en.wikipedia.org/wiki/Heap_spraying)
I am able to use the concept of heap spraying against this password reset mechanism. This, together with the speed/magic of HTTP pipelining makes this attack possible, way within the 10 hour time window before a token expires.
What would I do next? I’m glad you asked. This is what I would do.
I would generate locally all values within those ranges using something like C/seq/Python and output them to a file. Based on the ranges in this example, this will take a longggggg while and take up lots of disk space – tea and biscuits (possibly a crumpet) time.
I’d then fire up Burp and use normal Intruder to continuously request password resets for our proof of concept (victim) account. This is something which will need to be throttled down and limited because this has the potential for DoS as lots of server side things are happening here involving the server’s mail server and your local mail server. If this were a real attack then the victim would be getting spammed – a quiet attack this is not! It doesn’t matter too much the speed of the password reset requests (as in it doesn’t need to be the speed of light), what matters is that it gets started ahead of time as to fill our imaginary ‘heap' up server side (Note, not an actual memory heap, I’m just using the same concept). Think of it as the more lottery tickets you buy then the more likely you are to win - even if this is a tiny tiny tiny percentage increase, it’s still an increase in the right direction!
Now, for the big guns. We would bust something out like Burp’s Turbo Intruder, feed in our generated file from earlier with all the generated numbers in the ranges, tweak a few things, and fire them torpedoes!
Spoiler alert. I actually made the above story into a reality, and set this up locally.
Pull up a seat, sit back and relax.
To represent creating password reset link tokens on the server side, I used the code shown earlier to seed 50 token values (randomly, between 10 million and 2 billion) and saved them to a local file, 50.txt.
Figure 2. Last 10 lines of the 50 token values randomly generated earlier (full file contains 50 tokens).
I then create files (named after each of the numbers) without any file extension, inside the web server’s web root, under a “/resetpassword/” directory, e.g., “1313317317” becomes the file “/resetpassword/1313317317”. This is achieved with a bash ‘for’ loop.
Figure 3. Creating files with numbers as the actual filenames to represent valid tokens to live on the web server.
The web server (Apache) will be serving files from here. When a file (representing a token) is correctly requested it would return a “HTTP 200 OK” message else this would be a “HTTP 404 Not Found”. This recreates a valid password reset token link server side and a not so valid one. This represents me having used the heap spray concept/technique (to request a password reset link with a token being created server side), but only 50 times.
I then created a big (and I mean bigggg) file with ranges from 10 million to 99 million with this Python script. (Note, the highest number this will generate will actually be “98999999” based on this code due to the loop starting at “0” because computers and arrays, but as this is a proof of concept this is fine. We’d be very unlucky if the token was “99000000” and we could tweak the ‘for’ loop to go up to “99000001” if we wanted to accommodate, but for illustrative purposes, this is fine, just something I wanted to make you aware of!)
Figure 4. Code to create our list of tokens to hunt for.
As this is a proof of concept I also picked this smaller range to make it more manageable. In reality you’d probably still do this approach and increment/generate the next ranges in segments (up to 2 billion) accordingly if you didn’t get lucky. I ran the program and directed the output into a text file, numbers_to_hunt_for.txt.
After a couple of minutes we have our file, with all values from 10 million up to just one shy of 99 million.
Figure 5. Last 10 lines of our list of tokens file to hunt for (full file contains 89 million tokens).
We then fire up Apache to host the web server, servicing requests to /resetpassword/<token>.
We then open up Burp and configure Turbo Intruder, feeding in our numbers_to_hunt_for.txt file. I’ve not shown this because I don’t want this blog post to become a complete step-by-step guide/tutorial, this is not the intention of this post!
Testing this locally, without taking into account network latency, I had in the region of 10,500 requests per second from Turbo Intruder!
Figure 6. Turbo Intruder busting out ~10,500 requests per second!
Normally we wouldn’t see it from this perspective, but if we tail -f the web server logs while this attack is going on we’ll be spammed (scrolled out!) by tons of requests with HTTP 404 being returned. If you recall back, the 404’s is the server telling the attacker the token supplied is invalid.
Figure 7. View from the web server of invalid token requests, scrolling past at light speed.
Around 30 minutes later, the needle in the haystack is found and we are rewarded with a HTTP 200 being returned in Burp’s window, for token “29792672” (“/resetpassword/29792672”).
Figure 8. Turbo Intruder finding a valid token of “29792672” with a HTTP 200 OK response.
This is how it would look on the web server, which we wouldn’t see from the attacker perspective, but I’ve added it in to give you the 360 view – because I’m all about the views.
Figure 9. View from the web server of invalid tokens (HTTP 404) followed by a valid token (“29792672”) at the bottom (HTTP 200).
In the real world, we’d then connect using this token and set a new password for the user. It is worth noting that in the real world (a real application!), there may be some other active reset password tokens already ‘in the wild’ triggered by actual users which you may stumble upon using this method.
Beautiful, isn’t it? I speak with my pentester hat firmly on of course.
With the defence hat on, how does this get fixed? Reset tokens should be random and unique - there are plenty of crypto articles out there about how to seed and do this, so I won’t repeat. Lots of frameworks have specifics you can and should use to do things securely. The great fail in this example was that when a new token was generated that the old one wasn’t nuked. This allowed us to do our spraying thing. If we had been hunting for just one valid token it would have been possible but taken a while longer. This brings me onto the last point, token expiry. The token expiry time also combined for this to happen. This expiry should be reduced down to a much much shorter time, something like an hour would be good – it all depends on the criticality of the application and your threat model of course - you may want this value to be 5 minutes!
I wanted this blog post to be about spotting patterns in things, pairing together (and attacking) lots of minor weaknesses, applying techniques from other places (aka spraying) and using (and abusing) the super power that is HTTP pipelining to achieve great things.
As always, thanks for reading! Happy hunting.