diff --git a/api/__database.py b/api/__database.py index 13c72446..a1d77653 100644 --- a/api/__database.py +++ b/api/__database.py @@ -19,11 +19,15 @@ from core import compatible def create_connection(language): - ''' + """ a function to create sqlite3 connections to db, it retries 100 times if connection returned an error - :param language: language - :return: sqlite3 connection if success otherwise False - ''' + + Args: + language: language + + Returns: + sqlite3 connection if success otherwise False + """ try: # retries for i in range(0, 100): @@ -38,13 +42,17 @@ def create_connection(language): def send_submit_query(query, language): - ''' + """ a function to send submit based queries to db (such as insert and update or delete), it retries 100 times if connection returned an error. - :param query: query to execute - :param language: language - :return: True if submitted success otherwise False - ''' + + Args: + query: query to execute + language: language + + Returns: + True if submitted success otherwise False + """ conn = create_connection(language) if not conn: return False @@ -65,12 +73,16 @@ def send_submit_query(query, language): def send_read_query(query, language): - ''' + """ a function to send read based queries to db (such as select), it retries 100 times if connection returned an error. - :param query: query to execute - :param language: language - :return: return executed query otherwise False - ''' + + Args: + query: query to execute + language: language + + Returns: + return executed query otherwise False + """ conn = create_connection(language) if not conn: return False @@ -89,24 +101,28 @@ def send_read_query(query, language): def submit_report_to_db(date, scan_id, report_filename, events_num, verbose, api_flag, report_type, graph_flag, category, profile, scan_method, language, scan_cmd, ports): - ''' + """ this function created to submit the generated reports into db, the files are not stored in db, just the path! - :param date: date and time - :param scan_id: scan hash id - :param report_filename: report full path and filename - :param events_num: length of events in the report - :param verbose: verbose level used to generated the report - :param api_flag: 0 (False) if scan run from CLI and 1 (True) if scan run from API - :param report_type: could be TEXT, JSON or HTML - :param graph_flag: name of the graph used (if it's HTML type) - :param category: category of the modules used in scan (vuln, scan, brute) - :param profile: profiles used in scan - :param scan_method: modules used in scan - :param language: scan report language - :param scan_cmd: scan command line if run in CLI otherwise messages(language, 158) - :param ports: selected port otherwise None - :return: return True if submitted otherwise False - ''' + + Args: + date: date and time + scan_id: scan hash id + report_filename: report full path and filename + events_num: length of events in the report + verbose: verbose level used to generated the report + api_flag: 0 (False) if scan run from CLI and 1 (True) if scan run from API + report_type: could be TEXT, JSON or HTML + graph_flag: name of the graph used (if it's HTML type) + category: category of the modules used in scan (vuln, scan, brute) + profile: profiles used in scan + scan_method: modules used in scan + language: scan report language + scan_cmd: scan command line if run in CLI otherwise messages(language, 158) + ports: selected port otherwise None + + Returns: + return True if submitted otherwise False + """ info(messages(language, 169)) return send_submit_query(""" INSERT INTO reports ( @@ -125,25 +141,33 @@ def submit_report_to_db(date, scan_id, report_filename, events_num, verbose, api def remove_old_logs(host, type, scan_id, language): - ''' + """ this function remove old events (and duplicated) from database based on host, module, scan_id - :param host: host - :param type: module name - :param scan_id: scan id hash - :param language: language - :return: True if success otherwise False - ''' + + Args: + host: host + type: module name + scan_id: scan id hash + language: language + + Returns: + True if success otherwise False + """ return send_submit_query("""delete from hosts_log where host="{0}" and type="{1}" and scan_id!="{2}" """ .format(host, type, scan_id), language) def submit_logs_to_db(language, log): - ''' + """ this function created to submit new events into database - :param language: language - :param log: log event in JSON type - :return: True if success otherwise False - ''' + + Args: + language: language + log: log event in JSON type + + Returns: + True if success otherwise False + """ if type(log) == str: log = json.loads(log) return send_submit_query(""" @@ -162,13 +186,17 @@ def submit_logs_to_db(language, log): def __select_results(language, page): - ''' + """ this function created to crawl into submitted results, it shows last 10 results submitted in the database. you may change the page (default 1) to go to next/previous page. - :param language: language - :param page: page number - :return: list of events in array and JSON type, otherwise an error in JSON type. - ''' + + Args: + language: language + page: page number + + Returns: + list of events in array and JSON type, otherwise an error in JSON type. + """ page = int(page * 10 if page > 0 else page * -10) - 10 selected = [] try: @@ -198,12 +226,16 @@ def __select_results(language, page): def __get_result(language, id): - ''' + """ this function created to download results by the result ID. - :param language: language - :param id: result id - :return: result file content (TEXT, HTML, JSON) if success otherwise and error in JSON type. - ''' + + Args: + language: language + id: result id + + Returns: + result file content (TEXT, HTML, JSON) if success otherwise and error in JSON type. + """ try: try: filename = send_read_query("""select report_filename from reports where id=\"{0}\";""".format(id), @@ -216,12 +248,16 @@ def __get_result(language, id): def __last_host_logs(language, page): - ''' + """ this function created to select the last 10 events from the database. you can goto next page by changing page value. - :param language: language - :param page: page number - :return: an array of events in JSON type if success otherwise an error in JSON type - ''' + + Args: + language: language + page: page number + + Returns: + an array of events in JSON type if success otherwise an error in JSON type + """ page = int(page * 10 if page > 0 else page * -10) - 10 data_structure = { "host": "", @@ -277,12 +313,16 @@ def __last_host_logs(language, page): def __logs_by_scan_id(scan_id, language): - ''' + """ select all events by scan id hash - :param scan_id: scan id hash - :param language: language - :return: an array with JSON events or an empty array - ''' + + Args: + scan_id: scan id hash + language: language + + Returns: + an array with JSON events or an empty array + """ try: logs = [] for log in send_read_query( @@ -305,12 +345,16 @@ def __logs_by_scan_id(scan_id, language): def __logs_to_report_json(host, language): - ''' + """ select all reports of a host - :param host: the host to search - :param language: language - :return: an array with JSON events or an empty array - ''' + + Args: + host: the host to search + language: language + + Returns: + an array with JSON events or an empty array + """ try: logs = [] for log in send_read_query( @@ -333,12 +377,16 @@ def __logs_to_report_json(host, language): def __logs_to_report_html(host, language): - ''' + """ generate HTML report with d3_tree_v2_graph for a host - :param host: the host - :param language: language - :return: HTML report - ''' + + Args: + host: the host + language: language + + Returns: + HTML report + """ try: logs = [] for log in send_read_query( @@ -377,13 +425,17 @@ def __logs_to_report_html(host, language): def __search_logs(language, page, query): - ''' + """ search in events (host, date, port, module, category, description, username, password, scan_id, scan_cmd) - :param language: language - :param page: page number - :param query: query to search - :return: an array with JSON structure of founded events or an empty array - ''' + + Args: + language: language + page: page number + query: query to search + + Returns: + an array with JSON structure of founded events or an empty array + """ page = int(page * 10 if page > 0 else page * -10) - 10 data_structure = { "host": "", diff --git a/api/__start_scan.py b/api/__start_scan.py index b4f40631..fe53927a 100644 --- a/api/__start_scan.py +++ b/api/__start_scan.py @@ -5,6 +5,15 @@ from core.attack import __go_for_attacks def __scan(config): + """ + call for attacks with separated config and parse ARGS + + Args: + config: config in JSON + + Returns: + True if success otherwise False + """ # Setting Variables targets = config["targets"] check_ranges = config["check_ranges"] @@ -28,7 +37,7 @@ def __scan(config): profile = config["profile"] backup_ports = config["backup_ports"] - __go_for_attacks(targets, check_ranges, check_subdomains, log_in_file, time_sleep, language, verbose_level, retries, - socks_proxy, users, passwds, timeout_sec, thread_number, ports, ping_flag, methods_args, - backup_ports, scan_method, thread_number_host, graph_flag, profile, True) - return True + return __go_for_attacks(targets, check_ranges, check_subdomains, log_in_file, time_sleep, language, verbose_level, + retries, + socks_proxy, users, passwds, timeout_sec, thread_number, ports, ping_flag, methods_args, + backup_ports, scan_method, thread_number_host, graph_flag, profile, True) diff --git a/api/api_core.py b/api/api_core.py index 8cd89cf8..5d888d11 100644 --- a/api/api_core.py +++ b/api/api_core.py @@ -12,6 +12,16 @@ from flask import abort def __structure(status="", msg=""): + """ + basic JSON message structure + + Args: + status: status (ok, failed) + msg: the message content + + Returns: + a JSON message + """ return { "status": status, "msg": msg @@ -19,6 +29,16 @@ def __structure(status="", msg=""): def __get_value(flask_request, _key): + """ + get a value from GET, POST or CCOKIES + + Args: + flask_request: the flask request + _key: the value name to find + + Returns: + the value content if found otherwise None + """ try: key = flask_request.args[_key] except: @@ -30,11 +50,21 @@ def __get_value(flask_request, _key): except: key = None if key is not None: - key = key.replace("\"", "").replace("'", "") + # fix it later + key = key.replace("\\\"", "\"").replace("\\\'", "\'") return key def __remove_non_api_keys(config): + """ + a function to remove non-api keys while loading ARGV + + Args: + config: all keys in JSON + + Returns: + removed non-api keys in all keys in JSON + """ non_api_keys = ["start_api", "api_host", "api_port", "api_debug_mode", "api_access_key", "api_client_white_list", "api_client_white_list_ips", "api_access_log", "api_access_log", "api_access_log_filename", "show_version", "check_update", "help_menu_flag", "targets_list", "users_list", "passwds_list", @@ -47,12 +77,28 @@ def __remove_non_api_keys(config): def __is_login(app, flask_request): + """ + check if session is valid + + Args: + app: flask app + flask_request: flask request + + Returns: + True if session is valid otherwise False + """ if app.config["OWASP_NETTACKER_CONFIG"]["api_access_key"] == __get_value(flask_request, "key"): return True return False def __mime_types(): + """ + contains all mime types for HTTP request + + Returns: + all mime types in json + """ return { ".aac": "audio/aac", ".abw": "application/x-abiword", @@ -126,10 +172,25 @@ def __mime_types(): def root_dir(): + """ + find the root directory for web static files + + Returns: + root path for static files + """ return os.path.join(os.path.join(os.path.dirname(os.path.dirname(__file__)), "web"), "static") def get_file(filename): + """ + open the requested file in HTTP requests + + Args: + filename: path and the filename + + Returns: + content of the file or abort(404) + """ try: src = os.path.join(root_dir(), filename) return open(src, 'rb').read() @@ -138,11 +199,30 @@ def get_file(filename): def __api_key_check(app, flask_request, language): + """ + check the validity of API key + + Args: + app: the flask app + flask_request: the flask request + language: language + + Returns: + 200 HTTP code if it's valid otherwise 401 error + + """ if app.config["OWASP_NETTACKER_CONFIG"]["api_access_key"] != __get_value(flask_request, "key"): abort(401, messages(language, 160)) + return def __languages(): + """ + define list of languages with country flag for API + + Returns: + HTML code for each language with its country flag + """ languages = [lang for lang in messages(-1, 0)] res = "" flags = { @@ -174,6 +254,12 @@ def __languages(): def __graphs(): + """ + all available graphs for API + + Returns: + HTML content or available graphs + """ res = """     """ for graph in load_all_graphs(): @@ -183,6 +269,12 @@ def __graphs(): def __profiles(): + """ + all available profiles for API + + Returns: + HTML content or available profiles + """ profiles = _builder(_profiles(), default_profiles()) res = "" for profile in profiles: @@ -192,6 +284,12 @@ def __profiles(): def __scan_methods(): + """ + all available modules for API + + Returns: + HTML content or available modules + """ methods = load_all_modules() methods.remove("all") res = "" @@ -204,6 +302,17 @@ def __scan_methods(): def __rules(config, defaults, language): + """ + Load ARGS from API requests and apply the rules + + Args: + config: all user config + defaults: default config + language: language + + Returns: + config with applied rules + """ # Check Ranges config["check_ranges"] = True if config["check_ranges"] is not False else False # Check Subdomains diff --git a/api/engine.py b/api/engine.py index 3b4d8d6f..d242fddc 100644 --- a/api/engine.py +++ b/api/engine.py @@ -47,40 +47,101 @@ app.config.from_object(__name__) def __language(app=app): + """ + find the language in config + + Args: + app: flask app + + Returns: + the language in string + """ return app.config["OWASP_NETTACKER_CONFIG"]["language"] @app.errorhandler(400) def error_400(error): + """ + handle the 400 HTTP error + + Args: + error: the flask error + + Returns: + 400 JSON error + """ return jsonify(__structure(status="error", msg=error.description)), 400 @app.errorhandler(401) def error_401(error): + """ + handle the 401 HTTP error + + Args: + error: the flask error + + Returns: + 401 JSON error + """ return jsonify(__structure(status="error", msg=error.description)), 401 @app.errorhandler(403) def error_403(error): + """ + handle the 403 HTTP error + + Args: + error: the flask error + + Returns: + 403 JSON error + """ return jsonify(__structure(status="error", msg=error.description)), 403 @app.errorhandler(404) def error_404(error): + """ + handle the 404 HTTP error + + Args: + error: the flask error + + Returns: + 404 JSON error + """ return jsonify(__structure(status="error", msg=messages(app.config["OWASP_NETTACKER_CONFIG"]["language"], 162))), 404 @app.before_request def limit_remote_addr(): + """ + check if IP filtering applied and API address is in whitelist + + Returns: + None if it's in whitelist otherwise abort(403) + """ # IP Limitation if app.config["OWASP_NETTACKER_CONFIG"]["api_client_white_list"]: if flask_request.remote_addr not in app.config["OWASP_NETTACKER_CONFIG"]["api_client_white_list_ips"]: abort(403, messages(__language(), 161)) + return @app.after_request def access_log(response): + """ + if access log enabled, its writing the logs + + Args: + response: the flask response + + Returns: + the flask response + """ if app.config["OWASP_NETTACKER_CONFIG"]["api_access_log"]: r_log = open(app.config["OWASP_NETTACKER_CONFIG"]["api_access_log_filename"], "ab") # if you need to log POST data @@ -99,6 +160,15 @@ def access_log(response): @app.route("/", defaults={"path": ""}) @app.route("/") def get_statics(path): + """ + getting static files and return content mime types + + Args: + path: path and filename + + Returns: + file content and content type if file found otherwise abort(404) + """ static_types = __mime_types() return Response(get_file(os.path.join(root_dir(), path)), mimetype=static_types.get(os.path.splitext(path)[1], "text/html")) @@ -106,6 +176,12 @@ def get_statics(path): @app.route("/", methods=["GET", "POST"]) def index(): + """ + index page for WebUI + + Returns: + rendered HTML page + """ filename = _builder(_core_config(), _core_default_config())["log_in_file"] return render_template("index.html", scan_method=__scan_methods(), profile=__profiles(), graphs=__graphs(), languages=__languages(), filename=filename, @@ -114,6 +190,12 @@ def index(): @app.route("/new/scan", methods=["GET", "POST"]) def new_scan(): + """ + new scan through the API + + Returns: + a JSON message with scan details if success otherwise a JSON error + """ _start_scan_config = {} __api_key_check(app, flask_request, __language()) for key in _core_default_config(): @@ -134,12 +216,24 @@ def new_scan(): @app.route("/session/check", methods=["GET"]) def __session_check(): + """ + check the session if it's valid + + Returns: + a JSON message if it's valid otherwise abort(401) + """ __api_key_check(app, flask_request, __language()) return jsonify(__structure(status="ok", msg=messages(__language(), 165))), 200 @app.route("/session/set", methods=["GET"]) def __session_set(): + """ + set session on the browser + + Returns: + 200 HTTP response if session is valid and a set-cookie in the response if success otherwise abort(403) + """ __api_key_check(app, flask_request, __language()) res = make_response(jsonify(__structure(status="ok", msg=messages(__language(), 165)))) res.set_cookie("key", value=app.config["OWASP_NETTACKER_CONFIG"]["api_access_key"]) @@ -148,6 +242,12 @@ def __session_set(): @app.route("/session/kill", methods=["GET"]) def __session_kill(): + """ + unset session on the browser + + Returns: + a 200 HTTP response with set-cookie to "expired" to unset the cookie on the browser + """ res = make_response(jsonify(__structure(status="ok", msg=messages(__language(), 166)))) res.set_cookie("key", value="expired") return res @@ -155,6 +255,12 @@ def __session_kill(): @app.route("/results/get_list", methods=["GET"]) def __get_results(): + """ + get list of scan's results through the API + + Returns: + an array of JSON scan's results if success otherwise abort(403) + """ __api_key_check(app, flask_request, __language()) try: page = int(__get_value(flask_request, "page")) @@ -165,6 +271,12 @@ def __get_results(): @app.route("/results/get", methods=["GET"]) def __get_result_content(): + """ + get a result HTML/TEXT/JSON content + + Returns: + content of the scan result + """ __api_key_check(app, flask_request, __language()) try: id = int(__get_value(flask_request, "id")) @@ -175,6 +287,12 @@ def __get_result_content(): @app.route("/logs/get_list", methods=["GET"]) def __get_last_host_logs(): + """ + get list of logs through the API + + Returns: + an array of JSON logs if success otherwise abort(403) + """ __api_key_check(app, flask_request, __language()) try: page = int(__get_value(flask_request, "page")) @@ -185,6 +303,12 @@ def __get_last_host_logs(): @app.route("/logs/get_html", methods=["GET"]) def __get_logs_html(): + """ + get host's logs through the API in HTML type + + Returns: + HTML report + """ __api_key_check(app, flask_request, __language()) try: host = __get_value(flask_request, "host") @@ -195,6 +319,12 @@ def __get_logs_html(): @app.route("/logs/get_json", methods=["GET"]) def __get_logs(): + """ + get host's logs through the API in JSON type + + Returns: + an array with JSON events + """ __api_key_check(app, flask_request, __language()) try: host = __get_value(flask_request, "host") @@ -205,6 +335,12 @@ def __get_logs(): @app.route("/logs/search", methods=["GET"]) def ___go_for_search_logs(): + """ + search in all events + + Returns: + an array with JSON events + """ __api_key_check(app, flask_request, __language()) try: page = int(__get_value(flask_request, "page")) @@ -219,6 +355,20 @@ def ___go_for_search_logs(): def __process_it(api_host, api_port, api_debug_mode, api_access_key, api_client_white_list, api_client_white_list_ips, api_access_log, api_access_log_filename, language): + """ + a function to run flask in a subprocess to make kill signal in a better way! + + Args: + api_host: host/IP to bind address + api_port: bind port + api_debug_mode: debug mode flag + api_access_key: API access key + api_client_white_list: clients while list flag + api_client_white_list_ips: clients white list IPs + api_access_log: access log flag + api_access_log_filename: access log filename + language: language + """ app.config["OWASP_NETTACKER_CONFIG"] = { "api_access_key": api_access_key, "api_client_white_list": api_client_white_list, @@ -232,6 +382,20 @@ def __process_it(api_host, api_port, api_debug_mode, api_access_key, api_client_ def _start_api(api_host, api_port, api_debug_mode, api_access_key, api_client_white_list, api_client_white_list_ips, api_access_log, api_access_log_filename, language): + """ + entry point to run the API through the flask + + Args: + api_host: host/IP to bind address + api_port: bind port + api_debug_mode: debug mode + api_access_key: API access key + api_client_white_list: clients while list flag + api_client_white_list_ips: clients white list IPs + api_access_log: access log flag + api_access_log_filename: access log filename + language: language + """ # Starting the API write_to_api_console(messages(language, 156).format(api_access_key)) p = multiprocessing.Process(target=__process_it, diff --git a/api/readme.md b/api/readme.md new file mode 100644 index 00000000..be69a623 --- /dev/null +++ b/api/readme.md @@ -0,0 +1,10 @@ +OWASP Nettacker API Files +========================= + +OWASP Nettacker API files are stored in here. + +* `__database.py` contains database interaction functions +* `engine.py` is entry point of API and main functions +* `api_core.py` has core functions +* `__start_scan.py` run new scans +* `database.sqlite3` an empty API database for sample, its copy to `~/.owasp-nettacker/database.sqlite3` and stores data i there. \ No newline at end of file diff --git a/core/readme.md b/core/readme.md new file mode 100644 index 00000000..8c979bc2 --- /dev/null +++ b/core/readme.md @@ -0,0 +1,22 @@ +OWASP Nettacker core functions +============================== + +OWASP Nettacker core functions are stored in here. + +* `_die.py` exit functions +* `_time.py` time functions +* `alert.py` user alerts and printing functions +* `args_loader.py` ARGV commands and apply rules +* `attack.py` start new attacks and multi-processing managements +* `color.py` color founds for windows and linux/mac. +* `compatible.py` compatibility functions +* `config.py` user configs (could be modify by user) +* `config_builder.py` core static configs (same as user configs but should not be change by users) +* `get_input.py` get inputs from users functions +* `ip.py` IPv4 and IPv6 functions +* `load_modules` load modules, requirements, paths functions +* `log.py` log the scans and generate reports +* `parse.py` parse the ARGV and pass it +* `targets.py` process, calculate and count targets +* `update.py` updates functions of the framework +* `wizard.py` wizard mode for the framework \ No newline at end of file diff --git a/lib/readme.md b/lib/readme.md new file mode 100644 index 00000000..20d0e534 --- /dev/null +++ b/lib/readme.md @@ -0,0 +1,14 @@ +OWASP Nettacker Libraries and Modules +===================================== + +OWASP Nettacker modules and libraries are located in here + +* `argparse` argparse fixed library for unicode +* `brute` contains brute force types modules +* `graph` graph modules of the framework +* `html_log` HTML log contents +* `icmp` ICMP library +* `language` contains messages in several languages +* `scan` contains scan types modules +* `vuln` contains vulnerability types modules +* `socks_resolver` resolve address by socks proxy diff --git a/scripts/readme.md b/scripts/readme.md index ad5a8efd..5331b49b 100644 --- a/scripts/readme.md +++ b/scripts/readme.md @@ -1 +1,7 @@ -Script files are located in here \ No newline at end of file +OWASP Nettacker Scripts +======================= +Script files are located in here. + +* `__travis_test__.py` test the framework through the TravisCI +* `nettacker` run OWASP Nettacker through the terminal +* `nettacker.bat` run OWASP Nettacker throough the Windows CMD \ No newline at end of file diff --git a/setup.py b/setup.py index 1cccee6a..19d11a61 100644 --- a/setup.py +++ b/setup.py @@ -8,11 +8,15 @@ from setuptools import find_packages def package_files(directory): - ''' + """ This function was created to crawl the directory and find files (none python files) using os.walk - :param directory: path to crawl - :return: list of package files in an array - ''' + + Args: + directory: path to crawl + + Returns: + list of package files in an array + """ paths = [] for (path, directories, filenames) in os.walk(directory): for filename in filenames: diff --git a/web/readme.md b/web/readme.md index 14234956..6ae4ba9f 100644 --- a/web/readme.md +++ b/web/readme.md @@ -1 +1,5 @@ -Web UI files are located in here \ No newline at end of file +OWASP Nettacker WebUI +===================== +Web UI files are located in here. + +* `static` static files for web UI \ No newline at end of file