diff --git a/flask_soc_site/__init__.py b/flask_soc_site/__init__.py new file mode 100644 index 0000000..b223897 --- /dev/null +++ b/flask_soc_site/__init__.py @@ -0,0 +1,32 @@ +import os +from flask import Flask, redirect, url_for, render_template +from dotenv import load_dotenv + +# Instead of turning 'lookup' into a Blueprint, maybe I could make a history of previous lookups a Blueprint? + + +def create_app(test_config=None): + app = Flask(__name__) + app.config.from_pyfile("config.py") + load_dotenv() + app.secret_key = os.getenv("SECRET_KEY") + + if test_config is None: + app.config.from_pyfile("config.py", silent=True) + else: + app.config.update(test_config) + + try: + os.makedirs(app.instance_path) + except OSError: + pass + + @app.route("/") + def index(): + return render_template("index.html") + + from . import lookup + + app.register_blueprint(lookup.bp) + + return app diff --git a/flask_soc_site/config.py b/flask_soc_site/config.py new file mode 100644 index 0000000..e69de29 diff --git a/flask_soc_site/lookup.py b/flask_soc_site/lookup.py new file mode 100644 index 0000000..c296d0f --- /dev/null +++ b/flask_soc_site/lookup.py @@ -0,0 +1,31 @@ +from flask import Blueprint, request, render_template, flash +from .src import host_lookup +from markupsafe import escape + +bp = Blueprint("lookup", __name__) + + +@bp.route("/lookup", methods=(["GET", "POST"])) +def lookup(): + hosts = [] + results = [] + if request.method == "GET": + return render_template("lookup.html") + elif request.method == "POST" and any(request.form.get("host")): + print(f'REQUEST.FORM.GET("HOST") IS {request.form.get("host")}') + user_input = escape(request.form.get("host").strip()) + hosts, errors = host_lookup.process_input(user_input) + for host in hosts: + result = host_lookup.Lookedup(host) + results.append(result) + return render_template("results.html", hosts=results, errors=errors) + else: + flash("Nothing entered! Try again?", "error") + return render_template("lookup.html") + + +# elif request.method == "POST" and "file" in request.files: +# file = request.files["file"] +# extracted = upload.extract(file) +# hosts = host_lookup.process_file(extracted) +# return render_template("results.html", hosts=hosts) diff --git a/flask_soc_site/src/abuseipdb_api.py b/flask_soc_site/src/abuseipdb_api.py new file mode 100644 index 0000000..df6260f --- /dev/null +++ b/flask_soc_site/src/abuseipdb_api.py @@ -0,0 +1,52 @@ +from base64 import decode +import json +import os +import requests +import requests_cache +from dotenv import load_dotenv +from pprint import pprint + + +class API_error(Exception): + pass + + +def environment(): + requests_cache.install_cache(expire_after=360, allowable_methods=("POST")) + load_dotenv() + api_key = os.getenv("ABUSEIPDB_API") + return api_key + + +def lookup(api_key, host): + url = "https://api.abuseipdb.com/api/v2/check" + payload = {"ipAddress": "", "maxAgeInDays": "90"} + payload.update({"ipAddress": host}) + headers = {"Accept": "application/json", "Key": api_key} + response = requests.request( + method="GET", url=url, params=payload, headers=headers, verify=False + ) # TODO: remove SSL verify=False and add signed certificate if possible. + # Figure out how caching functions here: https://requests-cache.readthedocs.io/en/stable/examples.html + response_dict = json.loads(response.text) + lookup = dict.fromkeys( + ["score", "last_reported", "IP_address", "CDN", "Tor", "total_reports"] + ) + print(response_dict) + lookup["score"] = response_dict["data"]["abuseConfidenceScore"] + lookup["last_reported"] = response_dict["data"]["lastReportedAt"] + lookup["IP_address"] = response_dict["data"]["ipAddress"] + lookup["usage"] = response_dict["data"]["usageType"] + lookup["Tor"] = response_dict["data"]["isTor"] + lookup["total_reports"] = response_dict["data"]["totalReports"] + + print(requests_cache.get_cache()) + print("Cached:") + print("\n".join(requests_cache.get_cache().urls())) + + return lookup + + +def analyse(host): + api_key = environment() + result = lookup(api_key, host) + return result diff --git a/flask_soc_site/src/constants.py b/flask_soc_site/src/constants.py new file mode 100644 index 0000000..c7c46e5 --- /dev/null +++ b/flask_soc_site/src/constants.py @@ -0,0 +1,30 @@ +LOGCONF = { + "version": 1, + "formatters": { + "default": { + "format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s", + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + "formatter": "default", + }, + "file": { + "class": "logging.FileHandler", + "filename": "flask.log", + "formatter": "default", + }, + }, + "root": {"level": "DEBUG", "handlers": ["console", "file"]}, +} + +EMAIL = "email address" +URL = "url" +DOMAIN = "domain" +IPV4 = "ipv4" +IPV6 = "ipv6" +NO_HOST = "no host" + +domain_lookup = {} diff --git a/flask_soc_site/src/host_data.py b/flask_soc_site/src/host_data.py new file mode 100644 index 0000000..044c693 --- /dev/null +++ b/flask_soc_site/src/host_data.py @@ -0,0 +1,43 @@ +from ipaddress import ip_address +from checkdmarc.dmarc import check_dmarc +from checkdmarc.spf import check_spf +import validators +from ipwhois import IPWhois +from whois import whois +from .constants import DOMAIN, EMAIL, IPV4, IPV6, URL + + +def determine(host): + host_type = "" + if validators.url(host): + host_type = URL + elif validators.domain(host): + host_type = DOMAIN + elif validators.ip_address.ipv4(host): + host_type = IPV4 + elif validators.ip_address.ipv6(host): + host_type = IPV6 + elif validators.email(host): + host_type = EMAIL + else: + print("NO HOST TYPE") + return host_type + + +def domain(host): + result = dict(whois(host)) + if type(result["domain_name"]) is list: + result["domain_name"] = result["domain_name"][0] + return result + + +def emailsec(host): + spf = "" + dmarc = "" + result_spf = check_spf(host) + if result_spf["valid"]: + spf = result_spf["record"] + result_dmarc = check_dmarc(host) + if result_dmarc["valid"]: + dmarc = result_dmarc["record"] + return spf, dmarc diff --git a/flask_soc_site/src/host_lookup.py b/flask_soc_site/src/host_lookup.py new file mode 100644 index 0000000..ccc19dd --- /dev/null +++ b/flask_soc_site/src/host_lookup.py @@ -0,0 +1,154 @@ +# TODO: make this module only have wrapper functions, no direct lookups or imports. Move all the direct lookup functions (emailsec) into (a) separate module(s). + +import re +from ipaddress import ip_address +from checkdmarc.dmarc import check_dmarc +from checkdmarc.spf import check_spf +import validators +from ipwhois import IPWhois +from whois import whois + +# from constants import DOMAIN, EMAIL, IPV4, IPV6, URL +from . import abuseipdb_api, virustotal_api +import socket +from urllib.parse import urlparse +from . import host_data +import tldextract + + +class Lookedup(object): + def __init__(self, host): + self.host = host + self.host_type = determine(self.host) + self = self.specific() + # TODO: consolidate all below functions if possible + + def url_lookup(self): + self.domain = urlparse(self.host).netloc + self.ip_address = socket.gethostbyname(self.domain) + self.metadata = domain(self.domain) + self.email_security = spf_dmarc(self.domain) + self.vt, self.vt_dict = virustotal_api.analyse2(self.host, self.host_type) + self.abuseipdb = abuseipdb_api.analyse(self.ip_address) + return self + + def ip_lookup(self): + self.metadata = domain(self.host) + self.domain = self.metadata["domain_name"] + self.email_security = spf_dmarc(self.domain) + self.vt, self.vt_dict = virustotal_api.analyse2(self.host, self.host_type) + self.abuseipdb = abuseipdb_api.analyse(self.host) + return self + + def domain_lookup(self): + self.ip_address = socket.gethostbyname(self.host) + self.metadata = domain(self.host) + self.domain = self.metadata["domain_name"] + self.email_security = spf_dmarc(self.domain) + self.vt, self.vt_dict = virustotal_api.analyse2(self.host, self.host_type) + self.abuseipdb = abuseipdb_api.analyse(self.ip_address) + return self + + def email_lookup(self): + self.domain = self.host.split("@")[1] + self.metadata = domain(self.domain) + self.ip_address = socket.gethostbyname(self.domain) + self.email_security = spf_dmarc(self.domain) + self.vt, self.vt_dict = virustotal_api.analyse2(self.domain, self.host_type) + self.abuseipdb = abuseipdb_api.analyse(self.ip_address) + return self + + def specific(self): + if self.host_type == "url": + return self.url_lookup() + elif self.host_type == "domain": + return self.domain_lookup() + elif self.host_type == "ip": + return self.ip_lookup() + elif self.host_type == "email address": + return self.email_lookup() + + +def sanitize(user_input): + sanitized = [] + if user_input.strip() != "": + sanitized = re.split("; |, | |\n", user_input) + return sanitized + + +def determine(host): + host_type = "" + if validators.url(host): + host_type = "url" + elif validators.domain(host): + host_type = "domain" + elif validators.ip_address.ipv4(host): + host_type = "ip" + elif validators.ip_address.ipv6(host): + host_type = "ip" + elif validators.email(host): + host_type = "email address" + else: + host_type = "no host" + return host_type + + +def extract(user_input): + hosts = [] + errors = [] + for item in user_input: + if validators.url(item): + hosts.append(item) + elif validators.domain(item): + hosts.append(item) + elif validators.ip_address.ipv4(item): + hosts.append(item) + elif validators.ip_address.ipv6(item): + hosts.append(item) + elif validators.email(item): + hosts.append(item) + else: + errors.append(item) + return hosts, errors + + +def domain(host): + result = dict(whois(host)) + if type(result["creation_date"]) is list: + result["creation_date"] = result["creation_date"][0].strftime("%d-%m-%Y") + else: + result["creation_date"] = result["creation_date"].strftime("%d-%m-%Y") + if type(result["domain_name"]) is list: + result["domain_name"] = result["domain_name"][0] + return result + + +def spf_dmarc(domain): + spf = "" + dmarc = "" + result_spf = check_spf(domain) + if result_spf["valid"]: + spf = result_spf["record"] + result_dmarc = check_dmarc(domain) + if result_dmarc["valid"]: + dmarc = result_dmarc["record"] + return spf, dmarc + + +def process_input(user): + results = [] + sanitized = sanitize(user) + hosts, errors = extract(sanitized) + # for host in hosts: + # host_analyzed = Host(host).lookup() + # results.append(host_analyzed) + # return results + return hosts, errors + + +def process_file(file_content): + hosts = [] + for host in file_content: + result = Host(host) + hosts.append(result) + return hosts diff --git a/flask_soc_site/src/virustotal_api.py b/flask_soc_site/src/virustotal_api.py new file mode 100644 index 0000000..3283593 --- /dev/null +++ b/flask_soc_site/src/virustotal_api.py @@ -0,0 +1,111 @@ +import json +import time +import os +import requests +from dotenv import load_dotenv +from .constants import URL, DOMAIN, IPV4, IPV6, domain_lookup + + +def environment(): + load_dotenv() + api_key = os.getenv("VT_API") + return api_key + + +def analysis_object(api_key, host): + url = "https://www.virustotal.com/api/v3/urls" + payload = {"url": ""} + payload.update({"url": host}) + headers = { + "accept": "application/json", + "content-type": "application/x-www-form-urlencoded", + "x-apikey": api_key, + } + response = requests.post(url, data=payload, headers=headers) + response_dict = json.loads(response.text) + response_id = response_dict["data"]["id"] + return response_id + + +def analyse_domain(api_key, host): + url = "https://www.virustotal.com/api/v3/domains/" + host + headers = { + "accept": "application/json", + "content-type": "application/x-www-form-urlencoded", + "x-apikey": api_key, + } + analysis_json = requests.get(url, headers=headers) + response_dict = json.loads(analysis_json.text) + return response_dict, analysis_json + + +def analyse_URL(api_key, response_id): + url = "https://www.virustotal.com/api/v3/analyses/{}".format(response_id) + headers = {"accept": "application/json", "x-apikey": api_key} + analysis_json = requests.get(url, headers=headers) + analysis_dict = json.loads(analysis_json.text) + return analysis_dict, analysis_json + + +# This returns a differently shaped JSON and therefore dict: +# analysis_dict keys 'data', 'meta' +# analysis_dict['data'] keys 'id', 'type', 'links', 'attributes' +# analysis_dict['data']['attributes'] keys 'stats', (numbers) 'results', (all the AV engine results) 'date', (Linux epoch timestamp) 'status' + + +def analyse_IP(api_key, host): + analysis_url = "https://www.virustotal.com/api/v3/ip_addresses/{}".format(host) + headers = {"accept": "application/json", "x-apikey": api_key} + analysis_json = requests.get(analysis_url, headers=headers) + response_dict = json.loads(analysis_json.text) + # Implement this: https://docs.virustotal.com/reference/ip-info + return response_dict, analysis_json + + +def analyse(host, host_type): + api_key = environment() + if host_type == URL: + response_id = analysis_object(api_key, host) + result, analysis_json = analyse_URL(api_key, response_id) + elif host_type == DOMAIN: + result, analysis_json = analyse_domain(api_key, host) + elif host_type == IPV4 or IPV6: + result, analysis_json = analyse_IP(api_key, host) + return result, analysis_json + + +def analyse2(host, host_type): + api_key = environment() + if host_type == "url": + response_id = analysis_object(api_key, host) + result, analysis_json = analyse_URL(api_key, response_id) + elif host_type == "domain" or host_type == "email address": + result, analysis_json = analyse_domain(api_key, host) + elif host_type == "ip": + result, analysis_json = analyse_IP(api_key, host) + if host_type == "url": + vt_stats = result["data"]["attributes"]["stats"] + vt_results = result["data"]["attributes"]["results"] + last_update = result["data"]["attributes"]["date"] + elif host_type == "domain" or host_type == "email address" or host_type == "ip": + vt_stats = result["data"]["attributes"]["last_analysis_stats"] + vt_results = result["data"]["attributes"]["last_analysis_results"] + last_update = result["data"]["attributes"]["last_analysis_date"] + + summary = dict.fromkeys(["total", "score", "vendors", "last_update"]) + total = 0 + vendors = [] + for key, value in vt_stats.items(): + total += value + for key, value in vt_results.items(): + if value["category"] == "malicious": + vendors.append(key) + + summary["total"] = total + summary["score"] = vt_stats["malicious"] + summary["vendors"] = vendors + summary["last_update"] = time.strftime( + "%d-%m-%Y", + time.gmtime(last_update), + ) + return summary, analysis_json diff --git a/flask_soc_site/static/background/cool-background-trianglify.png b/flask_soc_site/static/background/cool-background-trianglify.png new file mode 100644 index 0000000..fdd637d Binary files /dev/null and b/flask_soc_site/static/background/cool-background-trianglify.png differ diff --git a/flask_soc_site/static/javascript.js b/flask_soc_site/static/javascript.js new file mode 100644 index 0000000..73cdfca --- /dev/null +++ b/flask_soc_site/static/javascript.js @@ -0,0 +1,12 @@ +$(function() { + $("td[colspan=7]").find("p").hide(); + $("table").click(function(event) { + event.stopPropagation(); + var $target = $(event.target); + if ( $target.closest("td").attr("colspan") > 1 ) { + $target.slideUp(); + } else { + $target.closest("tr").next().find("p").slideToggle(); + } + }); +}); diff --git a/flask_soc_site/static/styles/style.css b/flask_soc_site/static/styles/style.css new file mode 100644 index 0000000..db82e32 --- /dev/null +++ b/flask_soc_site/static/styles/style.css @@ -0,0 +1,379 @@ +@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&display=swap'); + +body { + min-height: 100vh; + padding: 0; + margin: 0; + background-color: #17141d; + color: white; + font-family: 'DM Mono', monospace; +} + +.no_bullets { + list-style-type: none; + padding: 0; + margin: 0; +} + +.headings { + color: #fdc105; + display: flex; + justify-content: center; +} + +.lookup_container { + display: flex; + justify-content: center; + flex-direction: column; + margin: 10px auto; + +} + +.input_container { + /* padding: 30px 30px; */ + /* flex: 1 1 0; */ + display: grid; + justify-content: center; +} + +.forms_container { + display: grid; + justify-content: center; + align-items: center; + flex-direction: column; + /* padding: 75px 250px; */ + /* flex: 1 1 0; */ + color: #fff; + background-image: url(/static/background/cool-background-trianglify.png); + /* background: linear-gradient(to bottom right, red, yellow); */ + border: 0; + /* transition: 0.2s; */ + /* overflow: hidden; */ + width: min(25em, 100%); +} + +.input_form { + /* padding: 5px 5px; */ + /* display: flex; */ + margin: auto; +} + +.upload_form { + padding: 5px 5px; + display: flex; +} + +.grid_test_container { + display: grid; + grid-gap: 10px; + grid-template-columns: repeat(5, minmax(0, 1fr)); + grid-gap: 10px; + background-color: #2196F3; + padding: 10px; +} + +.grid_test_container>.grid_test { + color: blue; + grid-column-start: span 2, 3; + background-color: #ffc100; + text-align: center; + padding: 20px 0; + font-size: 30px; +} + +.flex_in_grid_container { + display: grid; + grid-template-columns: 1fr 1fr; + grid-auto-rows: 75px; + grid-gap: 10px; +} + +.flex_in_grid { + display: flex; + align-items: center; + justify-content: center; +} + +.grid_container>div { + background-color: rbga(255, 255, 255, 0.8); + text-align: center; + padding: 20px 0; + font-size: 30px; +} + +.test_container { + /* for some reason the line below keeps the form a little less wide? */ + display: grid; + justify-content: center; + justify-items: center; + margin: auto; + /* + -ms-box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + */ +} + +.test_input_container { + /* + display: grid; + justify-items: center; + justify-content: center; + width: min(25em, 100%); + */ + background-color: #ffc100; + /* + -ms-box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + */ + /* + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + */ + justify-content: center; + justify-items: center; + align-items: center; + text-align: center; + width: min(25em, 100%); + margin: auto; + +} + +.test_forms_container { + /* + display: grid; + justify-content: center; + justify-items: center; + /* background-image: url(/static/background/cool-background-trianglify.png);*/ + */ + /* + background-color: #ffc100; + */ +} + +.test_text_container { + display: grid; + justify-content: center; +} + +.test { + display: grid; + margin: 5px auto; + background-color: #ffc100; + justify-content: center; + justify-items: center; + width: min(25em, 100%); + text-align: center; +} + +.general_container { + display: flex; + justify-content: center; + flex-direction: column; +} + + + +.error_container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + /* padding: 75px 250px; */ + /* color: #fff; */ + border: 0; + /* transition: 0.2s; */ + /* overflow: hidden; */ +} + +.error_form_empty { + padding: 15px 15px; + display: flex; + border-style: dashed; + border-color: #fad253; + /* background-color: #fad253; */ + background-color: #ffc100; + border: 5; +} + +.howto_container { + /* + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin: 10px auto; + background-image: url(/static/background/cool-background-trianglify.png); + overflow: hidden; + */ + display: grid; + justify-content: center; + margin: auto; +} + +.howto_text { + /* + display: flex; + justify-content: center; + border-style: dashed; + border-color: #fad253; + background-color: #ffc100; + border: 5; + margin: 10px auto; + */ + display: grid; + margin: 5px auto; + background-color: #ffc100; + justify-content: center; + justify-items: center; + width: min(35em, 100%); +} + +.btn-warning { + position: relative; + padding: 11px 16px; + font-size: 15px; + line-height: 1.5; + border-radius: 3px; + color: #000000; + background-color: #ffc100; + border: 0; + transition: 0.2s; + opacity: 50; + overflow: hidden; +} + +.btn-warning input[type="file"] { + color: transparent; + cursor: pointer; + position: absolute; + left: 0%; + top: 0%; + transform: scale(3); + opacity: 50; + transition: 0.2s; +} + +.btn-warning:hover { + background-color: #eed519; +} + +.submit-button { + position: relative; + padding: 11px 16px; + font-size: 15px; + line-height: 1.5; + border-radius: 3px; + color: #000000; + background-color: #ffc100; + border: 0; + transition: 0.2s; + opacity: 50; + overflow: hidden; +} + +.submit-button:hover { + background-color: #eed519; +} + +.table-container { + /* padding: 50px 50px; */ + flex: 1 1 0; + border-radius: 3px; +} + +.errors-container { + display: flex; + justify-content: center; + /* padding: 10px 10px; */ + flex: 1 1 0; + border-radius: 3px; + +} + +.results-table { + padding: 50px 50px; + border-radius: 3px; +} + +#myTable { + background-color: #fad253; + border-collapse: collapse; + width: 100%; + border: 1px solid #ddd; + font-size: 14px; +} + +#myTable th { + background-color: #fad253; + text-align: left; + /* Left-align text */ + padding: 12px; + /* Add padding */ +} + +#myTable tr { + /* Add a bottom border to all table rows */ + border-bottom: 1px solid #ddd; +} + +/*#myTable tr.header,*/ +#myTable tr:hover { + /* Add a grey background color to the table header and on hover */ + background-color: #ec974f; +} + +#myTable td[colspan] { + padding-top: 0; + padding-bottom: 0; +} + +#myTable td[colspan] p { + padding-top: 12px; + padding-bottom: 12px; +} + +/* TEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEST */ + + +#data { + background-color: #fad253; + border-collapse: collapse; + width: 100%; + border: 1px solid #ddd; + font-size: 14px; +} + +#data th { + background-color: #fad253; + text-align: left; + /* Left-align text */ + padding: 12px; + /* Add padding */ +} + +#data tr { + /* Add a bottom border to all table rows */ + border-bottom: 1px solid #ddd; +} + +/*#myTable tr.header,*/ +#data tr:hover { + /* Add a grey background color to the table header and on hover */ + background-color: #ec974f; +} + +#data td[colspan] { + padding-top: 0; + padding-bottom: 0; +} + +#data td[colspan] p { + padding-top: 12px; + padding-bottom: 12px; +} diff --git a/flask_soc_site/templates/empty_form.html b/flask_soc_site/templates/empty_form.html new file mode 100644 index 0000000..32bfa1f --- /dev/null +++ b/flask_soc_site/templates/empty_form.html @@ -0,0 +1,10 @@ +{% with messages=get_flashed_messages() %} {% if messages %} +
+
+

+ {% for message in messages %} + {{message}} + {% endfor %} {% endif %} {% endwith %} +

+
+
diff --git a/flask_soc_site/templates/forms.html b/flask_soc_site/templates/forms.html new file mode 100644 index 0000000..f766be7 --- /dev/null +++ b/flask_soc_site/templates/forms.html @@ -0,0 +1,19 @@ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
diff --git a/flask_soc_site/templates/history.html b/flask_soc_site/templates/history.html new file mode 100644 index 0000000..a75a351 --- /dev/null +++ b/flask_soc_site/templates/history.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/flask_soc_site/templates/howto.html b/flask_soc_site/templates/howto.html new file mode 100644 index 0000000..e620dda --- /dev/null +++ b/flask_soc_site/templates/howto.html @@ -0,0 +1,25 @@ +
+
+

This is a simple one-page Flask application to help my day-to-day work as a security analyst. Above you can + type + in the following:

+
+
+ +
+
+ Submit and you will get: +
+
+ +
+
diff --git a/flask_soc_site/templates/index.html b/flask_soc_site/templates/index.html new file mode 100644 index 0000000..68de695 --- /dev/null +++ b/flask_soc_site/templates/index.html @@ -0,0 +1,3 @@ +{% extends "layout.html" %} + +{% include "test.html" %} diff --git a/flask_soc_site/templates/layout.html b/flask_soc_site/templates/layout.html new file mode 100644 index 0000000..7f7b35e --- /dev/null +++ b/flask_soc_site/templates/layout.html @@ -0,0 +1,41 @@ + + + + + + + Got something to look up? I got you! + + + + + + + + + + + + {% block content %} {% endblock %} + + + + + + + + + + + + {% block scripts %}{% endblock %} + + + diff --git a/flask_soc_site/templates/lookup.html b/flask_soc_site/templates/lookup.html new file mode 100644 index 0000000..3954d63 --- /dev/null +++ b/flask_soc_site/templates/lookup.html @@ -0,0 +1,14 @@ +{% extends "layout.html" %} + +{% block content %} + + + +{% include "test.html" %} + + + +{% include "empty_form.html" %} + +{% endblock %} + diff --git a/flask_soc_site/templates/results-detail.html b/flask_soc_site/templates/results-detail.html new file mode 100644 index 0000000..e69de29 diff --git a/flask_soc_site/templates/results-overview-new.html b/flask_soc_site/templates/results-overview-new.html new file mode 100644 index 0000000..8b73068 --- /dev/null +++ b/flask_soc_site/templates/results-overview-new.html @@ -0,0 +1,98 @@ + + + +
+ {% if error in errors %} +

+ + You may have mistyped: {{errors|join(',')}}. Try again! + + {% endif %} +

+ +
+
+ + + + + + + + + + + + {% for host in hosts %} + + + + + + + + + + + + + {% endfor %} + +
IP addressVirusTotalAbuseIPDBDomainRegistration countryRegistration dateEmail security
{{host.host}}Score: {{host.vt['score']}} / {{host.vt['total']}}
+ Last updated: {{host.vt['last_update']}}
+
Score: {{host.abuseipdb['score']}} + Last reported: {{host.abuseipdb['last_reported']}}{{host.domain}}{{host.metadata['registrar_country']}}{{host.metadata['creation_date']}}SPF: {{host.email_security[0]}}, DMARC: {{host.email_security[1]}}
+

+ VirusTotal +
+ {% if host.vt['score'] > 0 %} + Vendor marked this host as malicious or suspicious: + {{host.vt['vendors']|join(', ')}} + {{host.vt['score']}} + {% else %} + No vendors marked this host as malicious or suspicious. + {% endif %} +
+ AbuseIPDB +
+ IP address is: {{host.abuseipdb['IP_address']}}
+ Is this Tor: {{host.abuseipdb['Tor']}}
+ Usage: {{host.abuseipdb['usage']}}
+
+ +

+
+
+
+ + + +{% block scripts %} + + + +{% endblock %} diff --git a/flask_soc_site/templates/results.html b/flask_soc_site/templates/results.html new file mode 100644 index 0000000..8f86e3a --- /dev/null +++ b/flask_soc_site/templates/results.html @@ -0,0 +1,20 @@ +{% extends "layout.html" %} + +{% block content %} + +{# For multiple results, maybe make a top results overview and then a navigation menu to specific URL pages? See: https://realpython.com/primer-on-jinja-templating/#include-a-navigation-menu #} + + +{% include "test.html" %} + + {# {% include "results-overview.html" %} #} + +{% include "history.html" %} + +{% include "results-overview-new.html" %} + +{% include "results-detail.html" %} + +{% include "empty_form.html" %} + +{% endblock %} diff --git a/flask_soc_site/templates/test-grid-and-flex.html b/flask_soc_site/templates/test-grid-and-flex.html new file mode 100644 index 0000000..85b1ecc --- /dev/null +++ b/flask_soc_site/templates/test-grid-and-flex.html @@ -0,0 +1,58 @@ +
+
+ 1 +
+
+ 2 +
+
+ 3 +
+
+ 4 +
+
TEST
+
TEST
+
TEST
+
+ GRID TEST +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
head 1head 2head 3head 4head 5head 6head 7head 8
table data 1table data 2table data 3table data 4table data 5table data 6table data 7table data 8
table data 1table data 2table data 3table data 4table data 5table data 6table data 7table data 8
+
+
diff --git a/flask_soc_site/templates/test.html b/flask_soc_site/templates/test.html new file mode 100644 index 0000000..b8c37e3 --- /dev/null +++ b/flask_soc_site/templates/test.html @@ -0,0 +1,19 @@ +
+ + +
+
+ + +
+
+ +
+

Put in your IP address, URL, domain or email address...

+

and see AbuseIPDB and VirusTotal scores, + registrar information and DMARC, DKIM and SPF information.

+
+
+

This is a simple one-page Flask application to help my day-to-day work as a security analyst.

+
+