Repository navigation
API
commix can be driven over HTTP instead of a command line. commixapi.py serves a small REST API in
which every scan is a task: it is created, given options by the names the command line knows
them under, started, and then read while it runs and after it ends.
Each task is run as a process of its own. A run is settled in state that is written once and read everywhere, so two of them inside one process would be one run with two sets of answers; a process apiece is what keeps several scans from becoming one.
python commixapi.py| Option | |
|---|---|
-s, --server
|
Run as a REST API server (the default). |
-c, --client
|
Run as a REST API client, against a server already serving. |
-H, --host
|
Address to listen on (Default: 127.0.0.1). |
-p, --port
|
Port to listen on (Default: 8775). |
--token |
Token a request has to carry (Default: one made up per run). |
--tasks-dir |
Directory to keep each task's options, output and report in. |
[08:11:53] [info] Serving the commix API at 'http://127.0.0.1:8911'.
[08:11:53] [info] Tasks are kept under '/home/user/commix-api/wiki'.
[08:11:53] [info] Send the token 's3cr3t' as the 'X-Api-Token' header.
Warning
The server runs scans on behalf of whoever can reach it. It listens on 127.0.0.1 for that
reason - a --host that answers the network hands command execution to it.
Every request carries the token as the X-Api-Token header. /version is the one address that
does not ask for it, so a client can find out what it is talking to before it authenticates.
curl -s -H "X-Api-Token: s3cr3t" http://127.0.0.1:8775/task/newEvery answer is JSON and says whether what was asked for happened.
{
"success": true,
"version": "v4.2.devXYZ",
"api": "1.0"
}Something the server understood but would not do is answered with 200 and "success": false,
carrying the reason - a wrong token (401), an address that is not served (404) and one asked
for with the wrong method (405) are the three that answer with a code of their own.
{
"success": false,
"message": "Unknown option 'nosuchthing'."
}Note
api is the version of this contract, not of commix. It is counted on its own, so a client can
tell whether it still knows how to talk to a newer commix.
| Method | Address | |
|---|---|---|
GET |
/version |
commix's version, and this contract's. |
GET |
/task/new |
Make a task, and hand back its id. |
GET |
/task/<id>/delete |
Forget a task, ending its scan if one is running. |
GET |
/option/<id>/list |
Every option of a task, with what it is set to. |
POST |
/option/<id>/get |
One option, named in {"option": "url"}. |
POST |
/option/<id>/set |
Set options, named as the command line names them. |
POST |
/scan/<id>/start |
Start the scan - any options in the body are set first. |
GET |
/scan/<id>/stop |
Ask the scan to stop, so that it writes down what it has. |
GET |
/scan/<id>/kill |
End the scan's process outright. |
GET |
/scan/<id>/status |
Where the run has got to. |
GET |
/scan/<id>/data |
The run's results, as they stand. |
GET |
/scan/<id>/log |
What the run has said, as {time, level, message}. |
GET |
/download/<id> |
Every file the task has to hand. |
GET |
/download/<id>/<path> |
One of them, as itself rather than as JSON. |
GET |
/admin/list |
Every task this server holds. |
GET |
/admin/flush |
Forget all of them. |
A task is made first, and answers with the id every other address is asked under.
curl -s -H "X-Api-Token: s3cr3t" http://127.0.0.1:8775/task/new{
"success": true,
"taskid": "93c54d9323c4dcba"
}Options are named the way the command line names them, with - read as _: url is --url,
flush_session is --flush-session, time_limit is --time-limit. A handful are kept under a
shorter name than they are spelled with - --user-agent as agent, --technique as tech - and
either name is taken, so nothing has to be known about which of the two this one is. They are
checked against commix's own parser, so a name it does not know is refused, and so is a value it
would not have taken.
curl -s -H "X-Api-Token: s3cr3t" -H "Content-Type: application/json" \
-d '{"url":"http://localhost:8081/scenarios/regular/GET/classic.php?addr=127.0.0.1","flush_session":true}' \
http://127.0.0.1:8775/option/93c54d9323c4dcba/setStarting the scan hands back the process it is running as. Options may be given here instead, which makes a scan two requests rather than three.
{
"success": true,
"engineid": 45671
}While it runs, status says so, and log hands back what it has said so far. Both answer the same
way after it ends, so a client polls one address throughout rather than two.
{
"success": true,
"status": "running",
"returncode": null
}curl -s -H "X-Api-Token: s3cr3t" "http://127.0.0.1:8775/scan/93c54d9323c4dcba/log?start=0&end=2"{
"success": true,
"log": [
{
"time": "08:12:01",
"level": "info",
"message": "Testing connection to the target URL."
},
{
"time": "08:12:05",
"level": "info",
"message": "Checking whether some kind of WAF/IPS is protecting the target."
}
]
}data is readable throughout as well - the report is written as the run goes, so findings appear
as they are confirmed rather than all at once at the end.
One of the four findings of that run is shown below; each also carries a reproduce field holding
the single request that brings it back.
{
"success": true,
"data": {
"target": "http://localhost:8081/scenarios/regular/GET/classic.php?addr=127.0.0.1",
"http_method": "GET",
"command": "/home/user/commix/commix.py -c /home/user/commix-api/122c078d2162c7f4/options.ini",
"started": "2026-10-08 08:12:08",
"status": "completed",
"findings": [
{
"parameter": "addr",
"http_method": "GET",
"technique": "Results-based",
"type": "Classic command injection",
"boundary": "';' separator, ' ' whitespace",
"payload": "QUE;echo USNTCB$((81+65))$(echo USNTCB)USNTCB;"
}
],
"finished": "2026-10-08 08:12:27",
"requests": 65,
"target_os": "Unix",
"waf_detected": false,
"evasion_applied": ""
},
"error": []
}A run over several targets - a bulk file, a proxy log, a sitemap or a crawl - carries one such
object per target under targets, with started, status, finished and requests standing for
the run as a whole. A run over one target keeps the flat shape above.
It is the same report --report-json
writes, so what a scan run from a command line hands back and what the API hands back cannot drift
apart. error carries what went wrong, which is what tells a run that could not reach its target
from one that reached it and found nothing - both end with no findings.
status is what the run itself wrote, except where it was given no chance to write anything.
not started |
The task exists, but nothing has been started under it. |
running |
The process is still going. |
completed |
It reached the end of what it was asked to do. |
time-limit |
--time-limit ended it, and it wrote down what it had. |
terminated |
/scan/<id>/stop ended it, and it wrote down what it had. |
failed |
It ended on an error - data carries which. |
killed |
/scan/<id>/kill ended the process outright, so it wrote nothing further. |
Stopping is worth preferring to killing: a stopped scan is asked to end and finishes its report first, while a killed one is gone before it can.
{
"success": true,
"status": "terminated",
"returncode": 1
}-c talks to a server already serving, from a terminal. It is the same addresses asked for the
same way, which is what makes a server worth having a look at before anything is written against
it.
python commixapi.py -c --token=s3cr3t[08:43:01] [info] Talking to commix v4.2.dev219 at 'http://127.0.0.1:8907'.
Available Commands
==================
Name Description
---- -----------
new Make a task and use it from here on
use <taskid> Work on a task that already exists
set <option> <value> Set an option on the task in use
options Show every option of the task in use that is set
start Start the task's scan
status Say where the scan has got to
data Show what the scan has found
log Show what the scan has said
stop Ask the scan to stop, keeping what it found
kill End the scan's process outright
list Show every task the server holds
flush Forget all of them
exit Leave
A task made with new is the one every command after it works on, until use takes another.
Values are read as what they look like, so set flush_session true sets a switch and
set time_limit 90 sets a number.
Note
The contract itself is written out in commixapi.yaml, an OpenAPI description of every address
on this page - enough for a client to be generated from rather than written.
/download/<id> says what the task has, and /download/<id>/<path> hands one of them back.
curl -s -H "X-Api-Token: s3cr3t" http://127.0.0.1:8775/download/93c54d9323c4dcba{
"success": true,
"files": [
{ "path": "console.log", "size": 5016 },
{ "path": "options.ini", "size": 2986 },
{ "path": "output/localhost_8081/logs.txt", "size": 2127 },
{ "path": "output/localhost_8081/session.db", "size": 16384 },
{ "path": "report.json", "size": 2631 }
]
}curl -s -OJ -H "X-Api-Token: s3cr3t" \
"http://127.0.0.1:8775/download/93c54d9323c4dcba/output/localhost_8081/logs.txt"This is the one address that answers with something other than JSON. The file arrives as itself,
under its own name, so anything a run writes - a session file to resume from, the traffic log
-t
keeps, a file read off the target - can be taken off the server that ran it.
Note
A path that resolves outside the task's own directory is refused however it is spelled, encoded or not. A task hands back what it produced, and nothing else the host happens to hold.
Every task has a directory of its own under --tasks-dir, holding the options it was started with,
the report it is writing, what it has printed, and whatever the run itself produced.
<tasks-dir>/<taskid>/
options.ini the options the run was started with
report.json the report, written as the run goes
console.log what the run has printed
output/ the run's own output directory
They outlive /task/<id>/delete, which forgets the task rather than erasing what it produced.
Note
- An option that acts on the host serving the API rather than on the target is refused:
--alertruns a command here,-creads a file here,--preprocessand--postprocessrun a script here, and--install,--updateand--purgechange this installation. A token for scanning somebody else's machine does not carry the right to run things on this one. -
--report-json,--output-dirand--batchare settled by the server for every task, and setting them is refused rather than quietly overridden, so that what a task says it was given is what it was run with. - A scan is given its options once, when it starts: setting one while it runs is refused rather than kept, because the running scan cannot be reached by it.
- Tasks outlive the server. One restarted over the same
--tasks-dirreads them back, so a task id still answers for what its run found and said. - A scan is always run with
--batch: there is nobody at the other end to answer a question. -
report_jsonandoutput_dirare settled by the server, and setting them has no effect.
At the right side panel, you can find detailed information about Commix Project.
- Usage - Exhaustive breakdown of all options and switches together with examples
- Techniques - Techniques supported by commix
- API - Driving commix over HTTP, a scan at a time
- Download and update - Keep your copy up-to-date
- Module development - Guide for extending commix with custom modules
- Third party libraries - Third-party components used by commix
- FAQ - Frequently Asked Questions
- License - Copyright information
- Usage examples - Real-world examples of using commix against vulnerable applications
- Filters bypass examples - Payloads and techniques used to evade input filters
- Getting shells - Getting an interactive, reverse or bind shell
- Presentations - Conference talks and public presentations featuring commix
- Screenshots - Visual examples of commix in action
- Third party references - References to commix in books, articles, research papers and blog posts
- Command injection testbeds - Deliberately vulnerable targets for safely testing commix