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()