openapi: 3.0.3 info: title: commix REST API version: "1.0" description: | The API served by `commixapi.py`, implemented in `src/utils/api.py`. A scan is a task: created, given options under the names the command line knows them by, started, then read while it runs and after it ends. Each task is run as a process of its own, started from a configuration file - the same file `-c` loads. Notes for implementers: * Options are the destinations of commix's own command-line options, with `-` read as `_`: `url` is `--url`, `flush_session` is `--flush-session`. A name the parser does not know is refused rather than kept. * `report_json`, `output_dir` and `batch` are settled by the server, and setting them is refused rather than ignored. * Options that act on the host serving the API rather than on the target are refused too: `--alert`, `-c`, `--save`, `--preprocess`, `--postprocess`, `--install`, `--update`, `--purge`, and the switches that are not scans at all. * Something the server understood but would not do answers `200` with `success: false` and a `message`. Only authentication (`401`), an address that is not served (`404`), one asked for with the wrong method (`405`) and an unhandled failure (`500`) answer with a code of their own. * `/scan/{taskid}/data` hands back the `--report-json` file the run is writing, so a run started from a command line and one started here cannot answer differently. license: name: GNU General Public License v3 (GPLv3) url: https://github.com/commixproject/commix/blob/master/LICENSE.txt contact: name: commix project url: https://commixproject.com externalDocs: description: The API, in the user's manual url: https://github.com/commixproject/commix/wiki/API servers: - url: http://127.0.0.1:8775 description: A local commixapi.py server security: - apiToken: [] tags: - name: Version description: What the server is - name: Tasks description: Making and forgetting tasks - name: Options description: Reading and settling a task's options - name: Scans description: Running a task, and reading it while it runs - name: Files description: What a task produced - name: Admin description: Every task at once paths: /version: get: tags: [Version] operationId: getVersion summary: commix's version, and this contract's description: >- The one address that is served without a token, so that a client can find out what it is talking to before it authenticates. `api` is counted apart from `version`, so a client can tell whether it still knows how to talk to a newer commix. security: [] responses: "200": description: The versions. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: version: { type: string, example: "v4.2.dev219" } api: { type: string, example: "1.0" } /task/new: get: tags: [Tasks] operationId: newTask summary: Make a task responses: "200": description: The task's id. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: taskid: { type: string, example: "93c54d9323c4dcba" } "401": { $ref: "#/components/responses/Unauthorized" } /task/{taskid}/delete: get: tags: [Tasks] operationId: deleteTask summary: Forget a task, ending its scan if one is running description: >- What the task produced is left on disk; it is the task that is forgotten, not its output. parameters: [ { $ref: "#/components/parameters/TaskId" } ] responses: "200": { $ref: "#/components/responses/Plain" } "401": { $ref: "#/components/responses/Unauthorized" } /option/{taskid}/list: get: tags: [Options] operationId: listOptions summary: Every option of a task, with what it is set to parameters: [ { $ref: "#/components/parameters/TaskId" } ] responses: "200": description: The options. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: options: type: object additionalProperties: true "401": { $ref: "#/components/responses/Unauthorized" } /option/{taskid}/get: post: tags: [Options] operationId: getOption summary: One option, by name parameters: [ { $ref: "#/components/parameters/TaskId" } ] requestBody: required: true content: application/json: schema: type: object required: [option] properties: option: { type: string, example: "url" } responses: "200": description: The option, under its own name. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object additionalProperties: true "401": { $ref: "#/components/responses/Unauthorized" } /option/{taskid}/set: post: tags: [Options] operationId: setOptions summary: Set options on a task description: >- Every option in the body is settled, in the order given. A name the parser does not know ends the request, and the ones before it stay set. parameters: [ { $ref: "#/components/parameters/TaskId" } ] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/Options" responses: "200": { $ref: "#/components/responses/Plain" } "401": { $ref: "#/components/responses/Unauthorized" } /scan/{taskid}/start: post: tags: [Scans] operationId: startScan summary: Start the task's scan description: >- Options in the body are settled first, which makes a scan two requests rather than three. One of `url`, `logfile`, `bulkfile`, `requestfile`, `sitemap_url` or `openapi` has to be set by then, and a scan already running is not started again. parameters: [ { $ref: "#/components/parameters/TaskId" } ] requestBody: required: false content: application/json: schema: $ref: "#/components/schemas/Options" responses: "200": description: The process the scan is running as. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: engineid: { type: integer, example: 45671 } "401": { $ref: "#/components/responses/Unauthorized" } /scan/{taskid}/stop: get: tags: [Scans] operationId: stopScan summary: Ask the scan to stop description: >- The run is asked to end and finishes its report first, so what it found up to then is kept. Worth preferring to `/kill`, which does not. parameters: [ { $ref: "#/components/parameters/TaskId" } ] responses: "200": description: Whether there was a running scan to stop. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: stopped: { type: boolean } "401": { $ref: "#/components/responses/Unauthorized" } /scan/{taskid}/kill: get: tags: [Scans] operationId: killScan summary: End the scan's process outright description: >- The process is given no chance to write anything further, so its report stops where it was. parameters: [ { $ref: "#/components/parameters/TaskId" } ] responses: "200": description: Whether there was a running scan to kill. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: killed: { type: boolean } "401": { $ref: "#/components/responses/Unauthorized" } /scan/{taskid}/status: get: tags: [Scans] operationId: scanStatus summary: Where the run has got to parameters: [ { $ref: "#/components/parameters/TaskId" } ] responses: "200": description: The status, and the code the process ended on where it has ended. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: status: type: string enum: [not started, running, completed, time-limit, terminated, failed, killed] returncode: type: integer nullable: true "401": { $ref: "#/components/responses/Unauthorized" } /scan/{taskid}/data: get: tags: [Scans] operationId: scanData summary: The run's results, as they stand description: >- Readable throughout: the report is written as the run goes, so findings appear as they are confirmed. `error` is 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. parameters: [ { $ref: "#/components/parameters/TaskId" } ] responses: "200": description: The report. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: data: { $ref: "#/components/schemas/Report" } error: type: array items: { $ref: "#/components/schemas/Issue" } "401": { $ref: "#/components/responses/Unauthorized" } /scan/{taskid}/log: get: tags: [Scans] operationId: scanLog summary: What the run has said parameters: - { $ref: "#/components/parameters/TaskId" } - name: start in: query required: false schema: { type: integer } description: First line to hand back. - name: end in: query required: false schema: { type: integer } description: Line to stop before. responses: "200": description: The lines. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: log: type: array items: { $ref: "#/components/schemas/LogLine" } "401": { $ref: "#/components/responses/Unauthorized" } /download/{taskid}: get: tags: [Files] operationId: listFiles summary: Every file the task has to hand description: >- The options it was started with, the report, what it printed, and whatever the run itself wrote - each by a path relative to the task's own directory, which is the path `/download/{taskid}/{path}` takes. parameters: [ { $ref: "#/components/parameters/TaskId" } ] responses: "200": description: The files, by path. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: files: type: array items: type: object properties: path: { type: string, example: "output/localhost_8081/logs.txt" } size: { type: integer, example: 2127 } "401": { $ref: "#/components/responses/Unauthorized" } /download/{taskid}/{path}: get: tags: [Files] operationId: downloadFile summary: One of those files, as itself description: >- The only address that answers with something other than JSON. A path that resolves outside the task's own directory is refused, however it is spelled. parameters: - { $ref: "#/components/parameters/TaskId" } - name: path in: path required: true schema: { type: string } description: The file's path, as the listing above gives it. responses: "200": description: The file. headers: Content-Disposition: schema: { type: string } description: 'attachment; filename=""' content: application/octet-stream: schema: { type: string, format: binary } "401": { $ref: "#/components/responses/Unauthorized" } "404": description: No such task, or no such file in it. content: application/json: schema: $ref: "#/components/schemas/Failure" /admin/list: get: tags: [Admin] operationId: listTasks summary: Every task this server holds responses: "200": description: The tasks, by id. content: application/json: schema: allOf: - $ref: "#/components/schemas/Success" - type: object properties: count: { type: integer } tasks: type: object additionalProperties: type: object properties: status: { type: string } returncode: { type: integer, nullable: true } "401": { $ref: "#/components/responses/Unauthorized" } /admin/flush: get: tags: [Admin] operationId: flushTasks summary: Forget every task, ending any scan still running responses: "200": { $ref: "#/components/responses/Plain" } "401": { $ref: "#/components/responses/Unauthorized" } components: securitySchemes: apiToken: type: apiKey in: header name: X-Api-Token description: >- The token the server was started with, or the one it made up and printed as it started. parameters: TaskId: name: taskid in: path required: true schema: type: string pattern: "^[0-9a-f]+$" description: The id `/task/new` handed back. responses: Plain: description: Whether what was asked for happened. content: application/json: schema: oneOf: - $ref: "#/components/schemas/Success" - $ref: "#/components/schemas/Failure" Unauthorized: description: The token was wrong or missing. content: application/json: schema: $ref: "#/components/schemas/Failure" schemas: Success: type: object required: [success] properties: success: type: boolean enum: [true] Failure: type: object required: [success, message] properties: success: type: boolean enum: [false] message: type: string example: "Unknown option 'nosuchthing'." Options: type: object description: >- Options by the destinations of commix's own command-line options, with `-` read as `_`. additionalProperties: true example: url: "http://commix-testbed/scenarios/regular/GET/classic.php?addr=127.0.0.1" flush_session: true time_limit: 90 Report: type: object description: >- The `--report-json` report, as it stands. A run over a single target is the object below. A run over several - `bulkfile`, `logfile`, `sitemap_url` or a crawl - carries one such object per target under `targets`, with `started`, `status`, `finished` and `requests` for the run as a whole. properties: targets: type: array description: Present only where the run covered more than one target. items: type: object target: { type: string } http_method: { type: string } command: { type: string } started: { type: string } finished: { type: string } status: type: string enum: [running, completed, time-limit, terminated, failed] requests: { type: integer } target_os: { type: string } waf_detected: { type: boolean } evasion_applied: { type: string } findings: type: array items: { $ref: "#/components/schemas/Finding" } errors: type: array items: { $ref: "#/components/schemas/Issue" } Finding: type: object properties: parameter: { type: string, example: "addr" } http_method: { type: string, example: "GET" } technique: { type: string, example: "Results-based" } type: { type: string, example: "Classic command injection" } boundary: { type: string, example: "';' separator, ' ' whitespace" } payload: { type: string } reproduce: type: string description: The single request that brings the finding back. reproduce_caveats: { type: string } Issue: type: object properties: time: { type: string, example: "08:12:05" } level: type: string enum: [error, critical, abort] message: { type: string } LogLine: type: object properties: time: { type: string, example: "08:12:01" } level: { type: string, example: "info" } message: { type: string, example: "Testing connection to the target URL." }