diff --git a/setup.py b/setup.py index 49f87cd..b770e04 100644 --- a/setup.py +++ b/setup.py @@ -15,5 +15,5 @@ setup( "flask_socketio", "sqlalchemy", ], - extras_require={"dev": ["pre-commit", "black", "flake8"]}, + extras_require={"dev": ["pre-commit", "black", "flake8", "pytest"]}, ) diff --git a/src/overseer/__init__.py b/src/overseer/__init__.py index c29d07d..d9efbdc 100644 --- a/src/overseer/__init__.py +++ b/src/overseer/__init__.py @@ -1,10 +1,17 @@ import click +from importlib.metadata import version from overseer.config import EnvConfig +from overseer.scanner import ScanManager +from overseer.database import DatabaseConnector from flask import Flask from flask.cli import FlaskGroup +__version__ = version("overseer") + app = Flask(__name__) config = EnvConfig() +database = DatabaseConnector(config.DATA_PATH) +scan_manager = ScanManager() def create_app(): diff --git a/src/overseer/api/__init__.py b/src/overseer/api/__init__.py index e69de29..7b6857a 100644 --- a/src/overseer/api/__init__.py +++ b/src/overseer/api/__init__.py @@ -0,0 +1,25 @@ +open_websockets = [] + + +""" +EVENT: + DATA: + { + id: 1, + target: "127.0.0.1", + total_progress: 90, + tcp_progress: 80, + udp_progress: 100, + } +""" + + +def send_websocket_event(data): + """Send an event to all registered websockets. + + Arguments: + data -- Data to send over the websocket(s) + """ + print("EMITTING DATA: %s" % data) + for socket in open_websockets: + socket.send(data) diff --git a/src/overseer/api/v1/events.py b/src/overseer/api/v1/events.py index 09fe891..8b5ba52 100644 --- a/src/overseer/api/v1/events.py +++ b/src/overseer/api/v1/events.py @@ -1,7 +1,9 @@ from overseer import app +from overseer.api import open_websockets from flask_socketio import SocketIO socketio = SocketIO(app, path="/api/v1/socket.io") +open_websockets.append(socketio) @socketio.on("message") diff --git a/src/overseer/api/v1/routes.py b/src/overseer/api/v1/routes.py index 36e2121..b33fb84 100644 --- a/src/overseer/api/v1/routes.py +++ b/src/overseer/api/v1/routes.py @@ -1,11 +1,91 @@ +import overseer from overseer.api.v1 import api +from flask import request @api.route("/status", methods=["GET"]) def status(): - return "STATUS PLACEHOLDER" + """ + GET: + REQUEST: /api/v1/status + RESPONSE: + { + version: "0.0.1",, + active_scans: [ + + ], + } + """ + return { + "version": overseer.__version__, + "active_scans": overseer.scan_manager.get_status(), + } -@api.route("/scan", methods=["GET"]) -def scan(): - return "SCAN PLACEHOLDER" +@api.route("/scans", methods=["POST"]) +def scans_post(): + """ + POST: + REQUEST: /api/v1/scans + DATA: { target: 'www.google.com' } + + RESPONSE: { status: "started" } + """ + data = request.get_json() + if data is None or "target" not in data: + return {"error": "Missing 'target'"}, 422 + + history_id = overseer.scan_manager.perform_scan(data["target"]) + if history_id is None: + return {"error": "Unable to resolve hostname."}, 422 + + return {"status": "started", "id": history_id} + + +@api.route("/scans/", methods=["GET"]) +def scan_get_history(target): + """ + GET: + REQUEST: /api/v1/scans/www.google.com + /api/v1/scans/1.1.1.1 + RESPONSE: { "data": [ ] } + """ + page = 1 + + if overseer.scan_manager.is_ip(target): + scan_results = overseer.database.get_scan_results_by_target( + page, ip_addr=target + ) + else: + scan_results = overseer.database.get_scan_results_by_target( + page, hostname=target + ) + + return {"data": __normalize_scan_results(scan_results)} + + +# TODO +@api.route("/search", methods=["GET"]) +def scan_get_all(search): + """ + GET: + REQUEST: /api/v1/search?query=1.1.1.1 + /api/v1/search?query=192.168.0.0/24 + /api/v1/search?query=www.google.com&page=2 + RESPONSE: { data: [ ] } + """ + return "SEARCH PLACEHOLDER" + + +def __normalize_scan_results(scan_results): + return list( + map( + lambda x: { + "results": x.results.split(","), + "created_at": x.created_at, + "status": x.status, + "error": x.error, + }, + scan_results, + ) + ) diff --git a/src/overseer/config.py b/src/overseer/config.py index f6450f4..2689ef9 100644 --- a/src/overseer/config.py +++ b/src/overseer/config.py @@ -9,3 +9,4 @@ def get_env(key, default=None, required=False): class EnvConfig: DATABASE = get_env("OVERSEER_DB", default="sqlite") + DATA_PATH = get_env("OVERSEER_DATA_PATH", default="./") diff --git a/src/overseer/database.py b/src/overseer/database.py index 509838c..a6e3cb0 100644 --- a/src/overseer/database.py +++ b/src/overseer/database.py @@ -1,105 +1,139 @@ -import models +from overseer.models import Base, ScanTarget, ScanHistory import ipaddress from datetime import datetime from os import path -from sqlalchemy import ( - create_engine, - or_, -) +from sqlalchemy import create_engine from sqlalchemy.orm import Session +from sqlalchemy.orm.exc import NoResultFound class DatabaseConnector: def __init__(self, data_path, in_memory=False): if in_memory: self.engine = create_engine( - "sqlite+pysqlite:///:memory:", echo=True, future=True + "sqlite+pysqlite:///:memory:", echo=False, future=True ) else: db_path = path.join(data_path, "overseer.sqlite") self.engine = create_engine( "sqlite+pysqlite:///%s" % db_path, - echo=True, + echo=False, future=True, ) - models.Base.metadata.create_all(self.engine) + Base.metadata.create_all(self.engine) + self.__cleanup_stale_records() - def create_scan_result(self, ip_addr, scan_results, hostname=None): - int_ip_addr = int(ipaddress.ip_addr(ip_addr)) + def __cleanup_stale_records(self): session = Session(bind=self.engine) + history_filter = ScanHistory.status == "IN_PROGRESS" + all_stale = session.query(ScanHistory).filter(history_filter).all() - # Does an existing target exist? - scan_target = ( - session.query(models.ScanTarget) - .filter( - or_( - models.ScanTarget.ip == int_ip_addr, - models.ScanTarget.hostname == hostname, - ) - ) - .first() - ) + for stale in all_stale: + stale.status = "FAILED" + stale.message = "Stale history" - # TODO: Do we need to update hostname? - - # Nope, create one - if not scan_target: - scan_target = models.ScanTarget(ip=int_ip_addr, hostname=hostname) - session.add(scan_target) - session.commit() - - # Create scan history - scan_history = models.ScanHistory( - target_id=scan_target.id, - results=",".join(map(str, scan_results)), - datetime=datetime.now(), - ) - session.add(scan_history) session.commit() - session.close() - def get_scan_results(self, **kwargs): - """Returns scan results for the queries target. - - Keyword arguments: - hostname -- The hostname of the target. - ip_addr -- The IP address of the target. - """ + def create_scan_target(self, **kwargs): if len(kwargs.keys() & {"ip_addr", "hostname"}) != 1: raise ValueError("Missing keyword argument: ip_addr or hostname") - hostname = kwargs["hostname"] if "hostname" in kwargs else None - ip_addr = kwargs["ip_addr"] if "ip_addr" in kwargs else None - int_ip_addr = int(ipaddress.ip_addr(ip_addr)) if ip_addr else None + if "ip_addr" in kwargs: + int_ip_addr = int(ipaddress.ip_address(kwargs["ip_addr"])) + scan_target = ScanTarget(ip=int_ip_addr, updated_at=datetime.now()) + elif "hostname" in kwargs: + scan_target = ScanTarget( + hostname=kwargs["hostname"], updated_at=datetime.now() + ) + + session = Session(bind=self.engine, expire_on_commit=False) + session.add(scan_target) + session.commit() + session.close() + return scan_target + + def get_scan_target(self, **kwargs): + if len(kwargs.keys() & {"ip_addr", "hostname"}) != 1: + raise ValueError("Missing keyword argument: ip_addr or hostname") + + if "ip_addr" in kwargs: + int_ip_addr = int(ipaddress.ip_address(kwargs["ip_addr"])) + target_filter = ScanTarget.ip == int_ip_addr + elif "hostname" in kwargs: + target_filter = ScanTarget.hostname == kwargs["hostname"] session = Session(bind=self.engine) + scan_target = session.query(ScanTarget).filter(target_filter).first() + session.close() + return scan_target - # Get all scan histories - scan_histories = ( - session.query(models.ScanHistory) - .join(models.ScanHistory.target) - .filter( - or_( - models.ScanTarget.ip == int_ip_addr, - models.ScanTarget.hostname == hostname, - ) - ) + def get_all_scan_targets(self, page=1): + session = Session(bind=self.engine) + scan_targets = ( + session.query(ScanTarget) + .order_by(ScanTarget.updated_at.desc()) + .offset((page - 1) * 25) + .limit(25) .all() ) - session.close() + return scan_targets + def create_scan_result(self, status, results=[], error=None, **kwargs): + scan_target = self.get_scan_target(**kwargs) + if not scan_target: + scan_target = self.create_scan_target(**kwargs) + + scan_history = ScanHistory( + target_id=scan_target.id, + results=",".join(results), + status=status, + error=error, + created_at=datetime.now(), + ) + session = Session(bind=self.engine, expire_on_commit=False) + session.add(scan_history) + session.commit() + session.close() + return scan_history + + def update_scan_result(self, history_id, status, results=None, error=None): + session = Session(bind=self.engine, expire_on_commit=False) + scan_history = session.query(ScanHistory).get(history_id) + + if scan_history is None: + raise NoResultFound("ScanHistory %s does not exist" % history_id) + + scan_history.status = status + if results is not None: + scan_history.results = ",".join(results) + if error is not None: + scan_history.error = error + + session.commit() + session.close() + return scan_history + + def get_scan_results_by_target(self, page=1, **kwargs): + if len(kwargs.keys() & {"ip_addr", "hostname"}) != 1: + raise ValueError("Missing keyword argument: ip_addr or hostname") + + if "ip_addr" in kwargs: + int_ip_addr = int(ipaddress.ip_address(kwargs["ip_addr"])) + history_filter = ScanTarget.ip == int_ip_addr + elif "hostname" in kwargs: + history_filter = ScanTarget.hostname == kwargs["hostname"] + + session = Session(bind=self.engine) + scan_histories = ( + session.query(ScanHistory) + .join(ScanHistory.target) + .filter(history_filter) + .order_by(ScanHistory.created_at.desc()) + .offset((page - 1) * 25) + .limit(25) + .all() + ) + session.close() return scan_histories - - -# FOR TESTING PURPOSES -def main(): - data_path = "/Users/evanreichard/Development/git/overseer/src/overseer" - db = DatabaseConnector(data_path) - db.create_scan_result("1.2.3.4", [5, 6, 7, 8], "test222.com") - db.get_scan_results(ip_addr="1.2.3.4") - - -if __name__ == "__main__": - main() diff --git a/src/overseer/models.py b/src/overseer/models.py index d9ee6b8..fb1eb2f 100644 --- a/src/overseer/models.py +++ b/src/overseer/models.py @@ -13,13 +13,16 @@ class ScanTarget(Base): # Integer representation of an IP Address ip = Column(Integer, index=True, unique=True) - # Corresponding hostname. + # Corresponding hostname hostname = Column(String, index=True, unique=True) + # Updated At + updated_at = Column(DateTime()) + def __repr__(self): return ( f"ScanTarget(id={self.id!r}, ip={self.ip!r}, " - f"hostname={self.hostname!r})" + f"hostname={self.hostname!r}, updated_at={self.updated_at!r})" ) @@ -32,17 +35,24 @@ class ScanHistory(Base): # Scan Target Reference target_id = Column(Integer, ForeignKey("scan_target.id")) - # Results + # Results (53 TCP, 53 UDP) results = Column(String) - # DateTime - datetime = Column(DateTime()) + # Created At + created_at = Column(DateTime()) + + # Status (IN_PROGRESS, COMPLETE, FAILED) + status = Column(String) + + # Error Message (If applicable) + error = Column(String) # Relationship target = relationship("ScanTarget", foreign_keys=[target_id]) def __repr__(self): return ( - f"ScanHistory(id={self.id!r}, target={self.target!r}, " - f"results={self.results!r}, datetime={self.datetime!r})" + f"ScanHistory(id={self.id!r}, target_id={self.target_id!r}, " + f"results={self.results!r}, created_at={self.created_at!r}, " + f"status={self.status!r}, error={self.error!r})" ) diff --git a/src/overseer/overseer.py b/src/overseer/overseer.py index 3a790e5..db94cd7 100644 --- a/src/overseer/overseer.py +++ b/src/overseer/overseer.py @@ -2,23 +2,20 @@ from overseer import app from flask import make_response, render_template, send_from_directory from overseer.api.v1 import api as api_v1 -""" -Initial Entrypoint to the SPA (i.e. 'index.html') -""" - @app.route("/", methods=["GET"]) def main_entry(): + """ + Initial Entrypoint to the SPA (i.e. 'index.html') + """ return make_response(render_template("index.html")) -""" -Front End Static Resources -""" - - @app.route("/static/") def static_resources(path): + """ + Front End Static Resources + """ return send_from_directory("static", path) diff --git a/src/overseer/resources/nmap-payloads b/src/overseer/resources/nmap-payloads new file mode 100644 index 0000000..ee897d7 --- /dev/null +++ b/src/overseer/resources/nmap-payloads @@ -0,0 +1,498 @@ +# Nmap nmap payload database -*- mode: fundamental; -*- +# $Id$ +# +# These payloads are sent with every host discovery or port scan probe +# by default. This database should only include payloads that are +# unlikely to crash services, trip IDS alerts, or change state on the +# server. The idea behind these is to evoke a response using a payload. +# Some of them are taken from nmap-service-probes. +# +# This collection of data is (C) 1996-2010 by Insecure.Com +# LLC. It is distributed under the Nmap Public Source license as +# provided in the LICENSE file of the source distribution or at +# https://nmap.org/data/LICENSE . Note that this license +# requires you to license your own work under a compatible open source +# license. If you wish to embed Nmap technology into proprietary +# software, we sell alternative licenses (contact sales@insecure.com). +# Dozens of software vendors already license Nmap technology such as +# host discovery, port scanning, OS detection, and version detection. +# For more details, see https://nmap.org/book/man-legal.html +# +# Each entry begins with a protocol (only "udp" is supported) followed +# by a comma-separated list of ports, followed by one or more quoted +# strings containing the payload. These elements may be broken across +# several lines. For future expansion, additional keywords may follow +# the payload data. Any data following one of these keywords must be on +# the same line as the keyword so that unknown keywords can be ignored +# by the parser. Currently this file contains some entries with the +# "source" keyword to specify a desired source port, but it is not +# honored by Nmap. +# +# Example: +# udp 1234 "payloaddatapayloaddata" +# "payloaddatapayloaddata" +# source 5678 + +# GenericLines. Use for the echo service. +udp 7 "\x0D\x0A\x0D\x0A" +# DNSStatusRequest +udp 53,5353,26198 "\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" +# DNS VER +udp 53,5353,26198 + "\x77\x77\x01\x00\x00" + "\x01\x00\x00\x00\x00\x00\x00\x07version\x04bind\x00\x00\x10\x00\x03" + +# DHCP INFORM +udp 67 + "\x01\x01\x06\x00" + "\x01\x23\x45\x67\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x35\xd4\xd8\x51\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x63\x82\x53\x63\x35\x01" + "\x08\xff" + +# TFTP GET +udp 69 "\x00\x01r7tftp.txt\x00octet\x00" + +# QUIC packet with unsupported version Q999 +# Also found on 443, but need to check whether DTLS or QUIC is more prevalent +udp 80 "\r12345678Q999\x00" +# RPCCheck +udp 111 + "\x72\xFE\x1D\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xA0" + "\x00\x01\x97\x7C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" +# ONCRPC CALL +udp 111,2049,4045,32768-65535 + "\x3e\xec\xe3\xca\x00\x00\x00\x00\x00\x00\x00\x02\x00" + "\xbc\x61\x4e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +# NTPRequest +udp 123 + "\xE3\x00\x04\xFA\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\xC5\x4F\x23\x4B\x71\xB1\x52\xF3" +# NTP REQ +udp 123 + "\xd9\x00\x0a\xfa\x00\x00\x00" + "\x00\x00\x01\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6" + "\xf1\x5e\xdb\x78\x00\x00\x00" +# DCERPC CALL +udp 135,1025-1199 + "\x05\x00\x0b\x03\x10\x00\x00\x00\x48\x00" + "\x00\x00\x01\x00\x00\x00\xb8\x10\xb8\x10\x00\x00\x00\x00\x01\x00\x00" + "\x00\x00\x00\x01\x00\x01\x23\x45\x67\x89\xab\xcd\xef\x01\x23\x45\x67" + "\x89\xab\xcd\xef\xe7\x03\x00\x00\xfe\xdc\xba\x98\x76\x54\x32\x10\x01" + "\x23\x45\x67\x89\xab\xcd\xef\xe7\x03\x00\x00" +# NBTStat +udp 137 + "\x80\xF0\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00" + "\x20CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00\x21\x00\x01" +# CIFS NS NAME QUERY UC +udp 137 + "\x01\x91\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" + "\x20CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00\x21\x00\x01" +# CIFS NS NAME QUERY BC +udp 137 + "\x01\x91\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00" + "\x20CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00\x21\x00\x01" +# SNMPv3GetRequest +udp 161,260,3401 + "\x30\x3A\x02\x01\x03\x30\x0F\x02\x02\x4A\x69\x02\x03\x00\xFF\xE3" + "\x04\x01\x04\x02\x01\x03\x04\x10\x30\x0E\x04\x00\x02\x01\x00\x02" + "\x01\x00\x04\x00\x04\x00\x04\x00\x30\x12\x04\x00\x04\x00\xA0\x0C" + "\x02\x02\x37\xF0\x02\x01\x00\x02\x01\x00\x30\x00" +# SNMP PUBLIC WALK +udp 161,260,3401 + "\x30\x1f\x02\x01\x00\x04\x06public\xa1\x12\x02\x01\x00\x02" + "\x01\x00\x02\x01\x00\x30\x07\x30\x05\x06\x01\x00\x05\x00" + +# Sqlping - disabled because it trips a Snort rule with SID 2049 +# ("MS-SQL ping attempt"). +# udp 1434 "\x02" + +# xdmcp - X Display Manager Control Protocol. Version 1, packet type +# Query (2), no authorization names. We expect a Willing or Unwilling +# packet in reply. +# http://cgit.freedesktop.org/xorg/doc/xorg-docs/plain/hardcopy/XDMCP/xdmcp.PS.gz +udp 177 "\x00\x01\x00\x02\x00\x01\x00" + +# Connectionless LDAP - used by Microsoft Active Directory +udp 389 + "\x30\x84\x00\x00\x00\x2d\x02\x01\x07\x63\x84\x00\x00\x00\x24\x04\x00" + "\x0a\x01\x00\x0a\x01\x00\x02\x01\x00\x02\x01\x64\x01\x01\x00\x87\x0b" + "objectClass0\x84\x00\x00\x00\x00" + + +# svrloc +udp 427 + "\x02\x01\x00\x006 \x00\x00\x00\x00\x00\x01\x00\x02en\x00\x00\x00\x15" + "service:service-agent\x00\x07default\x00\x00\x00\x00" + +# DTLS +udp 443,853,3391,4433,4740,5349,5684,5868,6514,6636,8232,10161,10162,12346,12446,12546,12646,12746,12846,12946,13046 + # DTLS 1.0, length 52 + "\x16\xfe\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36" + # ClientHello, length 40, sequence 0, offset 0 + "\x01\x00\x00\x2a\x00\x00\x00\x00\x00\x00\x00\x2a" + # DTLS 1.2 + "\xfe\xfd" + # Random + "\x00\x00\x00\x00\x7c\x77\x40\x1e\x8a\xc8\x22\xa0\xa0\x18\xff\x93" + "\x08\xca\xac\x0a\x64\x2f\xc9\x22\x64\xbc\x08\xa8\x16\x89\x19\x3f" + # Session id length 0, cookie length 0 + "\x00\x00" + # Cipher suites, mandatory TLS_RSA_WITH_AES_128_CBC_SHA + "\x00\x02\x00\x2f" + # Compressors (NULL) + "\x01\x00" + +# Internet Key Exchange version 1, phase 1 Main Mode. We offer every +# combination of (DES, 3DES) and (MD5, SHA) in the hope that one of them will +# be acceptable. Because we use a fixed cookie, we set the association lifetime +# to 1 second to reduce the chance that repeated probes will look like +# retransmissions (and therefore not get a response). This payload comes from +# ike-scan --lifetime 1 --cookie 0011223344556677 --trans=5,2,1,2 --trans=5,1,1,2 --trans=1,2,1,2 --trans=1,1,1,2 +# We expect another phase 1 message in response. This payload works better with +# a source port of 500 or a randomized initiator cookie. +udp 500 + # Initiator cookie 0x0011223344556677, responder cookie 0x0000000000000000. + "\x00\x11\x22\x33\x44\x55\x66\x77\x00\x00\x00\x00\x00\x00\x00\x00" + # Version 1, Main Mode, flags 0x00, message ID 0x00000000, length 192. + "\x01\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\xC0" + # Security Association payload, length 164, IPSEC, IDENTITY. + "\x00\x00\x00\xA4\x00\x00\x00\x01\x00\x00\x00\x01" + # Proposal 1, length 152, ISAKMP, 4 transforms. + "\x00\x00\x00\x98\x01\x01\x00\x04" + # Transform 1, 3DES-CBC, SHA, PSK, group 2. + "\x03\x00\x00\x24\x01\x01\x00\x00\x80\x01\x00\x05\x80\x02\x00\x02" + "\x80\x03\x00\x01\x80\x04\x00\x02" + "\x80\x0B\x00\x01\x00\x0C\x00\x04\x00\x00\x00\x01" + # Transform 2, 3DES-CBC, MD5, PSK, group 2. + "\x03\x00\x00\x24\x02\x01\x00\x00\x80\x01\x00\x05\x80\x02\x00\x01" + "\x80\x03\x00\x01\x80\x04\x00\x02" + "\x80\x0B\x00\x01\x00\x0C\x00\x04\x00\x00\x00\x01" + # Transform 3, DES-CBC, SHA, PSK, group 2. + "\x03\x00\x00\x24\x03\x01\x00\x00\x80\x01\x00\x01\x80\x02\x00\x02" + "\x80\x03\x00\x01\x80\x04\x00\x02" + "\x80\x0B\x00\x01\x00\x0C\x00\x04\x00\x00\x00\x01" + # Transform 4, DES-CBC, MD5, PSK, group 2. + "\x00\x00\x00\x24\x04\x01\x00\x00\x80\x01\x00\x01\x80\x02\x00\x01" + "\x80\x03\x00\x01\x80\x04\x00\x02" + "\x80\x0B\x00\x01\x00\x0C\x00\x04\x00\x00\x00\x01" +source 500 +# IPSEC START +udp 500,4500 + "\x31\x27\xfc" + "\xb0\x38\x10\x9e\x89\x00\x00\x00\x00\x00\x00\x00\x00\x01\x10\x02\x00" + "\x00\x00\x00\x00\x00\x00\x00\xcc\x0d\x00\x00\x5c\x00\x00\x00\x01\x00" + "\x00\x00\x01\x00\x00\x00\x50\x01\x01\x00\x02\x03\x00\x00\x24\x01\x01" + "\x00\x00\x80\x01\x00\x05\x80\x02\x00\x02\x80\x04\x00\x02\x80\x03\x00" + "\x03\x80\x0b\x00\x01\x00\x0c\x00\x04\x00\x00\x0e\x10\x00\x00\x00\x24" + "\x02\x01\x00\x00\x80\x01\x00\x05\x80\x02\x00\x01\x80\x04\x00\x02\x80" + "\x03\x00\x03\x80\x0b\x00\x01\x00\x0c\x00\x04\x00\x00\x0e\x10\x0d\x00" + "\x00\x18\x1e\x2b\x51\x69\x05\x99\x1c\x7d\x7c\x96\xfc\xbf\xb5\x87\xe4" + "\x61\x00\x00\x00\x04\x0d\x00\x00\x14\x40\x48\xb7\xd5\x6e\xbc\xe8\x85" + "\x25\xe7\xde\x7f\x00\xd6\xc2\xd3\x0d\x00\x00\x14\x90\xcb\x80\x91\x3e" + "\xbb\x69\x6e\x08\x63\x81\xb5\xec\x42\x7b\x1f\x00\x00\x00\x14\x26\x24" + "\x4d\x38\xed\xdb\x61\xb3\x17\x2a\x36\xe3\xd0\xcf\xb8\x19" +source 500 + +# Routing Information Protocol version 1. Special-case request for the entire +# routing table (address family 0, address 0.0.0.0, metric 16). RFC 1058, +# section 3.4.1. +udp 520 + "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x10" + +# RMCP ASF ping +udp 623 + "\x06\x00\xff\x06" # RMCP version 6, sequence 0xff, normal RMCP class ASF + "\x00\x00\x11\xbe" # IAN ASF code 4542 + "\x80\x00\x00\x00" # payload-less ASF presence ping + +# IPMI +# RMCP Get Channel Auth Capabilities +udp 623 + "\x06\x00\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x20\x18" + "\xc8\x81\x00\x38\x8e\x04\xb5" + +# serialnumberd. This service runs on Mac OS X Server. This probe +# requests the serial number of another server. In response we expect a +# packet starting with "SNRESPS:", followed by some data whose purpose +# is not known. +udp 626 "SNQUERY: 127.0.0.1:AAAAAA:xsvr" + +# OpenVPN P_CONTROL_HARD_RESET_CLIENT_V2 +# Byte 0; 0x38 opcode +# Byte 1-8: Session ID, random +# Byte 9: Message packet-id array length (0) +# Byte 10-13: Message packet-id (0) +udp 1194 "8d\xc1x\x01\xb8\x9b\xcb\x8f\0\0\0\0\0" +# OpenVPN when in PKI mode and without the "HMAC Firewall" setting enabled +# (tls-auth) should respond to the following probe, which is +# 0x38<8 random bytes><4 null bytes> +udp 1194 + "\x38\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00" + +# Citrix MetaFrame application browser service +# Original idea from http://sh0dan.org/oldfiles/hackingcitrix.html +# Payload contents copied from Wireshark capture of Citrix Program +# Neighborhood client application. The application uses this payload to +# locate Citrix servers on the local network. Response to this probe is +# a 48 byte UDP payload as shown here: +# +# 0000 30 00 02 31 02 fd a8 e3 02 00 06 44 c0 a8 80 55 +# 0010 00 00 00 00 00 00 00 00 00 00 00 00 02 00 06 44 +# 0020 c0 a8 80 56 00 00 00 00 00 00 00 00 00 00 00 00 +# +# The first 12 bytes appear to be the same in all responses. +# +# Bytes 0x00 appears to be a packet length field +# Bytes 0x0C - 0x0F are the IP address of the server +# Bytes 0x10 - 0x13 may vary, 0x14 - 0x1F do not appear to +# Bytes 0x20 - 0x23 are the IP address of the primary system in a server farm +# configuration +# Bytes 0x24 - 0x27 can vary, 0x28 - 0x2F do not appear to +udp 1604 + "\x1e\x00\x01\x30\x02\xfd\xa8\xe3\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +# RADIUS Access-Request. This is a degenerate packet with no username or +# password; we expect an Access-Reject in response. The Identifier and Request +# Authenticator are both 0. It was generated by running +# echo 'User-Password = ""' | radclient auth "" +# and then manually stripping out the password. +# +# Section 2 of the RFC says "A request from a client for which the +# RADIUS server does not have a shared secret MUST be silently +# discarded." So this payload only works when the server is configured +# (or misconfigured) to know the scanning machine as a client. +# +# RFC 2865: "The early deployment of RADIUS was done using UDP port +# number 1645, which conflicts with the "datametrics" service. The +# officially assigned port number for RADIUS is 1812. +udp 1645,1812 + "\x01\x00\x00\x14" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +# L2TP ICRQ +udp 1701 + "\xc8\x02" + "\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x80\x08\x00\x00\x00\x00\x00" + "\x01\x80\x08\x00\x00\x00\x02\x01\x00\x80\x0e\x00\x00\x00\x07" + "nxp-scan\x80\x0a\x00\x00\x00\x03\x00\x00\x00\x03\x80" + "\x08\x00\x00\x00\x09\x00\x00" + +# UPNP MSEARCH +udp 1900 + "M-SEARCH * HTTP/1.1\r\nHost: 239.255.255.250:1900\r\n" + "Man: \"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\n\r\n" + +# NFS version 2, RFC 1831. XID 0x00000000, program 100003 (NFS), procedure +# NFSPROC_NULL (does nothing, see section 2.2.1), null authentication (see +# section 9.1). +udp 2049 + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xA3" + "\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + +# GPRS Tunneling Protocol (GTP) +udp 2123,2152 +# GTPv1, protocol 1 +"\x32" +# EchoRequest +"\x01" +# message length +"\x00\x04" +# Tunnel endpoint identifier +"\x00\x00\x42\x00" +# sequence number +"\x13\x37" +# N-PDU number +"\x00" +# next extension header type +"\x00" + +# GPRS Tunneling Protocol (GTP) "prime" v2 +# This same packet can be used for GTP v2 on ports 2123 and 2152 if you change +# the first byte from \x4e to \x40 +udp 3386 +# GTP'v2 +"\x4e" +# EchoRequest +"\x01" +# message length +"\x00\x04" +# sequence number +"\xde\xfe\xc8\x00" + +# Freelancer game server status query +# http://sourceforge.net/projects/gameq/ +# (relevant files: games.ini, packets.ini, freelancer.php) +udp 2302 "\x00\x02\xf1\x26\x01\x26\xf0\x90\xa6\xf0\x26\x57\x4e\xac\xa0\xec\xf8\x68\xe4\x8d\x21" + +# Apple Remote Desktop (ARD) +udp 3283 "\0\x14\0\x01\x03" + +# STUN Binding request, see RFC 5389 Section 6 +# message type = 0x001, Binding (see Section 18.1) +# message length = 0 +# magic cookie = 0x2112a442 +# transaction ID = "\x00"*12 +udp 3478 "\x00\x01\x00\x00\x21\x12\xa4\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +# Sun Service Tag Discovery protocol (stdiscover) +# http://arc.opensolaris.org/caselog/PSARC/2006/638/stdiscover_protocolv2.pdf +# Would work better with a varying cookie; the second and later sends of this +# probe will be interpreted as resends by the server and will be ignored. +udp 6481 "[PROBE] 0000" + +# NAT-PMP external IP address request. See section 3.2 of +# http://files.dns-sd.org/draft-cheshire-nat-pmp.txt. +udp 5351 "\x00\x00" + +# DNS Service Discovery (DNS-SD) service query, as used in Zeroconf. +# Transaction ID 0x0000, flags 0x0000, 1 question: PTR query for +# _services._dns-sd._udp.local. If the remote host supports DNS-SD it will send +# back a list of all its services. This is the same as a packet capture of +# dns-sd -B _services._dns-sd._udp . +# See section 9 of +# http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt. +# This first probe is a QU probe, meaning a unicast response is desired +udp 5353 + "\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" + "\x09_services\x07_dns-sd\x04_udp\x05local\x00\x00\x0C\x80\x01" +# This second probe is a QM probe, meaning a unicast response is desired +udp 5353 + "\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" + "\x09_services\x07_dns-sd\x04_udp\x05local\x00\x00\x0C\x00\x01" + +# PCANY STATUS +udp 5632 "ST" + +# CoAP GET .well-known/core +udp 5683 "@\x01\x01\xce\xbb.well-known\x04core" + +# UT2K PING +udp 7777 "None\x00" + +# Ubiquiti Discovery Service - v1 +udp 10001 "\x01\x00\x00\x00" + +# Amanda backup service noop request. I think that this does nothing on the +# server but only asks it to send back its feature list. In reply we expect an +# ACK or (more likely) an ERROR. I couldn't find good online documentation of +# the Amanda network protocol. There is parsing code in the Amanda source at +# common-src/security-util.c. This is based on a packet capture of +# amcheck +udp 10080 + "Amanda 2.6 REQ HANDLE 000-00000000 SEQ 0\n" + "SERVICE noop\n" + +# VxWorks Wind River Debugger +udp 17185 +# Random XID +"\x00\x00\x00\x00" +# RPC version 2 procedure call +"\x00\x00\x00\x00\x00\x00\x00\x02" +# WDB version 1 +"\x55\x55\x55\x55\x00\x00\x00\x01" +# WDB_TARGET_PING +"\x00\x00\x00\x00" +# RPC Auth NULL +"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +# Checksum +"\xff\xff\x55\x13" +# WDB wrapper (length and sequence number) +"\x00\x00\x00\x30\x00\x00\x00\x01" +# Empty data? +"\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00" +# VXWORKS DEBUG (alternative?) +udp 17185 + "\x72\x37\x72\x37\x00\x00\x00" + "\x00\x00\x00\x00\x02\x55\x55\x55\x55\x00\x00\x00\x01\x00\x00\x00\x01" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff" + "\xff\x55\x10\x00\x00\x00\x3c\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00" + "\x00\x00\x00\x00\x00\x00" + +# Quake 2 and Quake 3 game servers (and servers of derived games like Nexuiz). +# Gets game information from the server (see probe responses in +# nmap-service-probes). These services typically run on a base port or a +# few numbers higher. +# Quake 2. Typical ports: 27910-97914. +udp 27910-27914 "\xff\xff\xff\xffstatus" +# Quake 3. Typical ports: +# 26000-26004: Nexuiz +# 27960-27964: Various games +# 30720-30724: Tremulous +# 44400: Warsow +udp 26000-26004,27960-27964,30720-30724,44400 "\xff\xff\xff\xffgetstatus" + +# Murmur 1.2.X (Mumble server) +# UDP ping. "abcdefgh" is an identifier. See +# http://mumble.sourceforge.net/Protocol. +udp 64738 "\x00\x00\x00\x00abcdefgh" + +# Ventrilo 2.1.2+ +# UDP general status request (encrypted). +# See http://aluigi.altervista.org/papers.htm#ventrilo +udp 3784 +"\x01\xe7\xe5\x75\x31\xa3\x17\x0b\x21\xcf\xbf\x2b\x99\x4e\xdd\x19\xac\xde\x08\x5f\x8b\x24\x0a\x11\x19\xb6\x73\x6f\xad\x28\x13\xd2\x0a\xb9\x12\x75" + +# Kademlia (kad) as used by various P2P applications. Send a Kademlia ping +# 4665, 4666, 4672, 6429: eDonkey/eMule and variants +udp 4665,4666,4672,6429 "\xE4\x60" + +# TeamSpeak 2 +# UDP login request +# See http://wiki.wireshark.org/TeamSpeak2 +udp 8767 +"\xf4\xbe\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x002x\xba\x85\tTeamSpeak\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\nWindows XP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00 \x00<\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08nickname\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +# TS3INIT1 +udp 9987 +"TS3INIT1\x00\x65\x00\x00\x88\x0a\x39\x7b\x0f\x00\x5b\x55\x72\xef\xdc\x78\x32\x6b\x00\x00\x00\x00\x00\x00\x00\x00" + +# TeamSpeak 3 +# UDP login request (encrypted) +# http://seclists.org/nmap-dev/2013/q3/72 +udp 9987 +"\x05\xca\x7f\x16\x9c\x11\xf9\x89\x00\x00\x00\x00\x02\x9d\x74\x8b\x45\xaa\x7b\xef\xb9\x9e\xfe\xad\x08\x19\xba\xcf\x41\xe0\x16\xa2\x32\x6c\xf3\xcf\xf4\x8e\x3c\x44\x83\xc8\x8d\x51\x45\x6f\x90\x95\x23\x3e\x00\x97\x2b\x1c\x71\xb2\x4e\xc0\x61\xf1\xd7\x6f\xc5\x7e\xf6\x48\x52\xbf\x82\x6a\xa2\x3b\x65\xaa\x18\x7a\x17\x38\xc3\x81\x27\xc3\x47\xfc\xa7\x35\xba\xfc\x0f\x9d\x9d\x72\x24\x9d\xfc\x02\x17\x6d\x6b\xb1\x2d\x72\xc6\xe3\x17\x1c\x95\xd9\x69\x99\x57\xce\xdd\xdf\x05\xdc\x03\x94\x56\x04\x3a\x14\xe5\xad\x9a\x2b\x14\x30\x3a\x23\xa3\x25\xad\xe8\xe6\x39\x8a\x85\x2a\xc6\xdf\xe5\x5d\x2d\xa0\x2f\x5d\x9c\xd7\x2b\x24\xfb\xb0\x9c\xc2\xba\x89\xb4\x1b\x17\xa2\xb6" + +# Memcached +# version request (shorter response than stats) +# https://github.com/memcached/memcached/blob/master/doc/protocol.txt +udp 11211 +"\0\x01\0\0\0\x01\0\0version\r\n" + +# Steam, typically using a port in 27015-27030. Send a "Source Engine query" +udp 27015-27030 + "\xff\xff\xff\xffTSource Engine Query\x00" + +# TRIN00 UNIX PING +udp 27444 "png l44adsl" + +# BO PING +udp 31337 + "\xce\x63\xd1\xd2\x16\xe7\x13\xcf\x38" + "\xa5\xa5\x86\xb2\x75\x4b\x99\xaa\x32\x58" + +# TRIN00 WIN PING +udp 34555 "png []..Ks" + +# Beckhoff ADS discovery request +# https://github.com/ONE75/adsclient/blob/master/src/AdsClient.Finder/DeviceFinder.cs#L49-L64 +udp 48899 +"\x03\x66\x14\x71\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x10\x27\x00\x00\x00\x00" diff --git a/src/overseer/scanner.py b/src/overseer/scanner.py index e69de29..75ac470 100644 --- a/src/overseer/scanner.py +++ b/src/overseer/scanner.py @@ -0,0 +1,225 @@ +import ipaddress +import overseer +from threading import Thread +from io import open +import socket +import os +import re +import time + + +class ScanManager: + def __init__(self): + self.pending_shutdown = False + self.active_scans = [] + self.broadcast_thread = Thread(target=self.__broadcast_thread) + self.broadcast_thread.start() + + def __broadcast_thread(self): + while not self.pending_shutdown: + time.sleep(5) + if len(self.active_scans) == 0: + continue + + for scan in self.active_scans: + # WebSocket progress + total_progress = (scan.tcp_progress + scan.udp_progress) / 2 + overseer.api.send_websocket_event( + { + "id": scan.history_id, + "target": scan.target, + "status": "RUNNING", + "tcp_progress": scan.tcp_progress, + "udp_progress": scan.udp_progress, + "total_progress": round(total_progress), + } + ) + + if scan.tcp_progress + scan.udp_progress != 200: + continue + + # Combine ports + results = list(map(lambda x: "%s UDP" % x, scan.udp_results)) + results.extend( + list(map(lambda x: "%s TCP" % x, scan.tcp_results)) + ) # noqa: E501 + results.sort() + + # Update database + overseer.database.update_scan_result( + scan.history_id, "COMPLETE", results=results + ) + + # WebSocket completion + overseer.api.send_websocket_event( + { + "id": scan.history_id, + "target": scan.target, + "status": "COMPLETE", + "results": results, + } + ) + + # Cleanup active scan + scan.join() + self.active_scans.remove(scan) + + def shutdown(self): + self.pending_shutdown = True + self.broadcast_thread.join() + + def get_status(self): + return list( + map( + lambda x: { + "id": x.history_id, + "target": x.target, + "status": "IN_PROGRESS", + "tcp_progress": x.tcp_progress, + "udp_progress": x.udp_progress, + "total_progress": round( + (x.tcp_progress + x.udp_progress) / 2 + ), # noqa: E501 + }, + self.active_scans, + ) + ) + + def perform_scan(self, target): + try: + target = socket.gethostbyname(target) + except socket.error: + return None + + if self.is_ip(target): + scan_history = overseer.database.create_scan_result( + "IN_PROGRESS", ip_addr=target + ) + else: + scan_history = overseer.database.create_scan_result( + "IN_PROGRESS", hostname=target + ) + + new_scan = Scanner(target, scan_history.id) + new_scan.start() + self.active_scans.append(new_scan) + return scan_history.id + + def is_ip(self, target): + try: + ipaddress.ip_address(target) + return True + except ValueError: + return False + + +class Scanner(Thread): + def __init__(self, target, history_id): + Thread.__init__(self) + self.target = target + self.history_id = history_id + + self.port_count = 1000 + + self.tcp_progress = 0 + self.udp_progress = 0 + + self.tcp_results = [] + self.udp_results = [] + + self.udp_payloads = {} + self.__load_nmap_payloads() + + def __load_nmap_payloads(self): + """Load and parse nmap UDP payloads""" + + # Open file & remove comments + nmap_payloads = os.path.join( + os.path.dirname(__file__), "./resources/nmap-payloads" + ) + f = open(nmap_payloads, "r", encoding="unicode_escape") + raw_file = re.sub( + r"^(#|\s*#).*$", "", f.read(), flags=re.MULTILINE | re.UNICODE + ) + f.close() + + # Find all matches + results = re.findall( + r"(^udp.*?)(?=udp|\Z)", + raw_file, + flags=re.MULTILINE | re.DOTALL | re.UNICODE, + ) + + for raw_match in results: + match = raw_match.strip() + match_payloads = list( + map(lambda x: x.strip(), re.findall(r'\"(.*)"', match)) + ) + raw_ports = re.match(r"^udp\s([\d,-]*)", match).group(1) + + for raw_port in raw_ports.split(","): + if "-" in raw_port: + port_range = raw_port.split("-") + start_port = int(port_range[0]) + end_port = int(port_range[1]) + for port_match in range(start_port, end_port + 1): + if port_match not in self.udp_payloads: + self.udp_payloads[port_match] = [] + self.udp_payloads[port_match].extend(match_payloads) + else: + port_match = int(raw_port) + if port_match not in self.udp_payloads: + self.udp_payloads[port_match] = [] + self.udp_payloads[port_match].extend(match_payloads) + + def run(self): + tcp_thread = Thread(target=self.__scan_tcp) + udp_thread = Thread(target=self.__scan_udp) + tcp_thread.start() + udp_thread.start() + tcp_thread.join() + udp_thread.join() + return {"TCP": self.tcp_results, "UDP": self.udp_results} + + def __scan_tcp(self): + for port in range(1, self.port_count): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(0.1) + result = s.connect_ex((self.target, port)) + if result == 0: + self.tcp_results.append(port) + s.close() + self.tcp_progress = round(port / self.port_count * 100) + + def __scan_udp(self): + for port in range(1, self.port_count): + # print("UDP port %s..." % port) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(0.01) + payloads = ["\x00"] + if port in self.udp_payloads: + payloads = self.udp_payloads[port] + for payload in payloads: + s.sendto(payload.encode("utf-8"), (self.target, port)) + try: + s.recvfrom(1) + self.udp_results.append(port) + break + except ConnectionRefusedError: + pass + except socket.timeout: + pass + s.close() + self.udp_progress = round(port / self.port_count * 100) + + +# FOR TESTING PURPOSES +def main(): + sm = ScanManager() + sm.perform_scan("localhost") + # sm.perform_scan("10.0.20.254") + # sm.perform_scan("10.0.21.20") + + +if __name__ == "__main__": + main() diff --git a/src/overseer/static/css/app.4d1483d7.css b/src/overseer/static/css/app.4d1483d7.css new file mode 100644 index 0000000..f660edc --- /dev/null +++ b/src/overseer/static/css/app.4d1483d7.css @@ -0,0 +1 @@ +#overseer-body[data-v-dc8467c2]{background-color:#ff0;width:100%;height:100%}#center[data-v-dc8467c2]{margin:0 auto;width:800px;height:100%;background-color:purple}#overseer-search[data-v-dc8467c2]{width:400px;border-radius:5px;height:40px;margin-top:300px;font-size:1rem;padding:.3rem .8rem;text-indent:0;outline:none;border:0 solid}#overseer-header[data-v-182df07c]{background-color:green;width:100%;height:60px}#overseer-notifications[data-v-5c26d9a3]{height:100%;width:320px;padding:10px;float:right;background-color:#00f}.overseer-notification[data-v-5c26d9a3]{width:300px;height:65px;background-color:green;border-radius:5px;border:1px solid #000;text-align:start;padding:10px}#overseer-app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;height:100%}body,html{height:100%;width:100%;margin:0} \ No newline at end of file diff --git a/src/overseer/static/css/app.fb0c6e1c.css b/src/overseer/static/css/app.fb0c6e1c.css deleted file mode 100644 index 899979b..0000000 --- a/src/overseer/static/css/app.fb0c6e1c.css +++ /dev/null @@ -1 +0,0 @@ -h3[data-v-b9167eee]{margin:40px 0 0}ul[data-v-b9167eee]{list-style-type:none;padding:0}li[data-v-b9167eee]{display:inline-block;margin:0 10px}a[data-v-b9167eee]{color:#42b983}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px} \ No newline at end of file diff --git a/src/overseer/static/img/logo.82b9c7a5.png b/src/overseer/static/img/logo.82b9c7a5.png deleted file mode 100644 index f3d2503..0000000 Binary files a/src/overseer/static/img/logo.82b9c7a5.png and /dev/null differ diff --git a/src/overseer/static/js/app.124cfaf1.js b/src/overseer/static/js/app.124cfaf1.js deleted file mode 100644 index cb46c2e..0000000 --- a/src/overseer/static/js/app.124cfaf1.js +++ /dev/null @@ -1,2 +0,0 @@ -(function(e){function t(t){for(var n,a,u=t[0],s=t[1],i=t[2],p=0,v=[];p\n
\n

{{ msg }}

\n

\n For a guide and recipes on how to configure / customize this project,
\n check out the\n vue-cli documentation.\n

\n

Installed CLI Plugins

\n \n

Essential Links

\n \n

Ecosystem

\n \n
\n\n\n\n\n\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./HelloWorld.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./HelloWorld.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./HelloWorld.vue?vue&type=template&id=b9167eee&scoped=true&\"\nimport script from \"./HelloWorld.vue?vue&type=script&lang=js&\"\nexport * from \"./HelloWorld.vue?vue&type=script&lang=js&\"\nimport style0 from \"./HelloWorld.vue?vue&type=style&index=0&id=b9167eee&scoped=true&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"b9167eee\",\n null\n \n)\n\nexport default component.exports","\n\n\n\n\n","import mod from \"-!../node_modules/cache-loader/dist/cjs.js??ref--12-0!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js!../node_modules/cache-loader/dist/cjs.js??ref--0-0!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../node_modules/cache-loader/dist/cjs.js??ref--12-0!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js!../node_modules/cache-loader/dist/cjs.js??ref--0-0!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./App.vue?vue&type=template&id=514e6843&\"\nimport script from \"./App.vue?vue&type=script&lang=js&\"\nexport * from \"./App.vue?vue&type=script&lang=js&\"\nimport style0 from \"./App.vue?vue&type=style&index=0&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","import Vue from 'vue'\nimport App from './App.vue'\n\nVue.config.productionTip = false\n\nnew Vue({\n render: h => h(App),\n}).$mount('#app')\n","module.exports = __webpack_public_path__ + \"img/logo.82b9c7a5.png\";"],"sourceRoot":""} \ No newline at end of file diff --git a/src/overseer/static/js/app.77b1e8f6.js b/src/overseer/static/js/app.77b1e8f6.js new file mode 100644 index 0000000..c1a79d7 --- /dev/null +++ b/src/overseer/static/js/app.77b1e8f6.js @@ -0,0 +1,2 @@ +(function(e){function t(t){for(var r,c,s=t[0],a=t[1],u=t[2],l=0,p=[];l\n
\n
\n \n
\n
\n\n\n\n\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OverseerHome.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OverseerHome.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./OverseerHome.vue?vue&type=template&id=dc8467c2&scoped=true&\"\nimport script from \"./OverseerHome.vue?vue&type=script&lang=js&\"\nexport * from \"./OverseerHome.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OverseerHome.vue?vue&type=style&index=0&id=dc8467c2&scoped=true&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"dc8467c2\",\n null\n \n)\n\nexport default component.exports","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{attrs:{\"id\":\"overseer-header\"}})}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OverseerHeader.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OverseerHeader.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./OverseerHeader.vue?vue&type=template&id=182df07c&scoped=true&\"\nimport script from \"./OverseerHeader.vue?vue&type=script&lang=js&\"\nexport * from \"./OverseerHeader.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OverseerHeader.vue?vue&type=style&index=0&id=182df07c&scoped=true&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"182df07c\",\n null\n \n)\n\nexport default component.exports","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{attrs:{\"id\":\"overseer-notifications\"}},_vm._l((_vm.items),function(item){return _c('div',{key:item.id,staticClass:\"overseer-notification\"},[_c('b',[_vm._v(\"Target: \")]),_vm._v(\" \"+_vm._s(item.target)+\" \"),_c('br'),_c('b',[_vm._v(\"Status: \")]),_vm._v(_vm._s(item.status)+\" \"),_c('br'),('total_progress' in item)?_c('span',[_c('b',[_vm._v(\"Progress: \")]),_vm._v(_vm._s(item.total_progress)+\" \"),_c('br')]):_vm._e()])}),0)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OverseerNotifications.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OverseerNotifications.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./OverseerNotifications.vue?vue&type=template&id=5c26d9a3&scoped=true&\"\nimport script from \"./OverseerNotifications.vue?vue&type=script&lang=js&\"\nexport * from \"./OverseerNotifications.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OverseerNotifications.vue?vue&type=style&index=0&id=5c26d9a3&scoped=true&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"5c26d9a3\",\n null\n \n)\n\nexport default component.exports","\n\n\n\n\n","import mod from \"-!../node_modules/cache-loader/dist/cjs.js??ref--12-0!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js!../node_modules/cache-loader/dist/cjs.js??ref--0-0!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../node_modules/cache-loader/dist/cjs.js??ref--12-0!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js!../node_modules/cache-loader/dist/cjs.js??ref--0-0!../node_modules/vue-loader/lib/index.js??vue-loader-options!./App.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./App.vue?vue&type=template&id=543eda39&\"\nimport script from \"./App.vue?vue&type=script&lang=js&\"\nexport * from \"./App.vue?vue&type=script&lang=js&\"\nimport style0 from \"./App.vue?vue&type=style&index=0&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","import Vue from 'vue'\nimport Vuex from 'vuex'\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n state: {\n ws_connected: false,\n notifications: []\n },\n mutations: {\n \"SOCKET_connect\"(state, status) {\n state.ws_connected = true\n console.log(\"CONNECT: \", state, status);\n },\n \"SOCKET_disconnect\"(state, status) {\n state.ws_connected = false\n console.log(\"DISCONNECT: \", state, status);\n },\n \"SOCKET_message\"(state, message) {\n let matchedItem = state.notifications.find(item => item.id == message.id);\n if (matchedItem){\n Object.keys(message).forEach(key => {\n // matchedItem[key] = message[key]\n Vue.set(matchedItem, key, message[key])\n });\n } else {\n state.notifications.push(message);\n }\n }\n },\n actions: {\n }\n})\n","import Vue from 'vue'\nimport App from './App.vue'\nimport store from './store'\nimport socketio from 'socket.io-client';\nimport VueSocketIO from 'vue-socket.io'\n\nVue.use(new VueSocketIO({\n debug: true,\n connection: socketio({ path: \"/api/v1/socket.io\" }),\n vuex: {\n store,\n actionPrefix: 'SOCKET_',\n mutationPrefix: 'SOCKET_'\n },\n}))\n\nVue.config.productionTip = false\n\nconsole.log(store)\n\nnew Vue({\n render: h => h(App),\n store\n}).$mount('#app')\n","export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--6-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OverseerHeader.vue?vue&type=style&index=0&id=182df07c&scoped=true&lang=css&\"","export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--6-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OverseerHome.vue?vue&type=style&index=0&id=dc8467c2&scoped=true&lang=css&\"","export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--6-oneOf-1-0!../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OverseerNotifications.vue?vue&type=style&index=0&id=5c26d9a3&scoped=true&lang=css&\""],"sourceRoot":""} \ No newline at end of file diff --git a/src/overseer/static/js/chunk-vendors.6130e7bc.js b/src/overseer/static/js/chunk-vendors.6130e7bc.js new file mode 100644 index 0000000..34e4d4d --- /dev/null +++ b/src/overseer/static/js/chunk-vendors.6130e7bc.js @@ -0,0 +1,32 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-vendors"],{"00ee":function(t,e,n){var r=n("b622"),o=r("toStringTag"),i={};i[o]="z",t.exports="[object z]"===String(i)},"01d3":function(t,e,n){const r=n("c9eb"),o=n("d941");t.exports=function(t){const e=t.xdomain,n=t.xscheme,i=t.enablesXDR;try{if("undefined"!==typeof XMLHttpRequest&&(!e||r))return new XMLHttpRequest}catch(s){}try{if("undefined"!==typeof XDomainRequest&&!n&&i)return new XDomainRequest}catch(s){}if(!e)try{return new(o[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(s){}}},"0299":function(t,e,n){"use strict";var r,o="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),i=64,s={},a=0,c=0;function u(t){var e="";do{e=o[t%i]+e,t=Math.floor(t/i)}while(t>0);return e}function f(t){var e=0;for(c=0;c{this.opts.autoUnref&&this.ws._socket.unref(),this.onOpen()},this.ws.onclose=this.onClose.bind(this),this.ws.onmessage=t=>this.onData(t.data),this.ws.onerror=t=>this.onError("websocket error",t)}write(t){const n=this;this.writable=!1;let r=t.length,i=0;const s=r;for(;i{this.doWrite(t,()=>{this.writable=!0,this.emit("drain")})})}uri(){let t=this.query||{};const e=this.opts.secure?"https":"http";let n="";!1!==this.opts.timestampRequests&&(t[this.opts.timestampParam]=s()),this.supportsBinary||t.sid||(t.b64=1),t=o.encode(t),this.opts.port&&("https"===e&&443!==Number(this.opts.port)||"http"===e&&80!==Number(this.opts.port))&&(n=":"+this.opts.port),t.length&&(t="?"+t);const r=-1!==this.opts.hostname.indexOf(":");return e+"://"+(r?"["+this.opts.hostname+"]":this.opts.hostname)+n+this.opts.path+t}}t.exports=c},"0a5e":function(t,e,n){const r=n("0949"),o=n("d941"),i=/\n/g,s=/\\n/g;let a;class c extends r{constructor(t){super(t),this.query=this.query||{},a||(a=o.___eio=o.___eio||[]),this.index=a.length;const e=this;a.push((function(t){e.onData(t)})),this.query.j=this.index}get supportsBinary(){return!1}doClose(){this.script&&(this.script.onerror=()=>{},this.script.parentNode.removeChild(this.script),this.script=null),this.form&&(this.form.parentNode.removeChild(this.form),this.form=null,this.iframe=null),super.doClose()}doPoll(){const t=this,e=document.createElement("script");this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),e.async=!0,e.src=this.uri(),e.onerror=function(e){t.onError("jsonp poll error",e)};const n=document.getElementsByTagName("script")[0];n?n.parentNode.insertBefore(e,n):(document.head||document.body).appendChild(e),this.script=e;const r="undefined"!==typeof navigator&&/gecko/i.test(navigator.userAgent);r&&setTimeout((function(){const t=document.createElement("iframe");document.body.appendChild(t),document.body.removeChild(t)}),100)}doWrite(t,e){const n=this;let r;if(!this.form){const t=document.createElement("form"),e=document.createElement("textarea"),n=this.iframeId="eio_iframe_"+this.index;t.className="socketio",t.style.position="absolute",t.style.top="-1000px",t.style.left="-1000px",t.target=n,t.method="POST",t.setAttribute("accept-charset","utf-8"),e.name="d",t.appendChild(e),document.body.appendChild(t),this.form=t,this.area=e}function o(){a(),e()}function a(){if(n.iframe)try{n.form.removeChild(n.iframe)}catch(t){n.onError("jsonp polling iframe removal error",t)}try{const t='