QuicDraw (H3)
-----------
QuicDraw(H3): HTTP/3 Fuzzing and Racing (Client)
-----------
_ _
(_) | | ______
__ _ _ _ _ ___ __| |_ __ __ ___ __ /\ /\___ /
/ _` | | | | |/ __/ _` | '__/ _` \ \ /\ / / / /_/ / |_ \
| (_| | |_| | | (_| (_| | | | (_| |\ V V / / __ / ___) |
\__, |\__,_|_|\___\__,_|_| \__,_| \_/\_/ \/ /_/ |____/
|_| _______
\ |QFS____| -------------------- HTTP/3
\ |_//
|_|
GitHub: https://github.com/cyberark/QuicDrawH3
License: Apache-2.0 License
Author: Maor Abutbul <CyberArk Labs>
-----------
QuicDraw is a security research tool designed for fuzzing and racing
HTTP/3 servers. QuicDraw implements the Quic-Fin-Sync on HTTP/3 (over
QUIC), for race-condition testing.
The tool was originally published as part of CyberArk Labs' research: "Racing and Fuzzing HTTP/3: Open-sourcing QuicDraw(H3)"
The research, has been presented at: BlackAlps, Nullcon (Goa), and BlueHatIL26.
TOC
- QuicDraw
- QuicDraw-UI
- Contributing
- Limitations
- Known issues
- License
- Contact
Main Features
- Implements the
Quic-Fin-Syncon HTTP3 (over QUIC), for race-condition testing. - Supports fuzzing multiple requests with the
FUZZand wordlist (-wargument) mechanisms. - Custom HTTP headers functionality (
-Hargument).- Note: Custom headers are converted to lowercase since we have seen issues with some server implementations.
- Supports SSLKEYLOGFILE (
-largument) for TLS decryption/inspection via packet analyzers such as Wireshark. - Based on aioquic (http3_client)
- aioquic is a library for the QUIC network protocol in Python.
- It features a minimal TLS 1.3 implementation, a QUIC stack, and an HTTP/3 stack.
Quick Start
Prerequisite:
- python >=3.9
- pip3
Install using pip
The easiest way to install QuicDraw is to run:
pip install quicdraw
Running (after pip install)
quicdraw -h
Install quicdraw-ui using pip (PyPi)
The easiest way to install QuicDraw-UI is to run:
pip install quicdraw[ui]
quicdraw-ui -h
Build and install locally by cloning the source (optional)
Build from source
If there are no wheels for your system or if you wish to build QuicDraw from source.
Clone the repository:
(You may prefer to do this within a Virtual Environment)
git clone https://github.com/cyberark/quicdrawh3.git
python3 -m build
pip install .\dist\quicdraw-<VERSION>.whl
pip install .\dist\quicdraw-<VERSION>.whl[ui] # with the GUI (dependencies)
Usage
Print Help
quicdraw -h
Normal HTTP/3 (over QUIC) Requests
An HTTP/3 GET Request
quicdraw <https://http3_server.com/path>
An HTTP/3 POST Request
HTTP POST requests are determined by using the -d argument followed by
the HTTP POST data to be sent.
quicdraw <https://http3_server.com/path> -d '{"key":"value"}'
Observability
QuicDraw offers several ways to inspect requests and responses, from quick console output to files consumable by other tools:
| Flag | Purpose |
|---|---|
-i, --include |
Prepend the HTTP response headers to the output (curl-style) |
-v / -vv |
Increase logging verbosity (-v debug, -vv also QUIC) |
-o, --output |
Write the raw response body to a file or stdout |
-l, --secrets-log |
Log TLS secrets to a file, for use with Wireshark |
Include response headers in the output -i
Use the -i/--include argument to print the HTTP response headers
alongside the response, mirroring curl -i. Each received response
header is printed to the console, and when combined with -o/--output
the headers are also prepended to the written body.
quicdraw <https://http3_server.com/path> -i
Without -i, only the response status line and body are surfaced; the
individual headers are still available at debug level via -v.
Verbose logging -v / -vv
Verbosity is cumulative — repeat the flag to increase detail:
-vsets the QuicDraw logger to DEBUG. It logs (prints) the request data to be sent, the full response headers, and the response content, along with the tool version. In the case of GET requests (no-dargument supplied), the request URL (:path) is logged (printed).-vvadditionally raises the underlying QUIC logger to DEBUG, so you also see low-level QUIC/HTTP-3 protocol events.
quicdraw <https://http3_server.com/path> -v # debug logging
quicdraw <https://http3_server.com/path> -vv # debug + QUIC internals
Note: -v is intended for interactive inspection — it interleaves the
response content with log lines. For scripting or piping the response
body, prefer -o/--output (below).
Write the response body to a file or stdout -o OUTPUT
Use the -o/--output argument to write the raw HTTP response body to a
file, mirroring curl's output behavior. Pass a filename to save the body,
or - to stream it to standard output (so it can be piped to other tools).
Unlike -v, which prints the response content interleaved with log lines,
-o writes only the response body, making it suitable for scripting and
piping.
Save the response body to a file:
quicdraw <https://http3_server.com/path> -o response.json
Stream the response body to stdout and pipe it to another tool:
quicdraw <https://http3_server.com/path> -o - | grep -i "token"
Notes:
-owrites the response body only. Combine it with-i/--includeto prepend the response headers (curl-style).- When multiple requests/streams are sent (e.g. with
-tror-w), all response bodies are concatenated into the single output target. -o/--outputwrites a single response target (file or stdout), whereas--output-dirwrites each downloaded stream to its own file in a directory. They are independent options.
Log TLS Secrets to file -l SECRETS_LOG
Use the -l/--secrets-log argument to log the TLS secrets to a file
(SSLKEYLOGFILE), for use with a packet analyzer such as Wireshark.
quicdraw <https://http3_server.com/path> -l /path/to/secrets.log
To inspect the traffic in Wireshark: Open Wireshark → Edit → Preferences
→ Protocols → TLS and set "(Pre)-Master-Secret log filename" to the full
path of secrets.log. Wireshark can then decrypt the captured QUIC/TLS
traffic.
Test Race-Conditions in HTTP/3 applications
-tr TOTAL_REQUESTS
To use the same request multiple times (using the Quic-Fin-Sync /
single-packet), use the -tr/--total-requests argument.
Note: If a WORDLIST (-w) argument is specified, this argument
(-tr TOTAL_REQUESTS) is overridden by the wordlist number of lines.
Racing example
Repeat the same request 12 times (-tr 12) (using Quic-Fin-Sync)
quicdraw <https://http3_server.com/path> -d '{"key":"value"}' -H 'Authorization: bearer eyJ...' -tr 12
Repeat the same request 12 times (-tr 12), use Quic-Fin-Sync and log (-l) TLS secrets
quicdraw <https://http3_server.com/path> -d '{"key":"value"}' -H 'Authorization: bearer eyJ...' -H 'content-type: application/json' -l /m2a/ssl_key_log_file.log -tr 12
Repeat the same request 12 times (-tr 12), use Quic-Fin-Sync, log (-l) TLS secrets, and print verbose (-v) output including HTTP response content
quicdraw <https://http3_server.com/path> -d '{"key": "value"}' -H 'Authorization: bearer eyJ...' -H 'content-type: application/json' -l /m2a/ssl_key_log_file.log -tr 12 -v
Fuzzing HTTP3 applications -w WORDLIST
Fuzzing in QuicDraw is based on a simple concept, like other web fuzzers
(Ffuf,
Wfuzz), go over the data section
(-d), and replace any reference to the FUZZ keyword with the value
given in the wordlist (-w) as the payload.
To define fuzzing, use the wordlist (-w/--wordlist) argument with
the FUZZ keyword either in the DATA (-d) section (POST-data fuzzing)
or in the request URL path (path fuzzing, when no -d is supplied).
Note: If the payload (-d) does not include the FUZZ keyword, the
same data will be sent according to the number of lines in the
wordlist file.
Fuzzing Example (POST data)
Use Quic-Fin-Sync, go over the data section (-d), and replace any reference to the FUZZ keyword with the value given in the wordlist file (-w) as the payload
quicdraw <https://http3_server.com/path> -w path/to/wordlist -d '{"example_key":"FUZZ"}'
Path Fuzzing Example (GET)
When no -d (POST data) argument is supplied, the FUZZ keyword is
substituted in the request URL path instead. For each word in the
wordlist (-w), a GET request is sent with FUZZ replaced by that word
— useful for endpoint/directory discovery.
quicdraw <https://http3_server.com/FUZZ> -w path/to/wordlist
For example, with a wordlist containing admin, login, and api, the
tool requests /admin, /login, and /api respectively.
Note: Per the Limitations, fuzzing is applied once.
If -d is supplied, QuicDraw assumes POST-data fuzzing and sends the
URL path as-is (any FUZZ in the path is left untouched).
QuicDraw-UI
-----------
QuicDraw-UI: HTTP/3 Request Editor - A GUI for QuicDraw(H3): HTTP/3 Fuzzing and Racing (Client)
-----------
_ _
(_) | | __ ______
__ _ _ _ _ ___ __| |_ __ __ ___ __ / / / / _/
/ _` | | | | |/ __/ _` | '__/ _` \ \ /\ / / _____ / / / // /
| (_| | |_| | | (_| (_| | | | (_| |\ V V / /____// /_/ // /
\__, |\__,_|_|\___\__,_|_| \__,_| \_/\_/ \____/___/
|_| _______
\ |QFS____| -------------------- HTTP/3
\ |_//
|_|
Install quicdraw-ui (using pip PyPi)
The easiest way to install QuicDraw-UI is to run:
pip install quicdraw[ui]
quicdraw-ui -h
Example 1: Simple HTTP/3 Request
Send a basic request to an HTTP/3 server:
quicdraw-ui https://www.cyberark.com
HTTP/3 Request Editor
Advanced Tab
The following options can be set by the advanced tab
Option Description
-l, --secrets-log TLS secrets file (for Wireshark)
-v, --verbose Verbose output
Results Tab:
Example 2: Fuzzing with a Wordlist
To fuzz an HTTP/3 endpoint, you need:
- A wordlist file (
-w) - contains payloads to test (one per line) - The
FUZZkeyword in your data - gets replaced by each wordlist entry
quicdraw-ui https://www.cyberark.com -w path/to/wordlist -d '{"example_key":"FUZZ"}'
The FUZZ keyword in {"example_key":"FUZZ"} will be replaced with
each line from your wordlist file.
Command-Line Options
QuicDraw-UI parameters are imported to the UI.
Option Description
-d, --data HTTP POST data (use FUZZ for wordlist
substitution)
-H, --header Custom header (repeatable)
-b, --cookie Custom cookie header
-w, --wordlist Fuzzing wordlist file
-tr, --total-requests Number of concurrent requests (race testing)
-l, --secrets-log TLS secrets file (for Wireshark)
-v, --verbose Verbose output
Note: "copy-as-curl compatible" meaning common curl arguments (-d,-H,-b) are supported by QuicDraw-UI.
Testing
QuicDraw includes a comprehensive pytest-based test suite covering unit, integration, and end-to-end scenarios.
Running Tests
Running Tests
Install Test Dependencies
# Install project with test dependencies
pip install -e ".[test]"
# Or install test dependencies separately
pip install pytest pytest-asyncio pytest-cov pytest-mock
Run All Tests
# Run all tests
pytest
# Run with verbose output
pytest -v
# Run tests with coverage report
pytest --cov=src/quicdraw --cov-report=html --cov-report=term-missing
# View coverage report (after running with --cov-report=html)
open htmlcov/index.html # macOS
xdg-open htmlcov/index.html # Linux
Run Specific Test Categories
# Run only unit tests (fast, no I/O)
pytest tests/unit/ -v
# Run only integration tests
pytest tests/integration/ -v
# Run only end-to-end tests
pytest tests/e2e/ -v
# Run tests with specific markers
pytest -m unit # Unit tests only
pytest -m integration # Integration tests only
pytest -m "not slow" # Skip slow tests
Run Specific Test Files or Functions
# Run a specific test file
pytest tests/unit/test_validation.py -v
# Run a specific test function
pytest tests/unit/test_validation.py::test_non_negative_float_valid -v
# Run tests matching a pattern
pytest -k "validation" -v
Test Structure
tests/
├── unit/ # Fast unit tests (no I/O dependencies)
├── integration/ # Integration tests with mocked I/O
├── e2e/ # End-to-end workflow tests
└── ui/ # GUI component tests (optional)
Contributing
We welcome contributions of all kinds to this repository. For instructions on how to get started and descriptions of our development workflows, please see our contributing guide
Limitations
- The
Quic-Fin-Syncis mostly effective in POST requests (using the-dargument).- GET requests will benefit from the mechanism, but according to our tests, only a few requests "fit" on a single QUIC packet.
- The fuzzing mechanism (
FUZZand--wordlist/-w) only works in POST messages data or in the GET request URL (:path) argument. - Currently, the fuzzing mechanism only works once, meaning if the
data argument is supplied (
-d), we assume fuzzing on the POST data, supplying theFUZZkeyword in the URL (:path) will result in sending the URL (:path) as-is (including theFUZZkeyword). - We do not support multiple different domains in the current version. (For different paths, you can use the FUZZ keyword in the URL's path part)
Known issues
- None
License
Copyright (c) 2025 CyberArk Software Ltd. All rights reserved This
repository is licensed under the Apache-2.0 License - see
LICENSE for more details.
Contact
Feel free to contact us via GitHub issues if you have any feature requests or project issues.
Contact Via LinkedIn
Metadata
Release files for quicdraw 0.9.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| quicdraw-0.9.2.tar.gz | 91.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| quicdraw-0.9.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 178.8 kB
Release files / quicdraw-0.9.2.tar.gz
| Download URL | quicdraw-0.9.2.tar.gz |
|---|---|
| Size | 91.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fc80db955081c63e3bf4a8bd3e84c5653590701e7facf7d38fb3f8adb218bc8b
|
|
BLAKE2b-256 checksum How to use checksums |
b168564561d1bfd13261763288bf78e9ddc5430264cacb8582cbbf5b2c546528
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.12.10
|
Release files / quicdraw-0.9.2-py3-none-any.whl
| Download URL | quicdraw-0.9.2-py3-none-any.whl |
|---|---|
| Size | 87.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
168672f2060e8d9f17e23551b0dc947fab185e9b778e9e27e4046b3cbfbce172
|
|
BLAKE2b-256 checksum How to use checksums |
12a4c6ee260f8c1f512e87b7bd71a7f4b2cf28ff5f36898d5e4c0973a88d2c26
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.12.10
|