From e037fe05ff1f0c2893b3f51e06e0261ca4245d63 Mon Sep 17 00:00:00 2001
From: Aldo Cortesi pathod is developed by Aldo Cortesi.
- Offsets are calculated relative to the base message, before any injections or other
- transforms are applied. They have 3 flavors:
- Literal values are specified as a quoted strings:
- Either single or double quotes are accepted, and quotes can be escaped with backslashes
- within the string:
- Literal values can contain Python-style backslash escape sequences:
- You can load a value from a specified file path. To do so, you have to specify a
- _staticdir_ option to pathod on the command-line, like so:
-
- All paths are relative paths under this directory. File loads are indicated by starting
- the value specifier with the left angle bracket:
- The path value can also be a quoted string, with the same syntax as literals:
- An @-symbol lead-in specifies that generated data should be used. There are two components
- to a generator specification - a size, and a data type. By default pathod
- assumes a data type of "bytes".
- Here's a value specifier for generating 100 bytes:
-
- About
-
-
-
- Language Spec
- The mini-language at the heart of pathoc and pathod.
-
-Features
- OFFSET
-
-
-
-
-
- VALUE
-
- Literals
-
- "foo"
-
- 'fo\'o'
-
- 'foo\r\nbar'
-
- Files
-
- pathod -d ~/myassets
-
- <my/path
-
- <"my/path"
-
-
- Generated values
-
- @100
-
- You can use standard suffixes to indicate larger values. Here, for instance, is a - specifier for generating 100 megabytes: -
- -@100m- -
- Data is generated and served efficiently - if you really want to send a terabyte - of data to a client, pathod can do it. The supported suffixes are: -
- -b | -1024**0 (bytes) | -
k | -1024**1 (kilobytes) | -
m | -1024**2 (megabytes) | -
g | -1024**3 (gigabytes) | -
t | -1024**4 (terabytes) | -
- Data types are separated from the size specification by a comma. This specification - generates 100mb of ASCII: -
- -@100m,ascii- -
Supported data types are:
- -ascii | -All ASCII characters | -
ascii_letters | -A-Za-z | -
ascii_lowercase | -a-z | -
ascii_uppercase | -A-Z | -
bytes | -All 256 byte values | -
digits | -0-9 | -
hexdigits | -0-f | -
octdigits | -0-7 | -
punctuation | -
- !"#$%&\'()*+,-./:; - <=>?@[\\]^_`{|}~- |
-
whitespace | -
- \t\n\x0b\x0c\r and space- |
-
method:path:[colon-separated list of features]- - -
method | -
- - A VALUE specifying the HTTP method to - use. Standard methods do not need to be enclosed in quotes, while - non-standard methods can be specified as quoted strings. - - -- The special method ws creates a valid websocket upgrade - GET request, and signals to pathoc to switch to websocket recieve - mode if the server responds correctly. Apart from that, websocket - requests are just like any other, and all aspects of the request - can be over-ridden. - - |
- ||
hVALUE=VALUE | -- Set a header. - | -||
r | -- Set the "raw" flag on this response. Pathod will not calculate a Content-Length header - if a body is set. - | -||
cVALUE | -- A shortcut for setting the Content-Type header. Equivalent to h"Content-Type"=VALUE - | -||
uVALUE
- uSHORTCUT - |
-
-
- Set a User-Agent header on this request. You can specify either a complete
- VALUE, or a User-Agent shortcut:
-
-
|
- ||
bVALUE | -- Set the body. The appropriate Content-Length header is added automatically unless - the "r" flag is set. - | -||
sVALUE | -- An embedded Response specification, appended to the path of the request. - | -||
xINTEGER | -- Repeat this message N times. - | -||
dOFFSET | -- HTTP/1 only Disconnect after - OFFSET bytes. - | -||
iOFFSET,VALUE | -- HTTP/1 only Inject the specified - value at the offset. - | -||
pOFFSET,SECONDS | -- HTTP/1 only Pause for SECONDS - seconds after OFFSET bytes. SECONDS can be an integer or "f" to pause - forever. - | -
code:[colon-separated list of features]- -
code | -
- An integer specifying the HTTP response code. -- The special method ws creates a valid websocket upgrade - response (code 101), and moves pathod to websocket mode. Apart - from that, websocket responses are just like any other, and all - aspects of the response can be over-ridden. - - |
-
mVALUE | -- HTTP/1 only HTTP Reason message. - Automatically chosen according to the response code if not specified. - | -
hVALUE=VALUE | -- Set a header. - | -
r | -- Set the "raw" flag on this response. Pathod will not calculate a Content-Length header - if a body is set, or add a Date header to the response. - | -
lVALUE | -- A shortcut for setting the Location header. Equivalent to h"Location"=VALUE - | -
cVALUE | -- A shortcut for setting the Content-Type header. Equivalent to h"Content-Type"=VALUE - | -
bVALUE | -- Set the body. The appropriate Content-Length header is added automatically unless - the "r" flag is set. - | -
dOFFSET | -- HTTP/1 only Disconnect after - OFFSET bytes. - | -
iOFFSET,VALUE | -- HTTP/1 only Inject the specified - value at the offset. - | -
pOFFSET,SECONDS | -- HTTP/1 only Pause for SECONDS - seconds after OFFSET bytes. SECONDS can be an integer or "f" to pause - forever. - | -
wf:[colon-separated list of features]- - -
bVALUE | -- Set the frame payload. If a masking key is present, the value is encoded automatically. - | -
cINTEGER | -- - Set the op code. This can either be an integer from 0-15, or be one of the following - opcode names: text (the default), - continue, binary, close, ping, - pong. - - | -
dOFFSET | -- Disconnect after OFFSET bytes. - | -
[-]fin | -- Set or un-set the fin bit. - | -
iOFFSET,VALUE | -- Inject the specified value at the offset. - | -
kVALUE | -- Set the masking key. The resulting value must be exactly 4 bytes long. The special - form - knone specifies that no key should be set, even if the mask - bit is on. - | -
lINTEGER | -- Set the payload length in the frame header, regardless of the actual body length. - | -
[-]mask | -- Set or un-set the mask bit. - | -
pOFFSET,SECONDS | -- Pause for SECONDS seconds after OFFSET bytes. SECONDS can be an integer or "f" to - pause forever. - | -
rVALUE | -- Set the raw frame payload. This disables masking, even if the key is present. - | -
[-]rsv1 | -- Set or un-set the rsv1 bit. - | -
[-]rsv2 | -- Set or un-set the rsv2 bit. - | -
[-]rsv3 | -- Set or un-set the rsv3 bit. - | -
xINTEGER | -- Repeat this message N times. - | -
- Behind the pathod and pathoc command-line tools lurks pathod, - a powerful library for manipulating and serving HTTP requests and responses. - The canonical documentation for the library is in the code, and can be - accessed using pydoc. -
-- Pathoc is a perverse HTTP daemon designed to let you craft almost any conceivable - HTTP request, including ones that creatively violate the standards. HTTP requests - are specified using a - small, terse language, which pathod shares with - its server-side twin pathod. To view pathoc's complete - range of options, use the command-line help: -
- -pathoc --help- -
The basic pattern for pathoc commands is as follows:
- -pathoc hostname request [request ...]- -
- That is, we specify the hostname to connect to, followed by one or more requests. - Lets start with a simple example: -
- -- > pathoc google.com get:/ << 301 Moved Permanently: 219 bytes -- -
- Here, we make a GET request to the path / on port 80 of google.com. Pathoc's output - tells us that the server responded with a 301. We can tell pathoc to connect - using SSL, in which case the default port is changed to 443 (you can over-ride - the default port with the -p command-line option): -
- -- > pathoc -s google.com get:/ << 301 Moved Permanently: 219 bytes --
- There are two ways to tell pathoc to issue multiple requests. The first is to specify - them on the command-line, like so: -
- -- > pathoc google.com get:/ get:/ << 301 Moved Permanently: 219 bytes << - 301 Moved Permanently: 219 bytes -- -
- In this case, pathoc issues the specified requests over the same TCP connection - - so in the above example only one connection is made to google.com -
- -The other way to issue multiple requets is to use the -n flag:
- -- > pathoc -n 2 google.com get:/ << 301 Moved Permanently: 219 bytes << 301 - Moved Permanently: 219 bytes -- -
- The output is identical, but two separate TCP connections are made to the upstream - server. These two specification styles can be combined: -
- -- > pathoc -n 2 google.com get:/ get:/ << 301 Moved Permanently: 219 bytes << - 301 Moved Permanently: 219 bytes << 301 Moved Permanently: 219 bytes << - 301 Moved Permanently: 219 bytes -- -
Here, two distinct TCP connections are made, with two requests issued over each.
-- The combination of pathoc's powerful request specification language and a few of - its command-line options makes for quite a powerful basic fuzzer. Here's - an example: -
- -- > pathoc -e -I 200 -t 2 -n 1000 localhost get:/:b@10:ir,@1 -- -
- The request specified here is a valid GET with a body consisting of 10 random bytes, - but with 1 random byte inserted in a random place. This could be in the headers, - in the initial request line, or in the body itself. There are a few things - to note here: -
- -- Pathoc has a reasonably sophisticated suite of features for interacting with proxies. - The proxy request syntax very closely mirrors that of straight HTTP, which - means that it is possible to make proxy-style requests using pathoc without - any additional syntax, by simply specifying a full URL instead of a simple - path: -
- -> pathoc -p 8080 localhost "get:'http://google.com'"- -
- Another common use case is to use an HTTP CONNECT request to probe remote servers - via a proxy. This is done with the -c command-line option, - which allows you to specify a remote host and port pair: -
- -> pathoc -c google.com:80 -p 8080 localhost get:/- -
- Note that pathoc does not negotiate SSL without being explictly instructed - to do so. If you're making a CONNECT request to an SSL-protected resource, - you must also pass the -s flag: -
- -> pathoc -sc google.com:443 -p 8080 localhost get:/-
- One interesting feature of the Request sppecification language is that you can embed - a response specifcation in it, which is then added to the request path. Here's - an example: -
- -> pathoc localhost:9999 "get:/p/:s'401:ir,@1'"- -
- This crafts a request that connects to the pathod server, and which then crafts a - response that generates a 401, with one random byte embedded at a random - point. The response specification is parsed and expanded by pathoc, so you - see syntax errors immediately. This really becomes handy when combined with - the -e flag to show the expanded request: -
- -- > > pathoc -e localhost:9999 "get:/p/:s'401:ir,@1'" >> Spec: get:/p/:s'401:i15,\'o\':h\'Content-Length\'=\'0\'':h'Content-Length'='0' - << 401 Unoauthorized: 0 bytes- -
- Note that the embedded response has been resolved before being sent - to the server, so that "ir,@1" (embed a random byte at a random location) - has become "i15,\'o\'" (embed the character "o" at offset 15). You now have - a pathoc request specification that is precisely reproducable, even with - random components. This feature comes in terribly handy when testing a proxy, - since you can now drive the server repsonse completely from the client, and - have a complete log of reproducible requests to analyse afterwards. -
-- Pathod is a pathological HTTP daemon designed to let you craft almost any conceivable - HTTP response, including ones that creatively violate the standards. HTTP responses - are specified using a - small, terse language, which pathod shares with - its evil twin pathoc. -
- -To start playing with pathod, simply fire up the daemon:
- -./pathod- -
- By default, the service listens on port 9999 of localhost. Pathod's documentation - is self-hosting, and the pathod daemon exposes an interface that lets you - play with the specifciation language, preview what responses and requests - would look like on the wire, and view internal logs. To access all of this, - just fire up your browser, and point it to the following URL: -
- -http://localhost:9999- -
- The default crafting anchor point is the path /p/. Anything after - this URL prefix is treated as a response specifier. So, hitting the following - URL will generate an HTTP 200 response with 100 bytes of random data: -
- -http://localhost:9999/p/200:b@100- -
- See the language documentation to get (much) - fancier. The pathod daemon also takes a range of configuration options. To - view those, use the command-line help: -
- -./pathod --help- -
- Pathod automatically responds to both straight HTTP and proxy requests. For proxy - requests, the upstream host is ignored, and the path portion of the URL is - used to match anchors. This lets you test software that supports a proxy - configuration by spoofing responses from upstream servers. -
- -- By default, we treat all proxy CONNECT requests as HTTPS traffic, serving the response - using either pathod's built-in certificates, or the cert/key pair specified - by the user. You can over-ride this behaviour if you're testing a client - that makes a non-SSL CONNECT request using the -C command-line option. -
-- Anchors provide an alternative to specifying the response in the URL. Instead, you - attach a response to a pre-configured anchor point, specified with a regex. - When a URL matching the regex is requested, the specified response is served. -
- -./pathod -a "/foo=200"- -
- Here, "/foo" is the regex specifying the anchor path, and the part after the "=" - is a response specifier. -
-- There are two operators in the language that - load contents from file - the + operator to load an entire request - specification from file, and the > value specifier. In pathod, - both of these operators are restricted to a directory specified at startup, - or disabled if no directory is specified:
-./pathod -d ~/staticdir"-
- Pathod uses the non-standard 800 response code to indicate internal errors, to distinguish - them from crafted responses. For example, a request to: -
- -http://localhost:9999/p/foo- -
- ... will return an 800 response because "foo" is not a valid page specifier. -
-- pathod exposes a simple API, intended to make it possible to drive and inspect the - daemon remotely for use in unit testing and the like. -
- -- /api/clear_log - | -- A POST to this URL clears the log buffer. - | -
- /api/info - | -- Basic version and configuration info. - | -
- /api/log - | -
- Returns the current log buffer. At the moment the buffer size is 500 entries - when
- the log grows larger than this, older entries are discarded.
- The returned data is a JSON dictionary, with the form:
-
- { 'log': [ ENTRIES ] }You can preview the JSON data - returned for a log entry through the built-in web interface. - |
-
The pathod.test module is a light, flexible testing layer for HTTP clients. - It works by firing up a Pathod instance in a separate thread, letting you use - Pathod's full abilities to generate responses, and then query Pathod's internal - logs to establish what happened. All the mechanics of startup, shutdown, finding - free ports and so forth are taken care of for you. -
- -The canonical docs can be accessed using pydoc:
- -pydoc pathod.test- -
- The remainder of this page demonstrates some common interaction patterns using - nose. These examples are - also applicable with only minor modification to most commonly used Python testing - engines. -
- -The easiest way to install pathod is to use pip:
- -pip install pathod- -
- This will automatically pull in all the dependencies, and you should be good to go. -
-You can find the project source on GitHub:
- - - -Please also use the github issue tracker to report bugs.
-import requests
-from pathod import test
-
-
-def test_simple():
- """
- Testing the requests module with
- a pathod context manager.
- """
- # Start pathod in a separate thread
- with test.Daemon() as d:
- # Get a URL for a pathod spec
- url = d.p("200:b@100")
- # ... and request it
- r = requests.put(url)
-
- # Check the returned data
- assert r.status_code == 200
- assert len(r.content) == 100
-
- # Check pathod's internal log
- log = d.last_log()["request"]
- assert log["method"] == "PUT"
-
import requests
-from pathod import test
-
-
-class Test:
-
- """
- Testing the requests module with
- a pathod instance started for
- each test.
- """
-
- def setUp(self):
- self.d = test.Daemon()
-
- def tearDown(self):
- self.d.shutdown()
-
- def test_simple(self):
- # Get a URL for a pathod spec
- url = self.d.p("200:b@100")
- # ... and request it
- r = requests.put(url)
-
- # Check the returned data
- assert r.status_code == 200
- assert len(r.content) == 100
-
- # Check pathod's internal log
- log = self.d.last_log()["request"]
- assert log["method"] == "PUT"
-
import requests
-from pathod import test
-
-
-class Test:
-
- """
- Testing the requests module with
- a single pathod instance started
- for the test suite.
- """
- @classmethod
- def setUpAll(cls):
- cls.d = test.Daemon()
-
- @classmethod
- def tearDownAll(cls):
- cls.d.shutdown()
-
- def setUp(self):
- # Clear the pathod logs between tests
- self.d.clear_log()
-
- def test_simple(self):
- # Get a URL for a pathod spec
- url = self.d.p("200:b@100")
- # ... and request it
- r = requests.put(url)
-
- # Check the returned data
- assert r.status_code == 200
- assert len(r.content) == 100
-
- # Check pathod's internal log
- log = self.d.last_log()["request"]
- assert log["method"] == "PUT"
-
- def test_two(self):
- assert not self.d.log()
-
Crafted malice for tormenting HTTP clients and servers
- - -A perverse HTTP client.
- - {% include "request_previewform.html" %} -pip install pathod-
id | -method | -path | -|||
---|---|---|---|---|---|
ERROR: {{ i["msg"] }} | - {% else %} -{{ i["id"] }} | -{{ i["request"]["method"] }} | -{{ i["request"]["path"] }} | - {% endif %} -
- {{ alog }} --{% endblock %} diff --git a/pathod/templates/request_preview.html b/pathod/templates/request_preview.html deleted file mode 100644 index 25d73679..00000000 --- a/pathod/templates/request_preview.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "frame.html" %} {% block body %} -
{{ syntaxerror }}
-{{ marked }}-
{{ error }}
-{{ output }}-
Note: pauses are skipped when generating previews!
-- Check out the complete language docs. Here are - some examples to get you started: -
- -get:/ | -Get path / | -
get:/:b@100 | -100 random bytes as the body | -
get:/:h"Etag"="';drop table browsers;" | -Add a header | -
get:/:u"';drop table browsers;" | -Add a User-Agent header | -
get:/:b@100:dr | -Drop the connection randomly | -
- - | -- |
get:/:b@100,ascii:ir,@1 | -100 ASCII bytes as the body, and randomly inject a random byte | -
ws:/ | -Initiate a websocket handshake. | -
{{ syntaxerror }}
-{{ marked }}-
{{ error }}
-{{ output }}-
Note: pauses are skipped when generating previews!
-- Check out the complete language docs. Here are - some examples to get you started: -
- -200 | -A basic HTTP 200 response. | -
200:r | -A basic HTTP 200 response with no Content-Length header. This will - hang. - | -
200:da | -Server-side disconnect after all content has been sent. | -
200:b@100 | -- 100 random bytes as the body. A Content-Lenght header is added, so the disconnect - is no longer needed. - | -
200:b@100:h"Etag"="';drop table servers;" | -Add a Server header | -
200:b@100:dr | -Drop the connection randomly | -
200:b@100,ascii:ir,@1 | -100 ASCII bytes as the body, and randomly inject a random byte | -
200:b@1k:c"text/json" | -1k of random bytes, with a text/json content type | -
200:b@1k:p50,120 | -1k of random bytes, pause for 120 seconds after 50 bytes | -
200:b@1k:pr,f | -1k of random bytes, but hang forever at a random location | -
- 200:b@100:h@1k,ascii_letters='foo' - | -- 100 ASCII bytes as the body, randomly generated 100k header name, with the value - 'foo'. - | -