From 67264422bc030e220e0df02835cde256b5f1e5b5 Mon Sep 17 00:00:00 2001 From: Shaswat Date: Sat, 4 Oct 2025 01:42:57 +0530 Subject: [PATCH 1/6] Make uvloop optional for Windows compatibility uvloop does not support Windows. This change makes it conditional using platform markers, only installing on non-Windows platforms. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e6939b9b5..f69393774 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ requests = "^2.32.3" sqlalchemy = "^2.0.22" texttable = "^1.7.0" zipp = "^3.19.1" -uvloop = "^0.21.0" +uvloop = {version = "^0.21.0", markers = "sys_platform != 'win32'"} pymysql = "^1.1.1" impacket = "^0.11.0" From 0b966cb2a75ff46b5057ed558839425183e2a1cc Mon Sep 17 00:00:00 2001 From: Shaswat Date: Sat, 4 Oct 2025 02:16:33 +0530 Subject: [PATCH 2/6] Add Windows compatibility support - Make uvloop optional using platform markers (Windows unsupported) - Add win32 to allowed platforms in check_dependencies() - Replace os.geteuid() with cross-platform privilege checking - Add explicit UTF-8 encoding for file operations - Fix hardcoded path separators using pathlib in: - load_graphs() - load_modules() - load_languages() - get_result_content() This enables Nettacker to run on Windows systems. --- nettacker/api/engine.py | 4 ++-- nettacker/core/app.py | 21 ++++++++++++++++++--- nettacker/core/arg_parser.py | 15 ++++++++------- nettacker/core/template.py | 3 ++- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/nettacker/api/engine.py b/nettacker/api/engine.py index 9979e05ba..92b5743e8 100644 --- a/nettacker/api/engine.py +++ b/nettacker/api/engine.py @@ -379,6 +379,7 @@ def get_result_content(): Returns: content of the scan result """ + from pathlib import Path api_key_is_valid(app, flask_request) scan_id = get_value(flask_request, "id") if not scan_id: @@ -388,11 +389,10 @@ def get_result_content(): filename, file_content = get_scan_result(scan_id) except Exception: return jsonify(structure(status="error", msg="database error!")), 500 - return Response( file_content, mimetype=mime_types().get(os.path.splitext(filename)[1], "text/plain"), - headers={"Content-Disposition": "attachment;filename=" + filename.split("/")[-1]}, + headers={"Content-Disposition": "attachment;filename=" + Path(filename).name}, ) diff --git a/nettacker/core/app.py b/nettacker/core/app.py index 5cd47a98b..f75667502 100644 --- a/nettacker/core/app.py +++ b/nettacker/core/app.py @@ -36,6 +36,21 @@ log = logger.get_logger() +def is_running_with_privileges(): + """ + Check if running with elevated privileges (root/admin) + + Returns: + bool: True if running as root (Unix) or Administrator (Windows) + """ + if sys.platform == "win32": + try: + import ctypes + return ctypes.windll.shell32.IsUserAnAdmin() != 0 + except Exception: + return False + else: + return os.geteuid() == 0 class Nettacker(ArgParser): def __init__(self, api_arguments=None): @@ -66,7 +81,7 @@ def print_logo(): log.reset_color() def check_dependencies(self): - if sys.platform not in {"darwin", "freebsd13", "freebsd14", "freebsd15", "linux"}: + if sys.platform not in {"darwin", "freebsd13", "freebsd14", "freebsd15", "linux", "win32"}: die_failure(_("error_platform")) try: @@ -165,13 +180,13 @@ def expand_targets(self, scan_id): self.arguments.targets.append(sub_domain) # icmp_scan if self.arguments.ping_before_scan: - if os.geteuid() == 0: + if is_running_with_privileges(): selected_modules = self.arguments.selected_modules self.arguments.selected_modules = ["icmp_scan"] self.start_scan(scan_id) self.arguments.selected_modules = selected_modules if "icmp_scan" in self.arguments.selected_modules: - self.arguments.selected_modules.remove("icmp_scan") + self.arguments.selected_modules.remove("icmp_scan") self.arguments.targets = self.filter_target_by_event(targets, scan_id, "icmp_scan") else: log.warn(_("icmp_need_root_access")) diff --git a/nettacker/core/arg_parser.py b/nettacker/core/arg_parser.py index e8aed1218..11633e683 100644 --- a/nettacker/core/arg_parser.py +++ b/nettacker/core/arg_parser.py @@ -1,5 +1,6 @@ import json import sys +from pathlib import Path from argparse import ArgumentParser import yaml @@ -48,10 +49,9 @@ def load_graphs(): Returns: an array of graph names """ - graph_names = [] for graph_library in Config.path.graph_dir.glob("*/engine.py"): - graph_names.append(str(graph_library).split("/")[-2] + "_graph") + graph_names.append(graph_library.parent.name + "_graph") return list(set(graph_names)) @staticmethod @@ -62,13 +62,13 @@ def load_languages(): Returns: an array of languages """ + from pathlib import Path languages_list = [] for language in Config.path.locale_dir.glob("*.yaml"): - languages_list.append(str(language).split("/")[-1].split(".")[0]) + languages_list.append(Path(language).stem) # .stem gets filename without extension return list(set(languages_list)) - @staticmethod def load_modules(limit=-1, full_details=False): """ @@ -80,11 +80,13 @@ def load_modules(limit=-1, full_details=False): Returns: an array of all module names """ + from pathlib import Path # Search for Modules module_names = {} for module_name in sorted(Config.path.modules_dir.glob("**/*.yaml")): - library = str(module_name).split("/")[-1].split(".")[0] - category = str(module_name).split("/")[-2] + module_path = Path(module_name) + library = module_path.stem # Gets filename without extension + category = module_path.parent.name # Gets parent directory name module = f"{library}_{category}" contents = yaml.safe_load(TemplateLoader(module).open().split("payload:")[0]) module_names[module] = contents["info"] if full_details else None @@ -100,7 +102,6 @@ def load_modules(limit=-1, full_details=False): module_names["all"] = {} return module_names - @staticmethod def load_profiles(limit=-1): """ diff --git a/nettacker/core/template.py b/nettacker/core/template.py index ab6207237..27acb0d8e 100644 --- a/nettacker/core/template.py +++ b/nettacker/core/template.py @@ -32,7 +32,8 @@ def open(self): action = module_name_parts[-1] library = "_".join(module_name_parts[:-1]) - with open(Config.path.modules_dir / action / f"{library}.yaml") as yaml_file: + # TEMP FIX FOR TESTING - Explicitly specify UTF-8 encoding for Windows + with open(Config.path.modules_dir / action / f"{library}.yaml", encoding='utf-8') as yaml_file: return yaml_file.read() def format(self): From a8c49e7e81e5b0d15eff34ba4f8774668bb93f1f Mon Sep 17 00:00:00 2001 From: Shaswat Date: Sat, 4 Oct 2025 15:32:12 +0530 Subject: [PATCH 3/6] Add Windows compatibility support - Make uvloop optional using platform markers (Windows unsupported) - Add win32 to allowed platforms in check_dependencies() - Replace os.geteuid() with cross-platform privilege checking - Add explicit UTF-8 encoding for file operations - Fix hardcoded path separators using pathlib in: - load_graphs() - load_modules() - load_languages() - get_result_content() Resolves #[933] --- nettacker/api/engine.py | 5 +++-- nettacker/core/app.py | 2 +- nettacker/core/arg_parser.py | 15 +++++++-------- nettacker/core/template.py | 3 +-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/nettacker/api/engine.py b/nettacker/api/engine.py index 92b5743e8..dc3364244 100644 --- a/nettacker/api/engine.py +++ b/nettacker/api/engine.py @@ -7,6 +7,7 @@ import time from threading import Thread from types import SimpleNamespace +from pathlib import Path from flask import Flask, jsonify from flask import request as flask_request @@ -379,16 +380,16 @@ def get_result_content(): Returns: content of the scan result """ - from pathlib import Path api_key_is_valid(app, flask_request) scan_id = get_value(flask_request, "id") if not scan_id: return jsonify(structure(status="error", msg=_("invalid_scan_id"))), 400 - + try: filename, file_content = get_scan_result(scan_id) except Exception: return jsonify(structure(status="error", msg="database error!")), 500 + return Response( file_content, mimetype=mime_types().get(os.path.splitext(filename)[1], "text/plain"), diff --git a/nettacker/core/app.py b/nettacker/core/app.py index f75667502..c45d4f243 100644 --- a/nettacker/core/app.py +++ b/nettacker/core/app.py @@ -186,7 +186,7 @@ def expand_targets(self, scan_id): self.start_scan(scan_id) self.arguments.selected_modules = selected_modules if "icmp_scan" in self.arguments.selected_modules: - self.arguments.selected_modules.remove("icmp_scan") + self.arguments.selected_modules.remove("icmp_scan") self.arguments.targets = self.filter_target_by_event(targets, scan_id, "icmp_scan") else: log.warn(_("icmp_need_root_access")) diff --git a/nettacker/core/arg_parser.py b/nettacker/core/arg_parser.py index 11633e683..769c0cf1e 100644 --- a/nettacker/core/arg_parser.py +++ b/nettacker/core/arg_parser.py @@ -61,14 +61,13 @@ def load_languages(): Returns: an array of languages - """ - from pathlib import Path + """ languages_list = [] for language in Config.path.locale_dir.glob("*.yaml"): - languages_list.append(Path(language).stem) # .stem gets filename without extension - + languages_list.append(Path(language).stem) return list(set(languages_list)) + @staticmethod def load_modules(limit=-1, full_details=False): """ @@ -79,14 +78,13 @@ def load_modules(limit=-1, full_details=False): Returns: an array of all module names - """ - from pathlib import Path + """ # Search for Modules module_names = {} for module_name in sorted(Config.path.modules_dir.glob("**/*.yaml")): module_path = Path(module_name) - library = module_path.stem # Gets filename without extension - category = module_path.parent.name # Gets parent directory name + library = module_path.stem + category = module_path.parent.name module = f"{library}_{category}" contents = yaml.safe_load(TemplateLoader(module).open().split("payload:")[0]) module_names[module] = contents["info"] if full_details else None @@ -102,6 +100,7 @@ def load_modules(limit=-1, full_details=False): module_names["all"] = {} return module_names + @staticmethod def load_profiles(limit=-1): """ diff --git a/nettacker/core/template.py b/nettacker/core/template.py index 27acb0d8e..ba165eb3b 100644 --- a/nettacker/core/template.py +++ b/nettacker/core/template.py @@ -31,8 +31,7 @@ def open(self): module_name_parts = self.name.split("_") action = module_name_parts[-1] library = "_".join(module_name_parts[:-1]) - - # TEMP FIX FOR TESTING - Explicitly specify UTF-8 encoding for Windows + with open(Config.path.modules_dir / action / f"{library}.yaml", encoding='utf-8') as yaml_file: return yaml_file.read() From 92c3627f333a90f17d3a5792f5553db37c891986 Mon Sep 17 00:00:00 2001 From: Shaswat Date: Mon, 13 Oct 2025 02:41:46 +0530 Subject: [PATCH 4/6] Fix pre-commit formatting issues --- nettacker/api/engine.py | 6 +++--- nettacker/core/app.py | 33 ++++++++++++++++++--------------- nettacker/core/arg_parser.py | 16 ++++++++-------- nettacker/core/template.py | 6 ++++-- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/nettacker/api/engine.py b/nettacker/api/engine.py index dc3364244..4bf6a535b 100644 --- a/nettacker/api/engine.py +++ b/nettacker/api/engine.py @@ -5,9 +5,9 @@ import random import string import time +from pathlib import Path from threading import Thread from types import SimpleNamespace -from pathlib import Path from flask import Flask, jsonify from flask import request as flask_request @@ -384,12 +384,12 @@ def get_result_content(): scan_id = get_value(flask_request, "id") if not scan_id: return jsonify(structure(status="error", msg=_("invalid_scan_id"))), 400 - + try: filename, file_content = get_scan_result(scan_id) except Exception: return jsonify(structure(status="error", msg="database error!")), 500 - + return Response( file_content, mimetype=mime_types().get(os.path.splitext(filename)[1], "text/plain"), diff --git a/nettacker/core/app.py b/nettacker/core/app.py index c45d4f243..397f9851b 100644 --- a/nettacker/core/app.py +++ b/nettacker/core/app.py @@ -36,21 +36,24 @@ log = logger.get_logger() + def is_running_with_privileges(): - """ - Check if running with elevated privileges (root/admin) - - Returns: - bool: True if running as root (Unix) or Administrator (Windows) - """ - if sys.platform == "win32": - try: - import ctypes - return ctypes.windll.shell32.IsUserAnAdmin() != 0 - except Exception: - return False - else: - return os.geteuid() == 0 + """ + Check if running with elevated privileges (root/admin) + + Returns: + bool: True if running as root (Unix) or Administrator (Windows) + """ + if sys.platform == "win32": + try: + import ctypes + + return ctypes.windll.shell32.IsUserAnAdmin() != 0 + except Exception: + return False + else: + return os.geteuid() == 0 + class Nettacker(ArgParser): def __init__(self, api_arguments=None): @@ -180,7 +183,7 @@ def expand_targets(self, scan_id): self.arguments.targets.append(sub_domain) # icmp_scan if self.arguments.ping_before_scan: - if is_running_with_privileges(): + if is_running_with_privileges(): selected_modules = self.arguments.selected_modules self.arguments.selected_modules = ["icmp_scan"] self.start_scan(scan_id) diff --git a/nettacker/core/arg_parser.py b/nettacker/core/arg_parser.py index 769c0cf1e..9240eec26 100644 --- a/nettacker/core/arg_parser.py +++ b/nettacker/core/arg_parser.py @@ -1,7 +1,7 @@ import json import sys -from pathlib import Path from argparse import ArgumentParser +from pathlib import Path import yaml @@ -61,13 +61,13 @@ def load_languages(): Returns: an array of languages - """ + """ languages_list = [] for language in Config.path.locale_dir.glob("*.yaml"): - languages_list.append(Path(language).stem) + languages_list.append(Path(language).stem) return list(set(languages_list)) - + @staticmethod def load_modules(limit=-1, full_details=False): """ @@ -78,13 +78,13 @@ def load_modules(limit=-1, full_details=False): Returns: an array of all module names - """ + """ # Search for Modules module_names = {} for module_name in sorted(Config.path.modules_dir.glob("**/*.yaml")): module_path = Path(module_name) - library = module_path.stem - category = module_path.parent.name + library = module_path.stem + category = module_path.parent.name module = f"{library}_{category}" contents = yaml.safe_load(TemplateLoader(module).open().split("payload:")[0]) module_names[module] = contents["info"] if full_details else None @@ -100,7 +100,7 @@ def load_modules(limit=-1, full_details=False): module_names["all"] = {} return module_names - + @staticmethod def load_profiles(limit=-1): """ diff --git a/nettacker/core/template.py b/nettacker/core/template.py index ba165eb3b..d88bc2bf3 100644 --- a/nettacker/core/template.py +++ b/nettacker/core/template.py @@ -31,8 +31,10 @@ def open(self): module_name_parts = self.name.split("_") action = module_name_parts[-1] library = "_".join(module_name_parts[:-1]) - - with open(Config.path.modules_dir / action / f"{library}.yaml", encoding='utf-8') as yaml_file: + + with open( + Config.path.modules_dir / action / f"{library}.yaml", encoding="utf-8" + ) as yaml_file: return yaml_file.read() def format(self): From 342e3c5e6a32530a15d6c59138a5ba6144f6153f Mon Sep 17 00:00:00 2001 From: Shaswat Date: Thu, 16 Oct 2025 18:31:15 +0530 Subject: [PATCH 5/6] Refactor: Move is_running_with_privileges to common utils --- nettacker/core/app.py | 20 +------------------- nettacker/core/utils/common.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/nettacker/core/app.py b/nettacker/core/app.py index 397f9851b..4d9051a95 100644 --- a/nettacker/core/app.py +++ b/nettacker/core/app.py @@ -27,7 +27,7 @@ from nettacker.core.module import Module from nettacker.core.socks_proxy import set_socks_proxy from nettacker.core.utils import common as common_utils -from nettacker.core.utils.common import wait_for_threads_to_finish +from nettacker.core.utils.common import wait_for_threads_to_finish, is_running_with_privileges from nettacker.database.db import find_events, remove_old_logs from nettacker.database.mysql import mysql_create_database, mysql_create_tables from nettacker.database.postgresql import postgres_create_database @@ -37,24 +37,6 @@ log = logger.get_logger() -def is_running_with_privileges(): - """ - Check if running with elevated privileges (root/admin) - - Returns: - bool: True if running as root (Unix) or Administrator (Windows) - """ - if sys.platform == "win32": - try: - import ctypes - - return ctypes.windll.shell32.IsUserAnAdmin() != 0 - except Exception: - return False - else: - return os.geteuid() == 0 - - class Nettacker(ArgParser): def __init__(self, api_arguments=None): if not api_arguments: diff --git a/nettacker/core/utils/common.py b/nettacker/core/utils/common.py index 6418bbb90..d1c4e6a50 100644 --- a/nettacker/core/utils/common.py +++ b/nettacker/core/utils/common.py @@ -5,6 +5,7 @@ import importlib import math import multiprocessing +import os import random import re import string @@ -450,3 +451,21 @@ def generate_compare_filepath(scan_id): date_time=now(format="%Y_%m_%d_%H_%M_%S"), scan_id=scan_id, ) + + +def is_running_with_privileges(): + """ + Check if running with elevated privileges (root/admin) + + Returns: + bool: True if running as root (Unix) or Administrator (Windows) + """ + if sys.platform == "win32": + try: + import ctypes + + return ctypes.windll.shell32.IsUserAnAdmin() != 0 + except Exception: + return False + else: + return os.geteuid() == 0 From baa753025205829cf961537d7a754f1cbb84eadf Mon Sep 17 00:00:00 2001 From: Shaswat Date: Thu, 16 Oct 2025 22:35:14 +0530 Subject: [PATCH 6/6] Minor issue resolved --- nettacker/core/utils/common.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nettacker/core/utils/common.py b/nettacker/core/utils/common.py index d1c4e6a50..0dd09e3c7 100644 --- a/nettacker/core/utils/common.py +++ b/nettacker/core/utils/common.py @@ -462,8 +462,6 @@ def is_running_with_privileges(): """ if sys.platform == "win32": try: - import ctypes - return ctypes.windll.shell32.IsUserAnAdmin() != 0 except Exception: return False