From c17ea4d91c052dc4f5063ae0f11a40dd40c964b8 Mon Sep 17 00:00:00 2001 From: dolevf Date: Tue, 29 Sep 2020 23:29:08 -0400 Subject: [PATCH] blueprints --- .gitignore | 1 + core/security.py | 35 +++ core/workers.py | 22 ++ db/db_paths.py | 4 +- main.py | 469 ++++++--------------------------------- templates/console.html | 2 +- templates/dashboard.html | 23 +- templates/sidebar.html | 2 +- version.py | 2 +- views/view_assessment.py | 10 + views/view_assets.py | 13 ++ views/view_console.py | 10 + views/view_dashboard.py | 34 +++ views/view_docs.py | 10 + views/view_download.py | 65 ++++++ views/view_health.py | 0 views/view_index.py | 21 ++ views/view_login.py | 37 +++ views/view_logout.py | 20 ++ views/view_qs.py | 43 ++++ views/view_reports.py | 10 + views/view_scan.py | 27 +++ views/view_settings.py | 54 +++++ views/view_startover.py | 13 ++ views/view_stream.py | 19 ++ views/view_topology.py | 16 ++ views/view_vulns.py | 12 + views/view_welcome.py | 11 + views_api/api_health.py | 5 + views_api/api_scan.py | 58 +++++ 30 files changed, 628 insertions(+), 420 deletions(-) create mode 100644 core/security.py create mode 100644 core/workers.py create mode 100644 views/view_assessment.py create mode 100644 views/view_assets.py create mode 100644 views/view_console.py create mode 100644 views/view_dashboard.py create mode 100644 views/view_docs.py create mode 100644 views/view_download.py create mode 100644 views/view_health.py create mode 100644 views/view_index.py create mode 100644 views/view_login.py create mode 100644 views/view_logout.py create mode 100644 views/view_qs.py create mode 100644 views/view_reports.py create mode 100644 views/view_scan.py create mode 100644 views/view_settings.py create mode 100644 views/view_startover.py create mode 100644 views/view_stream.py create mode 100644 views/view_topology.py create mode 100644 views/view_vulns.py create mode 100644 views/view_welcome.py create mode 100644 views_api/api_health.py create mode 100644 views_api/api_scan.py diff --git a/.gitignore b/.gitignore index f559b3e..fd6e31b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dump.rdb reports/ **/reports/*.html **/logs/nerve.log +tests/ \ No newline at end of file diff --git a/core/security.py b/core/security.py new file mode 100644 index 0000000..ff6b4fb --- /dev/null +++ b/core/security.py @@ -0,0 +1,35 @@ +import config + +from core.redis import rds +from flask import session, redirect, request +from flask_httpauth import HTTPBasicAuth +from functools import wraps + +from werkzeug.security import ( + generate_password_hash, + check_password_hash +) + +auth = HTTPBasicAuth() + +@auth.verify_password +def verify_password(username, password): + if rds.is_ip_blocked(request.remote_addr): + return False + + if username == config.WEB_USER and \ + check_password_hash(generate_password_hash(config.WEB_PASSW), password): + return True + + rds.log_attempt(request.remote_addr) + return False + +def session_required(function_to_protect): + @wraps(function_to_protect) + def wrapper(*args, **kwargs): + if not session.get('session'): + return redirect('/login', 307) + + return function_to_protect(*args, **kwargs) + return wrapper + \ No newline at end of file diff --git a/core/workers.py b/core/workers.py new file mode 100644 index 0000000..87ec8f5 --- /dev/null +++ b/core/workers.py @@ -0,0 +1,22 @@ +import threading + +from bin.scanner import scanner +from bin.attacker import attacker +from bin.scheduler import scheduler + + +def start_workers(): + thread = threading.Thread(target=scanner) + thread.name = "scanner" + thread.daemon = True + thread.start() + + thread = threading.Thread(target=attacker) + thread.name = "attacker" + thread.daemon = True + thread.start() + + thread = threading.Thread(target=scheduler) + thread.name = "scheduler" + thread.daemon = True + thread.start() \ No newline at end of file diff --git a/db/db_paths.py b/db/db_paths.py index bfb0a5e..0a4bfff 100644 --- a/db/db_paths.py +++ b/db/db_paths.py @@ -8,4 +8,6 @@ '/documents', '/backup', '/backups', '/data' ] -COMMON_LOGIN_PATHS = ['/', '/login', '/remote/login', '/admin', '/administrator', '/panel', '/dashboard', '/adm', '/members', '/private', '/manager'] \ No newline at end of file +COMMON_LOGIN_PATHS = ['/', '/login', '/remote/login', '/admin', '/administrator', '/panel', '/dashboard', '/adm', '/members', '/private', '/manager'] + +COMMON_CGI_PATHS = ['/cgi-bin/status', '/cgi-bin', '/cgi-bin/php', '/cgi-bin/php5', '/cgi-bin/php4'] \ No newline at end of file diff --git a/main.py b/main.py index c12a029..4da6311 100644 --- a/main.py +++ b/main.py @@ -1,409 +1,78 @@ -import time -import json import config import os -import threading -import copy -from flask import ( - Flask, - request, - session, - redirect, - render_template, - make_response, - send_from_directory, - flash - ) - -from flask_httpauth import HTTPBasicAuth -from flask_restful import Resource, Api -from werkzeug.security import ( - generate_password_hash, - check_password_hash -) -from functools import wraps -from core.logging import logger -from core.register import Register +from flask import Flask +from flask_restful import Api from core.redis import rds -from core.parser import SchemaParser -from core.utils import Utils, Charts -from core.mailer import send_email -from core.reports import ( - generate_html, - generate_csv, - generate_txt -) -from bin.scanner import scanner -from bin.attacker import attacker -from bin.scheduler import scheduler -from version import VERSION +from core.workers import start_workers +from version import VERSION + +# Import Blueprints +from views.view_index import index +from views.view_docs import documentation +from views.view_dashboard import dashboard +from views.view_reports import reports +from views.view_assessment import assessment +from views.view_topology import topology +from views.view_assets import assets +from views.view_welcome import welcome +from views.view_qs import qs +from views.view_login import login +from views.view_console import console +from views.view_logout import logout +from views.view_download import download +from views.view_stream import stream +from views.view_settings import settings +from views.view_scan import scan +from views.view_vulns import vulns +from views.view_startover import startover + +# Import REST API Endpoints +from views_api.api_health import Health +from views_api.api_scan import Scan -auth = HTTPBasicAuth() app = Flask(__name__) + +# Initialize Blueprints +app.register_blueprint(index) +app.register_blueprint(login) +app.register_blueprint(logout) +app.register_blueprint(welcome) +app.register_blueprint(download) +app.register_blueprint(assets) +app.register_blueprint(stream) +app.register_blueprint(console) +app.register_blueprint(documentation) +app.register_blueprint(dashboard) +app.register_blueprint(qs) +app.register_blueprint(reports) +app.register_blueprint(assessment) +app.register_blueprint(topology) +app.register_blueprint(vulns) +app.register_blueprint(settings) +app.register_blueprint(scan) +app.register_blueprint(startover) + app.config.update( SESSION_COOKIE_SAMESITE='Strict', ) app.secret_key = os.urandom(24) api = Api(app) -register = Register() -utils = Utils() - -def session_required(function_to_protect): - @wraps(function_to_protect) - def wrapper(*args, **kwargs): - if not session.get('session'): - return redirect('/login', 307) - - return function_to_protect(*args, **kwargs) - return wrapper - -def run_workers(): - thread = threading.Thread(target=scanner) - thread.name = "scanner" - thread.daemon = True - thread.start() - - thread = threading.Thread(target=attacker) - thread.name = "attacker" - thread.daemon = True - thread.start() - - thread = threading.Thread(target=scheduler) - thread.name = "scheduler" - thread.daemon = True - thread.start() - -@auth.verify_password -def verify_password(username, password): - if rds.is_ip_blocked(request.remote_addr): - return False - - if username == config.WEB_USER and \ - check_password_hash(generate_password_hash(config.WEB_PASSW), password): - return True - - rds.log_attempt(request.remote_addr) - return False - -@app.route('/') -@session_required -def index(): - # We set a cookie for "First time users" to show them the welcome message once. - if 'toggle_welcome' not in request.cookies: - resp = make_response(render_template('welcome.html')) - resp.set_cookie('toggle_welcome', 'true') - return resp - - return redirect('/dashboard') - -@app.route('/settings', methods=['GET', 'POST', 'DELETE']) -@session_required -def settings(): - email_settings = rds.get_email_settings() - slack_settings = rds.get_slack_settings() - - if request.method == 'POST': - u_settings = request.get_json() - - if u_settings and isinstance(u_settings, dict): - if 'email' in u_settings: - msg, code = send_email(u_settings['email']) - - elif 'slack' in u_settings: - hook = u_settings['slack'].get('hook', None) - if utils.is_string_url(hook): - rds.store('p_settings_slack', hook) - code, msg = 200, 'Saved Slack Setting' - else: - code, msg = 400, 'Slack hook must be a URL' - - else: - code, msg = 400, 'Error Occurred' - - return {'status': msg }, code - - elif request.method == 'DELETE': - u_settings = request.get_json() - settings = u_settings.get('settings', None) - if settings == 'email': - rds.delete('p_settings_email') - code, msg = 200, 'Deleted Email Settings' - elif settings == 'slack': - rds.delete('p_settings_slack') - code, msg = 200, 'Deleted Slack Settings' - else: - code, msg = 400, 'Error Occurred' - - return {'status': msg}, code - - return render_template('settings.html', - email=email_settings, - slack=slack_settings) - -@app.route('/dashboard') -@session_required -def dashboard(): - chart = Charts() - networks = [] - domains = [] - hosts = rds.get_topology() - cfg = rds.get_scan_config() - vulns = rds.get_vuln_data() - if cfg: - networks = cfg['targets']['networks'] - domains = cfg['targets']['domains'] - - return render_template('dashboard.html', - hosts=hosts, - networks=networks, - last_scan=rds.get_last_scan(), - scan_count=rds.get_scan_count(), - domains=domains, - vulns=vulns, - chart=chart.make_doughnut(vulns), - radar=chart.make_radar(vulns)) - -@app.route('/reports') -@session_required -def reports(): - return render_template('reports.html') - -@app.route('/documentation') -@session_required -def documentation(): - return render_template('documentation.html') - -@app.route('/qs', methods=['GET', 'POST']) -@session_required -def quickstart(): - if request.method == 'POST': - # In Quickstart, we only take the network provided by the user as input - # The rest is as defined in config.py - network = request.values.get('network') - - if network: - scan = copy.deepcopy(config.DEFAULT_SCAN) - scan['targets']['networks'].append(network) - schema = SchemaParser(scan, request) - vfd, msg, scan = schema.verify() - - if vfd: - res, code, msg = register.scan(scan) - if res: - logger.info('A scan was initiated') - flash('Assessment started.', 'success') - return redirect('/qs') - else: - flash(msg, 'error') - - else: - flash(msg, 'error') - - return render_template('quickstart.html') - -@app.route('/scan', methods=["POST"]) -@session_required -def start_scan(): - scan = request.get_json() - - if scan and isinstance(scan, dict): - schema = SchemaParser(scan, request) - vfd, msg, scan = schema.verify() - - if not vfd: - return {'status':'Error: ' + msg }, 400 - else: - return {'status':'Malformed Scan Data'}, 400 - - res, code, msg = register.scan(scan) - - return {'status': msg}, code - -@app.route('/topology') -@session_required -def topology(): - data = rds.get_topology() - vulns = rds.get_vuln_data() - return render_template('topology.html', data=data, vulns=vulns) - -@app.route('/startover') -@session_required -def startover(): - rds.clear_session() - flash('Rolled back successfully', 'success') - return redirect('/', 301) - -@app.route('/assessment') -@session_required -def assessment(): - return render_template('assessment.html') - -@app.route('/vulnerabilities') -@session_required -def vulnerabilities(): - data = rds.get_vuln_data() - return render_template('vulnerabilities.html', data=data) - -@app.route('/assets') -@session_required -def assets(): - data = rds.get_inventory_data() - return render_template('assets.html', data=data) - -@app.route('/welcome') -@session_required -def welcome(): - return render_template('welcome.html') - -@app.route('/login', methods=['GET', 'POST']) -def login(): - msg = '' - if request.method == 'POST': - username = request.form.get('username', None) - password = request.form.get('password', None) - - if rds.is_ip_blocked(request.remote_addr): - return render_template('login.html', err='Your IP has been blocked.') - - if verify_password(username, password): - session['session'] = username - return redirect('/') - else: - return render_template('login.html', err='Incorrect username or password.') - - if not utils.is_version_latest(): - msg = 'New Version is Available' - return render_template('login.html', msg=msg) - -@app.route('/logout') -def logout(): - if session.get('session'): - session.pop('session') - - flash('Logged out successfully', 'success') - - #return render_template('login.html') - return redirect('/login') - -@app.route('/log') -@session_required -def log(): - return render_template('console.html') - -@app.route('/download/') -@session_required -def download(file): - if not file: - return {'status':'file is missing'}, 400 - - if file == 'server_log': - response = send_from_directory(directory='logs', - filename=config.WEB_LOG, - as_attachment=True, - cache_timeout=0) - return response - - else: - data = rds.get_vuln_data() - conf = rds.get_scan_config() - - if not data and not conf: - flash('There is no data in the system for report generation', 'error') - return redirect('/reports') - - if file == 'report_html': - report_file = generate_html(data, conf) - response = send_from_directory(directory='reports', - filename=report_file, - as_attachment=True, - cache_timeout=0) - return response - - elif file == 'report_txt': - report_file = generate_txt(data) - response = send_from_directory(directory='reports', - filename=report_file, - as_attachment=True, - cache_timeout=0) - return response - elif file == 'report_csv': - report_file = generate_csv(data) - response = send_from_directory(directory='reports', - filename=report_file, - as_attachment=True, - cache_timeout=0) - return response - -@app.route('/stream') -@session_required -def stream(): - def generate(): - with open('logs/nerve.log') as f: - while True: - yield f.read() - time.sleep(1) - - return app.response_class(generate(), mimetype='text/plain') - -class Health(Resource): - def get(self): - return {'status': 'OK'} - -class Scan(Resource): - @auth.login_required - def get(self, action=None): - if not action: - return {'status':'action type is missing'}, 400 - if action == 'status': - state = rds.get_session_state() - data = rds.get_vuln_data() - cfg = rds.get_scan_config() - - if not state: - state = 'idle' - - return {'status':state, 'vulnerabilities':data, 'scan_config':cfg} - - return {'status':'unsupported action'}, 400 - - def put(self, action=None): - if action == 'reset': - rds.clear_session() - return {'status':'flushed scan state'} - - return {'status':'unsupported action'}, 400 - - @auth.login_required - def post(self, action=None): - scan = request.get_json() - if scan and isinstance(scan, dict): - schema = SchemaParser(scan, request) - vfd, msg, scan = schema.verify() - if not vfd: - return {'status':'Error: ' + msg }, 400 - else: - return {'status':'Malformed Scan Data'}, 400 - - res, code, msg = register.scan(scan) - - return {'status': msg}, code - - @auth.login_required - def delete(self): - rds.flushdb() - return {'status':'OK'} - -api.add_resource(Health, - '/health') - -api.add_resource(Scan, - '/api/scan', - '/api/scan/' - ) - +api.add_resource(Health, '/health') +api.add_resource(Scan, '/api/scan', '/api/scan/') +# Set Security Headers +@app.after_request +def add_security_headers(resp): + if config.WEB_SECURITY: + resp.headers['Content-Security-Policy'] = config.WEB_SEC_HEADERS['CSP'] + resp.headers['X-Content-Type-Options'] = config.WEB_SEC_HEADERS['CTO'] + resp.headers['X-XSS-Protection'] = config.WEB_SEC_HEADERS['XSS'] + resp.headers['X-Frame-Options'] = config.WEB_SEC_HEADERS['XFO'] + resp.headers['Referrer-Policy'] = config.WEB_SEC_HEADERS['RP'] + resp.headers['Server'] = config.WEB_SEC_HEADERS['Server'] + return resp # Context Processors @app.context_processor @@ -435,21 +104,9 @@ def show_frequency(): return dict(frequency=scan_frequency) -# Set Security Headers -@app.after_request -def add_security_headers(resp): - if config.WEB_SECURITY: - resp.headers['Content-Security-Policy'] = config.WEB_SEC_HEADERS['CSP'] - resp.headers['X-Content-Type-Options'] = config.WEB_SEC_HEADERS['CTO'] - resp.headers['X-XSS-Protection'] = config.WEB_SEC_HEADERS['XSS'] - resp.headers['X-Frame-Options'] = config.WEB_SEC_HEADERS['XFO'] - resp.headers['Referrer-Policy'] = config.WEB_SEC_HEADERS['RP'] - resp.headers['Server'] = config.WEB_SEC_HEADERS['Server'] - return resp - if __name__ == '__main__': rds.initialize() - run_workers() + start_workers() app.run(debug = config.WEB_DEBUG, host = config.WEB_HOST, port = config.WEB_PORT, diff --git a/templates/console.html b/templates/console.html index 891989b..dc05763 100644 --- a/templates/console.html +++ b/templates/console.html @@ -95,7 +95,7 @@

Console

+ + +{% if vulns %} +{% endif %} - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - - {% endif %} - {% endwith %} +{% with messages = get_flashed_messages(with_categories=true) %} +{% if messages %} + {% endif %} +{% endwith %} + \ No newline at end of file diff --git a/templates/sidebar.html b/templates/sidebar.html index eebc3b7..7e509b7 100644 --- a/templates/sidebar.html +++ b/templates/sidebar.html @@ -27,7 +27,7 @@ Reports
  • - Console + Console
  • diff --git a/version.py b/version.py index 87e043c..8af1c58 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -VERSION = '2.4.6' +VERSION = '2.5.0' diff --git a/views/view_assessment.py b/views/view_assessment.py new file mode 100644 index 0000000..b2753a7 --- /dev/null +++ b/views/view_assessment.py @@ -0,0 +1,10 @@ +from core.security import session_required +from flask import Blueprint, render_template + +assessment = Blueprint('assessment', __name__, + template_folder='templates') + +@assessment.route('/assessment') +@session_required +def view_assessment(): + return render_template('assessment.html') diff --git a/views/view_assets.py b/views/view_assets.py new file mode 100644 index 0000000..944fd61 --- /dev/null +++ b/views/view_assets.py @@ -0,0 +1,13 @@ +from core.security import session_required +from flask import Blueprint, render_template +from core.redis import rds + + +assets = Blueprint('assets', __name__, + template_folder='templates') + +@assets.route('/assets') +@session_required +def view_assets(): + data = rds.get_inventory_data() + return render_template('assets.html', data=data) \ No newline at end of file diff --git a/views/view_console.py b/views/view_console.py new file mode 100644 index 0000000..531a530 --- /dev/null +++ b/views/view_console.py @@ -0,0 +1,10 @@ +from core.security import session_required +from flask import Blueprint, render_template + +console = Blueprint('console', __name__, + template_folder='templates') + +@console.route('/console') +@session_required +def view_console(): + return render_template('console.html') diff --git a/views/view_dashboard.py b/views/view_dashboard.py new file mode 100644 index 0000000..e2aefb0 --- /dev/null +++ b/views/view_dashboard.py @@ -0,0 +1,34 @@ +from core.security import session_required +from core.utils import Utils, Charts +from core.redis import rds + +from flask import Blueprint, render_template + + +dashboard = Blueprint('dashboard', __name__, + template_folder='templates') + +@dashboard.route('/dashboard') +@session_required +def view_dashboard(): + chart = Charts() + networks = [] + domains = [] + + hosts = rds.get_topology() + cfg = rds.get_scan_config() + vulns = rds.get_vuln_data() + + if cfg: + networks = cfg['targets']['networks'] + domains = cfg['targets']['domains'] + + return render_template('dashboard.html', + hosts=hosts, + networks=networks, + last_scan=rds.get_last_scan(), + scan_count=rds.get_scan_count(), + domains=domains, + vulns=vulns, + chart=chart.make_doughnut(vulns), + radar=chart.make_radar(vulns)) diff --git a/views/view_docs.py b/views/view_docs.py new file mode 100644 index 0000000..af165ab --- /dev/null +++ b/views/view_docs.py @@ -0,0 +1,10 @@ +from core.security import session_required +from flask import Blueprint, render_template + +documentation = Blueprint('documentation', __name__, + template_folder='templates') + +@documentation.route('/documentation') +@session_required +def view_doc(): + return render_template('documentation.html') diff --git a/views/view_download.py b/views/view_download.py new file mode 100644 index 0000000..4e86699 --- /dev/null +++ b/views/view_download.py @@ -0,0 +1,65 @@ +import config + +from core.redis import rds +from core.security import session_required + +from core.reports import ( + generate_html, + generate_csv, + generate_txt +) + +from flask import ( + Blueprint, + request, + flash, + redirect, + send_from_directory +) + +download = Blueprint('download', __name__, + template_folder='templates') + +@download.route('/download/') +@session_required +def view_download(file): + if not file: + return {'status':'file is missing'}, 400 + + if file == 'server_log': + response = send_from_directory(directory='logs', + filename=config.WEB_LOG, + as_attachment=True, + cache_timeout=0) + return response + + else: + data = rds.get_vuln_data() + conf = rds.get_scan_config() + + if not data and not conf: + flash('There is no data in the system for report generation', 'error') + return redirect('/reports') + + if file == 'report_html': + report_file = generate_html(data, conf) + response = send_from_directory(directory='reports', + filename=report_file, + as_attachment=True, + cache_timeout=0) + return response + + elif file == 'report_txt': + report_file = generate_txt(data) + response = send_from_directory(directory='reports', + filename=report_file, + as_attachment=True, + cache_timeout=0) + return response + elif file == 'report_csv': + report_file = generate_csv(data) + response = send_from_directory(directory='reports', + filename=report_file, + as_attachment=True, + cache_timeout=0) + return response diff --git a/views/view_health.py b/views/view_health.py new file mode 100644 index 0000000..e69de29 diff --git a/views/view_index.py b/views/view_index.py new file mode 100644 index 0000000..5a62546 --- /dev/null +++ b/views/view_index.py @@ -0,0 +1,21 @@ +from core.security import session_required +from flask import ( + Blueprint, + render_template, + request, + redirect, + make_response +) + +index = Blueprint('index', __name__, + template_folder='templates') + +@index.route('/') +@session_required +def view_index(): + if 'toggle_welcome' not in request.cookies: + resp = make_response(render_template('welcome.html')) + resp.set_cookie('toggle_welcome', 'true') + return resp + + return redirect('/dashboard') diff --git a/views/view_login.py b/views/view_login.py new file mode 100644 index 0000000..51ae1b5 --- /dev/null +++ b/views/view_login.py @@ -0,0 +1,37 @@ +from core.utils import Utils +from core.redis import rds +from core.security import verify_password + +from flask import ( + Blueprint, + render_template, + request, + session, + redirect +) + +login = Blueprint('login', __name__, + template_folder='templates') + +@login.route('/login', methods=['GET', 'POST']) +def view_login(): + utils = Utils() + msg = '' + + if request.method == 'POST': + username = request.form.get('username', None) + password = request.form.get('password', None) + + if rds.is_ip_blocked(request.remote_addr): + return render_template('login.html', err='Your IP has been blocked.') + + if verify_password(username, password): + session['session'] = username + return redirect('/') + else: + return render_template('login.html', err='Incorrect username or password.') + + if not utils.is_version_latest(): + msg = 'New Version is Available' + + return render_template('login.html', msg=msg) \ No newline at end of file diff --git a/views/view_logout.py b/views/view_logout.py new file mode 100644 index 0000000..5e0e955 --- /dev/null +++ b/views/view_logout.py @@ -0,0 +1,20 @@ +from core.security import session_required +from flask import ( + Blueprint, + redirect, + flash, + session +) + +logout = Blueprint('logout', __name__, + template_folder='templates') + +@logout.route('/logout') +@session_required +def view_logout(): + if session.get('session'): + session.pop('session') + + flash('Logged out successfully', 'success') + + return redirect('/login') diff --git a/views/view_qs.py b/views/view_qs.py new file mode 100644 index 0000000..17f5acb --- /dev/null +++ b/views/view_qs.py @@ -0,0 +1,43 @@ +import copy +import config + +from core.security import session_required +from core.redis import rds +from core.parser import SchemaParser +from core.register import Register +from core.logging import logger + +from flask import Blueprint, render_template, flash, request, redirect + +qs = Blueprint('qs', __name__, + template_folder='templates') + +@qs.route('/qs', methods=['GET', 'POST']) +@session_required +def view_qs(): + if request.method == 'POST': + register = Register() + # In Quickstart, we only take the network provided by the user as input + # The rest is as defined in config.py + network = request.values.get('network') + + if network: + scan = copy.deepcopy(config.DEFAULT_SCAN) + scan['targets']['networks'].append(network) + schema = SchemaParser(scan, request) + vfd, msg, scan = schema.verify() + + if vfd: + res, code, msg = register.scan(scan) + if res: + logger.info('A scan was initiated') + flash('Assessment started.', 'success') + return redirect('/qs') + else: + flash(msg, 'error') + + else: + flash(msg, 'error') + + return render_template('quickstart.html') + \ No newline at end of file diff --git a/views/view_reports.py b/views/view_reports.py new file mode 100644 index 0000000..7f8cf84 --- /dev/null +++ b/views/view_reports.py @@ -0,0 +1,10 @@ +from core.security import session_required +from flask import Blueprint, render_template + +reports = Blueprint('reports', __name__, + template_folder='templates') + +@reports.route('/reports') +@session_required +def view_reports(): + return render_template('reports.html') diff --git a/views/view_scan.py b/views/view_scan.py new file mode 100644 index 0000000..4d36a3f --- /dev/null +++ b/views/view_scan.py @@ -0,0 +1,27 @@ +from core.security import session_required +from core.register import Register +from core.parser import SchemaParser + +from flask import Blueprint, request + +scan = Blueprint('scan', __name__, + template_folder='templates') + +@scan.route('/scan', methods=["POST"]) +@session_required +def view_scan(): + register = Register() + scan = request.get_json() + + if scan and isinstance(scan, dict): + schema = SchemaParser(scan, request) + vfd, msg, scan = schema.verify() + + if not vfd: + return {'status':'Error: ' + msg }, 400 + else: + return {'status':'Malformed Scan Data'}, 400 + + res, code, msg = register.scan(scan) + + return {'status': msg}, code diff --git a/views/view_settings.py b/views/view_settings.py new file mode 100644 index 0000000..2f44e86 --- /dev/null +++ b/views/view_settings.py @@ -0,0 +1,54 @@ +from core.redis import rds +from core.utils import Utils +from core.security import session_required +from core.mailer import send_email + +from flask import Blueprint, render_template, request + +settings = Blueprint('settings', __name__, + template_folder='templates') + +@settings.route('/settings', methods=['GET', 'POST', 'DELETE']) +@session_required +def view_settings(): + utils = Utils() + email_settings = rds.get_email_settings() + slack_settings = rds.get_slack_settings() + + if request.method == 'POST': + u_settings = request.get_json() + + if u_settings and isinstance(u_settings, dict): + if 'email' in u_settings: + msg, code = send_email(u_settings['email']) + + elif 'slack' in u_settings: + hook = u_settings['slack'].get('hook', None) + if utils.is_string_url(hook): + rds.store('p_settings_slack', hook) + code, msg = 200, 'Saved Slack Setting' + else: + code, msg = 400, 'Slack hook must be a URL' + + else: + code, msg = 400, 'Error Occurred' + + return {'status': msg }, code + + elif request.method == 'DELETE': + u_settings = request.get_json() + settings = u_settings.get('settings', None) + if settings == 'email': + rds.delete('p_settings_email') + code, msg = 200, 'Deleted Email Settings' + elif settings == 'slack': + rds.delete('p_settings_slack') + code, msg = 200, 'Deleted Slack Settings' + else: + code, msg = 400, 'Error Occurred' + + return {'status': msg}, code + + return render_template('settings.html', + email=email_settings, + slack=slack_settings) diff --git a/views/view_startover.py b/views/view_startover.py new file mode 100644 index 0000000..fb997f1 --- /dev/null +++ b/views/view_startover.py @@ -0,0 +1,13 @@ +from core.redis import rds +from core.security import session_required +from flask import Blueprint, flash, redirect + +startover = Blueprint('startover', __name__, + template_folder='templates') + +@startover.route('/startover') +@session_required +def view_startover(): + rds.clear_session() + flash('Rolled back successfully', 'success') + return redirect('/dashboard', 301) diff --git a/views/view_stream.py b/views/view_stream.py new file mode 100644 index 0000000..b5de325 --- /dev/null +++ b/views/view_stream.py @@ -0,0 +1,19 @@ +import time + +from core.security import session_required +from flask import Blueprint, Response, stream_with_context + +stream = Blueprint('stream', __name__, + template_folder='templates') + +@stream.route('/log') +@session_required +def view_stream(): + def generate(): + with open('logs/nerve.log') as f: + while True: + yield f.read() + time.sleep(1) + + #return stream.view_stream.response_class(generate(), mimetype='text/plain') + return Response(stream_with_context(generate()), mimetype='text/plain') diff --git a/views/view_topology.py b/views/view_topology.py new file mode 100644 index 0000000..cac5c2a --- /dev/null +++ b/views/view_topology.py @@ -0,0 +1,16 @@ +from core.security import session_required +from core.utils import Utils, Charts +from core.redis import rds + +from flask import Blueprint, render_template + +topology = Blueprint('topology', __name__, + template_folder='templates') + +@topology.route('/topology') +@session_required +def view_topologys(): + data = rds.get_topology() + vulns = rds.get_vuln_data() + return render_template('topology.html', data=data, vulns=vulns) + diff --git a/views/view_vulns.py b/views/view_vulns.py new file mode 100644 index 0000000..9fba75d --- /dev/null +++ b/views/view_vulns.py @@ -0,0 +1,12 @@ +from core.security import session_required +from flask import Blueprint, render_template +from core.redis import rds + +vulns = Blueprint('vulnerabilities', __name__, + template_folder='templates') + +@vulns.route('/vulnerabilities') +@session_required +def view_vulns(): + data = rds.get_vuln_data() + return render_template('vulnerabilities.html', data=data) diff --git a/views/view_welcome.py b/views/view_welcome.py new file mode 100644 index 0000000..721f152 --- /dev/null +++ b/views/view_welcome.py @@ -0,0 +1,11 @@ +from core.security import session_required +from flask import Blueprint, render_template +from core.redis import rds + +welcome = Blueprint('welcome', __name__, + template_folder='templates') + +@welcome.route('/welcome') +@session_required +def view_welcome(): + return render_template('welcome.html', data=data) \ No newline at end of file diff --git a/views_api/api_health.py b/views_api/api_health.py new file mode 100644 index 0000000..f0b6a58 --- /dev/null +++ b/views_api/api_health.py @@ -0,0 +1,5 @@ +from flask_restful import Resource + +class Health(Resource): + def get(self): + return {'status': 'OK'} \ No newline at end of file diff --git a/views_api/api_scan.py b/views_api/api_scan.py new file mode 100644 index 0000000..ca42109 --- /dev/null +++ b/views_api/api_scan.py @@ -0,0 +1,58 @@ +from core.redis import rds +from core.parser import SchemaParser +from core.register import Register +#from core.security import auth, session_required, verify_password +from core.security import auth + +from flask_restful import Resource + +from flask import ( + request +) + +class Scan(Resource): + @auth.login_required + def get(self, action=None): + if not action: + return {'status':'action type is missing'}, 400 + + if action == 'status': + state = rds.get_session_state() + data = rds.get_vuln_data() + cfg = rds.get_scan_config() + + if not state: + state = 'idle' + + return {'status':state, 'vulnerabilities':data, 'scan_config':cfg} + + return {'status':'unsupported action'}, 400 + + def put(self, action=None): + if action == 'reset': + rds.clear_session() + return {'status':'flushed scan state'} + + return {'status':'unsupported action'}, 400 + + @auth.login_required + def post(self, action=None): + scan = request.get_json() + register = Register() + + if scan and isinstance(scan, dict): + schema = SchemaParser(scan, request) + vfd, msg, scan = schema.verify() + if not vfd: + return {'status':'Error: ' + msg }, 400 + else: + return {'status':'Malformed Scan Data'}, 400 + + res, code, msg = register.scan(scan) + + return {'status': msg}, code + + @auth.login_required + def delete(self): + rds.flushdb() + return {'status':'OK'} \ No newline at end of file