2. Cann0n Usage - A Quick Tutorial¶
The following examples will explain everything you need to know to test an HTTP server.
Don’t worry if you don’t know completely what’s going on here, you don’t have to learn Python to write tests (but you should because Python is absolutely amazing).
2.1. Preparations¶
Create a new text file, name it pwn.py and open it in your favourite code editor.
To execute the python script you just have to open a terminal and change to the directory where pwn.py is located. The following command will then execute your script.
python pwn.py
2.2. Command Line Arguments¶
First of all, we have to import a few things from the module
pewpewlaz0rt4nk
. In this tutorial we will only use the classes
Laz0rCannon
and Beam
of this module.
We also need the module sys
to evaluate arguments passed as command line
arguments. In case you’re using Python 2, you also need to include the
unicode_literals
module to treat all strings as unicode by default.
from __future__ import unicode_literals # Only for Python 2
import sys
from pewpewlaz0rt4nk import Laz0rCannon, Beam
Now, we set the default variables host and port. These will be used if no or more than two arguments have been passed.
host = 'localhost'
port = 31337
There is no point in having default variables if we don’t overwrite them on a specific condition, right?
The following code will check whether exactly three command line arguments exist. If they exist, host and port will be unpacked from a list of arguments, thus overwriting host and port in this process.
# Overwrite host and port if command line arguments have been passed
if len(sys.argv) == 3:
# Unpack the list sys.argv, ignore the first item but extract
# the following two
_, host, port = sys.argv
port = int(port)
2.3. Initialising the Cann0n¶
Evaluating the command line arguments in the previous section Command Line Arguments was just for convenience.
As the sections build upon one another we need the code of the previous section as basis before we can proceed:
from __future__ import unicode_literals # Only for Python 2
import sys
from pewpewlaz0rt4nk import Laz0rCannon, Beam
host = 'localhost'
port = 31337
# Overwrite host and port if command line arguments have been passed
if len(sys.argv) == 3:
# Unpack the list sys.argv, ignore the first item but extract
# the following two
_, host, port = sys.argv
port = int(port)
Now, we create an instance of class Laz0rCannon
which provides the
main functionality to test an HTTP server.
# Initialise cannon
cannon = Laz0rCannon(host=host, port=port)
The host=host
and port=port
part might look odd to you. These are called
Keyword Arguments.
One of the cool things about keyword arguments is that they do not have to be
passed in a specific order. Therefore, Laz0rCannon(port=port, host=host)
would do exactly the same.
2.4. A Cann0n Beam¶
Now, we will create a beam (test case) for the laz0r cannon. A test consists of both an HTTP request and the expected HTTP response.
We call this class a Beam because Pewpewlaz0rt4nks shoot laz0r beams to pulverize vulnerable servers (and your friend’s self-esteem).
2.4.1. Request¶
An HTTP request is a simple string containing a valid or invalid HTTP request. For example, to test a specific feature like authorisation or to check whether an expected status code was returned.
Due to the fact that HTTP requests use new lines to separate headers, you have
to specify them in your request. You can do this by using the escape sequence
\r\n
(or just \n
if you want to provoke a bad request).
A valid request could look like this:
request = 'GET /path/to/file HTTP/1.1\r\nHost: localhost\r\n\r\n'
2.4.2. Response¶
Note
Read this section thoroughly because it contains vital information about the HTTP response validation mechanism. Keep these mechanisms in mind while writing beams.
An expected HTTP response is a list of strings containing valid responses. The beginning of each line of the actual response will be compared against the current line of the expected response.
In addition to that, the order of the actual response lines has to be exactly the same as the order of the expected response lines but the amount of actual response lines may be longer.
The following three sections will explain these mechanism with examples.
2.4.2.1. Response line comparison¶
Let’s define an expected response line as HTTP/1.0 400 Bad Request
.
A response line is valid if the actual response line...
- is exactly
HTTP/1.0 400 Bad Request
. - begins with the same as above but is longer, like
HTTP/1.0 400 Bad Request Yes Indeed Very Bad Request
.
A response line is invalid if the actual response line...
- is shorter than the expected response, like
HTTP/1.0 400 Bad
. - does not match against the expected response, like
HTTP/1.0 404 Not Found
.
2.4.2.2. Response order comparison¶
Let’s define the expected response as the following:
response = ['HTTP/1.0 200', 'Date: ', 'Server: Droelfundzwuenfzig Server']
A valid actual response could look like this:
HTTP/1.0 200 OK
Date: Tue, 01 April 2014 22:38:34 GMT
Server: Droelfundzwuenfzig Server (version droelf*zwuenf+pi)
Content-Type: text/html
...
Note that the actual response contains more headers than we have defined. This will still be interpreted as a valid response.
But if the second and third header of the actual response would be reversed, the response would be invalid:
HTTP/1.0 200 OK
Server: Droelfundzwuenfzig Server droelf*zwuenf+pi
Date: Tue, 01 April 2014 22:38:34 GMT
Content-Type: text/html
...
2.4.2.3. Response length comparison¶
As seen in the section above, an actual response is still valid if it is longer than the expected response. But if the actual response is shorter, the response is invalid.
Let’s define the expected response as the following:
response = ['HTTP/1.0 200', 'Date: ', 'Server: ']
And let the actual response look like this:
HTTP/1.0 200 OK
Date: Tue, 01 April 2014 22:38:34 GMT
As you can see, although the first response lines are both valid, the third line is missing in the actual response. The result is an invalid response.
2.4.3. Creating a Beam¶
Now that we have understood how a request looks like and how the mechanism behind the response comparison works, we can write our first test case.
The following code creates an instance of class Beam
that awaits two
keyword arguments request and response. We have already learned how to
define these, so this should be an easy task.
# Append beams to the cannon
cannon += Beam(
request='GET /index.html HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n',
response=['HTTP/1.1 200']
)
This example represents a valid request as long as the file index.html actually exists in the HTTP server’s document root.
Have you noticed the curly bracket enclosed part {host}
in the request
string? This is called a replacement field and will be automatically replaced
by the keyword argument we have passed to the Laz0rCannon
.
You can learn more about these fields in the section Replacement Fields.
Here is another example.
cannon += Beam(
request='SHRED /index.html HTTP/1.1\r\nHost: {host}\r\n\r\n',
response=['HTTP/1.1 501']
)
Notice the difference? Yep, SHRED... I doubt there is an implementation of that request method. Therefore, we expect status 501 Not Implemented in the response.
Of course, even more beams can be added in this manner.
2.5. Pewpew!¶
Alright, the Laz0rCannon
has been idle long enough. It’s time to
fire our laz0r beams! The pewpew process can be started like this:
# PEWPEW!
cannon.pewpew()
After executing the complete code of this tutorial you should see a self-explanatory summary of the test cases.
It is possible to call pewpew multiple times. To reset the statistics and let the laz0r cool down a bit you can call reset on the cannon. The following example will show you how to use both methods in a loop.
# Massive PEWPEW!
for _ in range(100):
cannon.pewpew()
cannon.reset()
Now, you can start pwning your friend’s server. May the pewpew be with you!
2.6. Tutorial Code¶
The complete code of this tutorial.
from __future__ import unicode_literals # Only for Python 2
import sys
from pewpewlaz0rt4nk import Laz0rCannon, Beam
host = 'localhost'
port = 31337
# Overwrite host and port if command line arguments have been passed
if len(sys.argv) == 3:
# Unpack the list sys.argv, ignore the first item but extract
# the following two
_, host, port = sys.argv
port = int(port)
# Initialise cannon
cannon = Laz0rCannon(host=host, port=port)
# Append beams to the cannon
cannon += Beam(
request='GET /index.html HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n',
response=['HTTP/1.1 200']
)
cannon += Beam(
request='SHRED /index.html HTTP/1.1\r\nHost: {host}\r\n\r\n',
response=['HTTP/1.1 501']
)
# PEWPEW!
cannon.pewpew()