Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ WebUI/API Manual usage explained in the [Usage](Usage#api-and-webui) page but le

- [Purpose](#purpose)
- [Requests Structure](#requests-structure)
- [Upload File](#upload-file)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Sign the PR commit before merge

The reviewed commit 9d1383ddce47c4bc77a88ed59b1cd3e24fd59746 is unsigned (git log -1 --format='%G?' reports N), but this repository requires all commits to be signed; please rewrite or squash the PR commit with signing enabled before merge.

Useful? React with 👍 / 👎.

- [New Scan](#new-scan)
- [Set Session](#set-session)
* [Set Cookie](#set-cookie)
Expand Down Expand Up @@ -70,6 +71,34 @@ At the first, you must send an API key through the request each time you send a
401
```

## Upload File

The parameters that accept a file path (`targets_list`, `usernames_list`, `passwords_list`, and `read_from_file`) cannot be set directly through `/new/scan`; the API strips them from the form values and only honours paths populated by a prior upload. To provide a file for one of these parameters, `POST` it to `/upload/file` first and the server will store it and bind it to the named parameter for subsequent scans.

The request must be `multipart/form-data` with two fields:

- `file` — the file contents
- `param_name` — one of `targets_list`, `usernames_list`, `passwords_list`, `read_from_file`

Comment thread
pUrGe12 marked this conversation as resolved.
Outdated
Constraints:

- Maximum size: 10 MB (`MAX_CONTENT_LENGTH`)
- Allowed extensions for `targets_list`/`usernames_list`/`passwords_list`: `txt`, `csv`, `lst`, `list` (configurable via `allowed_upload_extensions` in `nettacker/config.py`). `read_from_file` is not extension-checked here because module configs gate it in the core.
- Filenames are sanitised and prefixed with a UUID before being written to the tmp directory.

```python
>>> r = requests.post(
... "https://127.0.0.1:5000/upload/file",
... data={"key": "<your_api_key>", "param_name": "targets_list"},
... files={"file": open("targets.txt", "rb")},
... verify=False,
... )
>>> r.json()
{"msg": "<uuid>_targets.txt", "status": "ok"}
```

Possible error responses (HTTP 400): `upload_no_file`, `upload_no_file_selected`, `upload_invalid_filename`, `upload_file_type_not_allowed`. A subsequent `POST /new/scan` will automatically pick up the uploaded path for the matching `param_name`.
Comment thread
pUrGe12 marked this conversation as resolved.
Outdated

## New Scan

To submit a new scan follow this step.
Expand Down Expand Up @@ -217,7 +246,7 @@ u'{"msg":"please choose your scan method!","status":"error"}\n'

```

All variables in JSON you've got in results could be changed in `GET`/`POST`/`Cookies`, you can fill them all just like normal CLI commands. (e.g. same scan method name (modules), you can separate with `,`, you can use `ports` like `80,100-200,1000,2000`, set users and passwords `user1,user2`, `passwd1,passwd2`). You cannot use `read_from_file:/tmp/users.txt` syntax in `methods_args`. if you want to send a big password list, just send it through the `POST` requests and separated with `,`.
All variables in JSON you've got in results could be changed in `GET`/`POST`/`Cookies`, you can fill them all just like normal CLI commands. (e.g. same scan method name (modules), you can separate with `,`, you can use `ports` like `80,100-200,1000,2000`, set users and passwords `user1,user2`, `passwd1,passwd2`). You cannot use `read_from_file:/tmp/users.txt` syntax in `methods_args`. If you want to send a big password list, either pass it inline through `POST` separated with `,` or upload it via [`/upload/file`](#upload-file) with `param_name=passwords_list` (the same applies to `targets_list`, `usernames_list`, and `read_from_file` — passing them as plain form values is ignored because the server only trusts paths produced by an upload).

## Set Session

Expand Down
41 changes: 41 additions & 0 deletions nettacker/api/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import random
import string
import time
import uuid
from threading import Thread
from types import SimpleNamespace

Expand Down Expand Up @@ -50,6 +51,11 @@

app = Flask(__name__, template_folder=str(Config.path.web_static_dir))
app.config.from_object(__name__)
app.config["MAX_CONTENT_LENGTH"] = (
10 * 1024 * 1024
) # https://flask.palletsprojects.com/en/stable/patterns/fileuploads/
FILE_UPLOAD_PARAMS = ("targets_list", "passwords_list", "usernames_list", "read_from_file")
uploaded_files = {}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

nettacker_path_config = Config.path
nettacker_application_config = Config.settings.as_dict()
Expand Down Expand Up @@ -236,6 +242,35 @@ def sanitize_report_path_filename(report_path_filename):
return safe_report_path


def allowed_file(filename):
return (
"." in filename
and filename.rsplit(".", 1)[1].lower() in Config.settings.allowed_upload_extensions
)


@app.route("/upload/file", methods=["POST"])
def upload_file():
api_key_is_valid(app, flask_request)
param_name = get_value(flask_request, "param_name")
if "file" not in flask_request.files:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
return jsonify(structure(status="error", msg=_("upload_no_file"))), 400
uploaded = flask_request.files["file"]
if uploaded.filename == "":
return jsonify(structure(status="error", msg=_("upload_no_file_selected"))), 400
if not allowed_file(uploaded.filename):
return jsonify(structure(status="error", msg=_("upload_file_type_not_allowed"))), 400
filename = secure_filename(uploaded.filename)
if not filename:
return jsonify(structure(status="error", msg=_("upload_invalid_filename"))), 400
unique_name = f"{uuid.uuid4().hex}_{filename}"
tmp_dir = nettacker_path_config.tmp_dir
tmp_dir.mkdir(parents=True, exist_ok=True)
uploaded.save(str(tmp_dir / unique_name))
uploaded_files[param_name] = str(tmp_dir / unique_name)
return jsonify(structure(status="ok", msg=unique_name)), 200


@app.route("/new/scan", methods=["GET", "POST"])
def new_scan():
"""
Expand All @@ -253,6 +288,12 @@ def new_scan():
if not report_path_filename:
return jsonify(structure(status="error", msg="Invalid report filename")), 400
form_values["report_path_filename"] = str(report_path_filename)

# Ensuring that these parameters cannot be set by the user without a file upload
for key in FILE_UPLOAD_PARAMS:
form_values.pop(key, None)
form_values.update(uploaded_files)

for key in nettacker_application_config:
if key not in form_values:
form_values[key] = nettacker_application_config[key]
Expand Down
1 change: 1 addition & 0 deletions nettacker/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ class DefaultSettings(ConfigBase):
verbose_mode = False
scan_compare_id = None
compare_report_path_filename = ""
allowed_upload_extensions = ("txt", "csv", "lst", "list")


class Config:
Expand Down
6 changes: 5 additions & 1 deletion nettacker/locale/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,8 @@ report_retrieval_fail: Could not retrieve the report
read_report_fail: Failed to read report file {0}
search_results_end: No more search results
database_error: Database error!
error_exclude_all: "Cannot exclude all modules. Please specify individual module names to exclude using -x."
error_exclude_all: "Cannot exclude all modules. Please specify individual module names to exclude using -x."
upload_no_file: no file provided
upload_no_file_selected: no file selected
upload_invalid_filename: invalid filename
upload_file_type_not_allowed: file type not allowed
24 changes: 23 additions & 1 deletion nettacker/web/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ <h3>Targets</h3>
<span class="input-group-addon"><i class="fa fa-space-shuttle"></i></span>
<input id="targets" type="text" class="form-control" data-role="tagsinput" placeholder="Add new target" >
</div>
<label class="btn btn-default btn-sm" style="margin-top: 5px;">
Upload targets file <input type="file" id="targets_list_file" style="display:none;">
</label>
<span id="targets_list_status"></span>
<div class="form-group" style="margin-top: 10px;">
<label>
<input id="skip_service_discovery" type="checkbox">
Expand Down Expand Up @@ -313,21 +317,39 @@ <h3>HTTP Headers</h3>
</div>
<br>


<div class="form-group col-md-6">
<h3>Usernames</h3>
<textarea class="input-mini form-control" rows="8" cols="50" id="usernames"
data-role="tagsinput" placeholder="separate with commas or new line">
</textarea>
<label class="btn btn-default btn-sm" style="margin-top: 5px;">
Upload usernames file <input type="file" id="usernames_list_file" style="display:none;" >
</label>
<span id="usernames_list_status"></span>
</div>
<div class="form-group col-md-6">
<h3>Passwords</h3>
<textarea class="form-control" rows="8" cols="50" id="passwords"
placeholder="separate with commas or new line"></textarea>
<label class="btn btn-default btn-sm" style="margin-top: 5px;">
Upload passwords file <input type="file" id="passwords_list_file" style="display:none;" >
</label>
<span id="passwords_list_status"></span>
</div>
<div class="clearfix"></div>
<div class="form-group col-md-6">
<h3>Wordlist</h3>
<label class="btn btn-default btn-sm">
Upload wordlist file <input type="file" id="read_from_file_file" style="display:none;"> <!-- Not checking for file extensions here because that is a config in the core -->
</label>
<span id="read_from_file_status"></span>
</div>
<br>
</div>
<div class="clearfix"></div>
<br>
<ul id="submit_new_scan" class="btn btn-primary" action="javascript:submit_new_scan()">
<ul id="submit_new_scan" class="btn btn-primary" style="margin-left: 15px;" action="javascript:submit_new_scan()">
Submit
</ul>
</form>
Expand Down
27 changes: 27 additions & 0 deletions nettacker/web/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,33 @@ $(document).ready(function () {
}
});

// file upload handler
$.each(["targets_list", "usernames_list", "passwords_list", "read_from_file"], function(_, param) {
$("#" + param + "_file").change(function () {
var fileInput = this;
if (!fileInput.files.length) return;
var formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("param_name", param);
var statusSpan = $("#" + param + "_status");
statusSpan.text("Uploading...");
$.ajax({
type: "POST",
url: "/upload/file",
data: formData,
processData: false,
contentType: false,
})
.done(function () {
statusSpan.text("Uploaded: " + fileInput.files[0].name);
})
.fail(function (jqXHR) {
statusSpan.text("Upload failed");
fileInput.value = "";
});
});
});

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
// submit new scan
$("#submit_new_scan").click(function () {
// set variables
Expand Down
Loading