Quick Start

Installation

From PyPI:

pip install pyrace

From GitHub:

git clone https://github.com/llamasoft/pyrace.git
cd pyrace
python setup.py install

Basic Usage

To get pyrace up and running, all you need is a Driver and a work_queue.

  • The work_queue defines the Requests that each Thread should execute.
  • The Driver creates the Threads and drives them through their work_queues in a synchronized manner.

The following is taken from examples/basic.py:

import requests
import pyrace

# The `Driver` class creates `Thread`s, provides them with a `work_queue`,
# and drives them through their `work_queue`s in a synchronized manner.
driver = pyrace.Driver()

# The `work_queue` list represents the work that each `Thread` should execute.
# The elements of a `work_queue` can be `Request`s or callable functions.
request = requests.Request('GET', 'https://now.httpbin.org')
work_queue = [request]

# The `process` method creates the specified number of worker `Thread`s,
# provides them each with a copy of the `work_queue`, then drives them
# through the `work_queue` tasks in a synchronized manner.
# The method returns a list of the `Thread` instances used.
result_threads = driver.process(work_queue, thread_count = 3)

# The primary `Thread` attributes we are interested in are `response` and `all_responses`.
# The `response` attribute contains the most recently returned `Response`.
# The `all_responses` attribute is a list of all `Response`s, in order.
# For information on `Response` objects, see Requests documentation:
#   http://docs.python-requests.org/en/master/user/quickstart/#response-content
for thread in result_threads:
    json_response = thread.response.json()
    epoch_time = json_response['now']['epoch']
    print("{:.4f}".format(epoch_time))

Regarding Thread Count

Less is more when it comes to the number of parallel threads!

Using too many threads may yield inconsistent results. This library sends requests as precise as possible, but networks can interfere with when the requests are received by the target server. Typically, request timing follows a normal distribution; most requests arrive very close together, but a few may arrive early and a few may arrive late.

Knowing this, choose your thread_count value accordingly:

If the target is meant to allow an action once (e.g. sending items, deleting a post), then successful race condition exploitation only depends on the timing of the first few requests. Using a large number of threads increases the chance that a request to arrive too early, preventing the other requests from having an effect. In these situations, you should only use two or three threads.

If the target is meant to allow an action multiple times (e.g. returning search results, uploading a file) then successful race condition exploitation only depends on the timing of any two requests. A large number of parallel requests may result in variability, but due to the tendancy for requests to arrive in groups, the chance of any two requests colliding increases. In these situations, the number of threads depends mainly on your network connection. A thread count between four and ten is reasonable but more may be used if your network allows.

Note

Be careful to not use too many threads as your burst of requests may result in (or be viewed as) a denial of service attack.

Advanced Usage

Keyword Arguments

pyrace supports passing custom parameters to the Thread and BaseConnection classes by providing extra keyword arguments to the process() method. These keywords are passed to the classes via race_args.

do_eval:
Evaluates statements embedded in Request fields. See examples/eval.py for example usage.
save_sent_cookies:
Saves user-defined Request cookies to the current Session. See examples/cookies.py for example usage.
send_kwargs:
Dict of extra arguments to pass to requests.Session.send(). See linked documentation’s source code for all possible values. Common values include verify and proxies.
connect_mode:
Determine which IP address threads connect to for a given hostname. If a hostname resolves to multiple IP addresses, this allows you to specify that all threads should connect to the same, different, or random IP addresses. See example/timing.py for example usage.

For a full list of valid race_args keywords, or for additional details on the above keywords, see Thread and BaseConnection documentation.

Callbacks

In addition to Requests, the work_queue also supports callable functions. These functions are passed a single argument: the calling Thread instance (i.e. self). This gives the user total control over the Thread, allowing you to do such things as:

  • Dynamically adding new Requests to the Thread‘s work_queue based on previous Responses.
  • Modifying the Thread‘s Session to edit headers or cookies.
  • Literally anything a Thread can do, you can control using callable functions.

For an example of callbacks in action, see example/callbacks.py.