Listen "High Performance Web Brute-Forcing đ¸đ"
Episode Synopsis
Finding and exploiting bespoke attacks on web applications is, of-course, exciting⌠but I find that performing the most simple of attacks, but as efficiently and effectively as possible, can also feel pretty damn rewarding.
In this short post iâll show you how writing just a few lines of code can have immense gains on web request brute-force attacks, versus using the tools you would probably reach for right now (letâs be honest, itâs Burp).
The task shares huge commonality with offline password cracking; where performance and strategy are everything. Much like a lot of my colleagues who are totally hooked on password cracking, i find the problem of effective web brute-forcing a seriously under-appreciated art.
As a rather contrived example, letâs say we wanted to brute-force Wikipedia pages looking for the word âLuftballonsâ.
Weâll start with our base URL of https://en.wikipedia.org/wiki/0Â (thatâs a zero), and increment 0Â until we find âLuftballonsâ, on page 99.
Lets see that attack in python using the Requests module:import httplib,time, requests
from timeit import default_timer as timer
start = timer()
for x in range(0,100):
r = requests.get('https://en.wikipedia.org/wiki/' + str(x))
if 'Luftballons' in r.text:
print (timer() - start)Execution time:Â 13.9255948067Â seconds. Horrendously slow.
Now, I know what you might be thinking⌠is Requests too high an API to work at speed? Is it bloated and slow compared to say, using raw sockets or something from the standard libary? Well, absolutely not. For a starter, Request is built on the speedy urllib3, but comes with a bunch of smart benefits weâre already taking advantage of without realising:
The gzip and deflate transfer-encodings are supported, so we can receive compressed server responses. This means there is less data on the wire, and we can move more of it in the same amount of time. The benefit is far superior to the processing time required to pack and unpack the server responses.
Persistent DNS. Contrary to what I have read on StackOverflow, using Requests with a single TCP connection does not appear to trigger DNS resolution on each request, it seems to do it once. If you can imaging having to do a full DNS resolve for each request, as some libraries might, the performance hit would be significant.
The problem then, is we are just using Requests really inefficiently.
It doesnât seem to be common knowledge, but Burp opens up a new TCP connection for every single Intruder request, which has a huge overhead on long brute-force attacks. This is what our script was doing too. Lets see what happens if we modify it to reuse the same connection:print 'Trying with requests single connection'
start2 = timer()
s = requests.Session()
for x in range(0,100):
r = s.get('https://en.wikipedia.org/wiki/' + str(x))
if 'Luftballons' in r.text:
print (timer() - start2)
Execution time: 3.16235017776. Much, much faster.
Now if we repeat this attack in Burp, itâll still have a considerable edge⌠why? because of threads.
For a short attack like this, Burpâs default of 5 threads keeps it in line with even highly efficient code. But the longer the attack runs, the greater the time wasted to creating new TCP connections. A few hours into an attack and youâve wasted lots of time.
When Burp says it has 5 thread, what it means is that it can make 5 simultaneous requests via their own connections. But we only have one connection, so lets implement 5 threads that reuse that one connection in our example:import time, requests
from timeit import default_timer as timer
from multiprocessing.dummy import Pool as ThreadPool
start3 = timer()
s = requests.Session()
payloads = []
for x in range(0,100):
payloads.append('https://en.wikipedia.org/wiki/' + str(x))
def worker6(payload):
r = s.get(payload)
if 'Luftballons' in r.text:
print (timer() - start3)
pool = ThreadPool(5)
results = pool.map(worker6, payloads)
pool.close()
pool.join()
Execution time: 0.93794298172. Very fast. Under the same conditions, this will stomp all over Burp; and pretty much anything else you can expect to make without considerable effort.
Room for improvement? sure!:
So the main problem with Request, and almost all http libraries, is that they donât support HTTP Pipelining. HTTP Pipelining is the idea of firing multiple requests through a single TCP connection, without having to wait for each response synchronously. If you look at our last code snippet, it looks like thats exactly what we are doing, but unfortunately weâre not. The Requests library actually locks a TCP connection until it has fully read the response content from the last request. The main reason we are able to get such a big perfomance boost from threads, is that we already have our next requests queued up on the connection and ready to fire the moment itâs available to use by the next worker thread. Weâve effectively just minimised the delay this connection sharing was causing us. Pipelining has its own issues, for example its not supported on all webserver, and connection issues are much harder to deal with if you have bits of multiple requests already in transit.
To get around these limitations but still reap the performance of asynchronous requests, we can do one obvious thing: increase the amount of connections.
We can wrap our last code snippet into 5 threads of its own. This gives us 5 TCP connections, each working as fast as possible to synchronously fire out requests. This is as close we can easily get to HTTP pipelining, but is arguably a far more stable attack.
If you really want to play with true pipelining, take a look at Rubyâs em-http-request.
Hopefully this gives you some ideas of how to script basic, yet efficient, brute-force attacks. Donât assume that because a tool already exists for a job that it means it does it best. As a pen-tester, time is precious and we need to spend it wisely.
-Hiburn8
Note: So burp has no time measurement feature in Intruder, so I created a hack to figure out roughly how fast burp is at making requests. Essentially, I created a jython plugin which registers an extension-generated payload for use in Intruder. When this plugin is called upon to create a payload, it returns an empty string payload, but logs the current time in microseconds to the plugin console. This doesnât give us the exact that time requests were issued or completed⌠but does help us figure out how fast burp is generating requests to send, which, alone, is twice as slow as the last example here in all of my test cases.
In this short post iâll show you how writing just a few lines of code can have immense gains on web request brute-force attacks, versus using the tools you would probably reach for right now (letâs be honest, itâs Burp).
The task shares huge commonality with offline password cracking; where performance and strategy are everything. Much like a lot of my colleagues who are totally hooked on password cracking, i find the problem of effective web brute-forcing a seriously under-appreciated art.
As a rather contrived example, letâs say we wanted to brute-force Wikipedia pages looking for the word âLuftballonsâ.
Weâll start with our base URL of https://en.wikipedia.org/wiki/0Â (thatâs a zero), and increment 0Â until we find âLuftballonsâ, on page 99.
Lets see that attack in python using the Requests module:import httplib,time, requests
from timeit import default_timer as timer
start = timer()
for x in range(0,100):
r = requests.get('https://en.wikipedia.org/wiki/' + str(x))
if 'Luftballons' in r.text:
print (timer() - start)Execution time:Â 13.9255948067Â seconds. Horrendously slow.
Now, I know what you might be thinking⌠is Requests too high an API to work at speed? Is it bloated and slow compared to say, using raw sockets or something from the standard libary? Well, absolutely not. For a starter, Request is built on the speedy urllib3, but comes with a bunch of smart benefits weâre already taking advantage of without realising:
The gzip and deflate transfer-encodings are supported, so we can receive compressed server responses. This means there is less data on the wire, and we can move more of it in the same amount of time. The benefit is far superior to the processing time required to pack and unpack the server responses.
Persistent DNS. Contrary to what I have read on StackOverflow, using Requests with a single TCP connection does not appear to trigger DNS resolution on each request, it seems to do it once. If you can imaging having to do a full DNS resolve for each request, as some libraries might, the performance hit would be significant.
The problem then, is we are just using Requests really inefficiently.
It doesnât seem to be common knowledge, but Burp opens up a new TCP connection for every single Intruder request, which has a huge overhead on long brute-force attacks. This is what our script was doing too. Lets see what happens if we modify it to reuse the same connection:print 'Trying with requests single connection'
start2 = timer()
s = requests.Session()
for x in range(0,100):
r = s.get('https://en.wikipedia.org/wiki/' + str(x))
if 'Luftballons' in r.text:
print (timer() - start2)
Execution time: 3.16235017776. Much, much faster.
Now if we repeat this attack in Burp, itâll still have a considerable edge⌠why? because of threads.
For a short attack like this, Burpâs default of 5 threads keeps it in line with even highly efficient code. But the longer the attack runs, the greater the time wasted to creating new TCP connections. A few hours into an attack and youâve wasted lots of time.
When Burp says it has 5 thread, what it means is that it can make 5 simultaneous requests via their own connections. But we only have one connection, so lets implement 5 threads that reuse that one connection in our example:import time, requests
from timeit import default_timer as timer
from multiprocessing.dummy import Pool as ThreadPool
start3 = timer()
s = requests.Session()
payloads = []
for x in range(0,100):
payloads.append('https://en.wikipedia.org/wiki/' + str(x))
def worker6(payload):
r = s.get(payload)
if 'Luftballons' in r.text:
print (timer() - start3)
pool = ThreadPool(5)
results = pool.map(worker6, payloads)
pool.close()
pool.join()
Execution time: 0.93794298172. Very fast. Under the same conditions, this will stomp all over Burp; and pretty much anything else you can expect to make without considerable effort.
Room for improvement? sure!:
So the main problem with Request, and almost all http libraries, is that they donât support HTTP Pipelining. HTTP Pipelining is the idea of firing multiple requests through a single TCP connection, without having to wait for each response synchronously. If you look at our last code snippet, it looks like thats exactly what we are doing, but unfortunately weâre not. The Requests library actually locks a TCP connection until it has fully read the response content from the last request. The main reason we are able to get such a big perfomance boost from threads, is that we already have our next requests queued up on the connection and ready to fire the moment itâs available to use by the next worker thread. Weâve effectively just minimised the delay this connection sharing was causing us. Pipelining has its own issues, for example its not supported on all webserver, and connection issues are much harder to deal with if you have bits of multiple requests already in transit.
To get around these limitations but still reap the performance of asynchronous requests, we can do one obvious thing: increase the amount of connections.
We can wrap our last code snippet into 5 threads of its own. This gives us 5 TCP connections, each working as fast as possible to synchronously fire out requests. This is as close we can easily get to HTTP pipelining, but is arguably a far more stable attack.
If you really want to play with true pipelining, take a look at Rubyâs em-http-request.
Hopefully this gives you some ideas of how to script basic, yet efficient, brute-force attacks. Donât assume that because a tool already exists for a job that it means it does it best. As a pen-tester, time is precious and we need to spend it wisely.
-Hiburn8
Note: So burp has no time measurement feature in Intruder, so I created a hack to figure out roughly how fast burp is at making requests. Essentially, I created a jython plugin which registers an extension-generated payload for use in Intruder. When this plugin is called upon to create a payload, it returns an empty string payload, but logs the current time in microseconds to the plugin console. This doesnât give us the exact that time requests were issued or completed⌠but does help us figure out how fast burp is generating requests to send, which, alone, is twice as slow as the last example here in all of my test cases.
More episodes of the podcast Rob Fuller's broadcasted articles on Inoreader
NTLMquic
30/04/2022
Mysteries of the Registry
30/04/2022
Managing Active Directory groups from Linux
17/01/2020
Searching Instagram â part 2
18/12/2019
Crack me if you can 2018 write-up
23/08/2018
Slack Notifications for Cobalt Strike
17/01/2017
Call for Papers Open
03/10/2016
ZARZA We are Zarza, the prestigious firm behind major projects in information technology.