first commit from a working website
This commit is contained in:
parent
2749d06a2d
commit
709d6e2f61
115
app.py
Normal file
115
app.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
# from dotenv import load_dotenv
|
||||||
|
import secrets
|
||||||
|
import socket
|
||||||
|
import uuid
|
||||||
|
from logging.config import dictConfig
|
||||||
|
from pprint import pprint
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from flask import Flask, flash, redirect, render_template, request, session, url_for
|
||||||
|
from markupsafe import escape
|
||||||
|
|
||||||
|
# from io import StringIO
|
||||||
|
from validators import domain, email, ipv4, ipv6, url
|
||||||
|
|
||||||
|
from constants import *
|
||||||
|
from host_lookup import abuseipdb, metadata, spf_dmarc, virustotal_api_test
|
||||||
|
from upload import csv_parse
|
||||||
|
|
||||||
|
# import csv
|
||||||
|
|
||||||
|
dictConfig(LOGCONF)
|
||||||
|
|
||||||
|
# put this in a .flaskenv file: https://dev.to/kubona_my/dealing-with-environment-variables-in-flask-o1
|
||||||
|
app = Flask(__name__)
|
||||||
|
generate_secret = secrets.token_urlsafe(16)
|
||||||
|
app.secret_key = generate_secret
|
||||||
|
# app.debug = True
|
||||||
|
|
||||||
|
|
||||||
|
class Info(object):
|
||||||
|
def __init__(self, host):
|
||||||
|
self.host = host
|
||||||
|
self.ip_address = None
|
||||||
|
self.host_type = metadata.check(self.host)
|
||||||
|
self.metadata = metadata.lookup(self.host)
|
||||||
|
self.emailsec = ()
|
||||||
|
self.vt = {}
|
||||||
|
self.abuseipdb = {}
|
||||||
|
|
||||||
|
def lookup(host):
|
||||||
|
result = Info(host)
|
||||||
|
if result.host_type == DOMAIN:
|
||||||
|
result.ip_address = socket.gethostbyname(host)
|
||||||
|
result.emailsec = spf_dmarc.lookup(host)
|
||||||
|
result.vt = virustotal_api_test.analyse(result.host, result.host_type)
|
||||||
|
result.abuseipdb = abuseipdb.analyse(result.ip_address)
|
||||||
|
print("[DEBUGGING]")
|
||||||
|
pprint(result.emailsec)
|
||||||
|
elif result.host_type == URL:
|
||||||
|
result.domain = urlparse(host).netloc
|
||||||
|
result.ip_address = socket.gethostbyname(result.domain)
|
||||||
|
result.vt = virustotal_api_test.analyse(result.host, result.host_type)
|
||||||
|
result.abuseipdb = abuseipdb.analyse(result.ip_address)
|
||||||
|
elif result.host_type == IPV4 or IPV6:
|
||||||
|
result.vt = virustotal_api_test.analyse(result.host, result.host_type)
|
||||||
|
result.abuseipdb = abuseipdb.analyse(host)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
# logging example taken from https://betterstack.com/community/guides/logging/how-to-start-logging-with-flask/
|
||||||
|
session["ctx"] = {"request_id": str(uuid.uuid4())}
|
||||||
|
app.logger.info("A user visited the home page >>> %s", session["ctx"])
|
||||||
|
|
||||||
|
return redirect(url_for("lookup"))
|
||||||
|
|
||||||
|
|
||||||
|
# refactor to handle form requests better: https://www.digitalocean.com/community/tutorials/how-to-use-web-forms-in-a-flask-application
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/lookup", methods=["GET", "POST"])
|
||||||
|
def lookup():
|
||||||
|
host = ""
|
||||||
|
host = escape(request.form.get("host"))
|
||||||
|
session["ctx"] = {"request_id": str(uuid.uuid4())}
|
||||||
|
# figure out how to start a session, maybe with a variable?
|
||||||
|
# variable = session.get('something')
|
||||||
|
if request.method == "GET":
|
||||||
|
return render_template("lookup_options.html")
|
||||||
|
elif request.method == "POST" and "host" in request.form:
|
||||||
|
host = ""
|
||||||
|
host = escape(request.form.get("host"))
|
||||||
|
session["ctx"] = {"request_id": str(uuid.uuid4())}
|
||||||
|
app.logger.info(
|
||||||
|
"A user submitted a host to look up. | host: %s >>> %s",
|
||||||
|
host,
|
||||||
|
session["ctx"],
|
||||||
|
)
|
||||||
|
if not host:
|
||||||
|
flash("Try again", "error")
|
||||||
|
return render_template("lookup_options.html")
|
||||||
|
elif host:
|
||||||
|
result = Info.lookup(host)
|
||||||
|
return render_template(
|
||||||
|
"lookup_options.html",
|
||||||
|
host=result.host,
|
||||||
|
host_type=result.host_type,
|
||||||
|
result=result,
|
||||||
|
)
|
||||||
|
elif request.method == "POST" and "file" in request.files:
|
||||||
|
file = request.files["file"]
|
||||||
|
extracted = csv_parse.extract(file)
|
||||||
|
results = []
|
||||||
|
for host in extracted:
|
||||||
|
results.append(Info.lookup(host))
|
||||||
|
print(results)
|
||||||
|
return render_template("lookup_options.html")
|
||||||
|
else:
|
||||||
|
flash("No file!", "error")
|
||||||
|
return render_template("lookup_options.html")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
26
constants.py
Normal file
26
constants.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
URL = "URL"
|
||||||
|
DOMAIN = "domain"
|
||||||
|
IPV4 = "IPv4"
|
||||||
|
IPV6 = "IPv6"
|
41
host_lookup/abuseipdb.py
Normal file
41
host_lookup/abuseipdb.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
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
|
||||||
|
print(requests_cache.get_cache())
|
||||||
|
print("Cached:")
|
||||||
|
print("\n".join(requests_cache.get_cache().urls()))
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def analyse(host):
|
||||||
|
api_key = environment()
|
||||||
|
result = lookup(api_key, host)
|
||||||
|
decoded_result = json.loads(result.text)
|
||||||
|
return decoded_result
|
30
host_lookup/metadata.py
Normal file
30
host_lookup/metadata.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
from ipaddress import ip_address
|
||||||
|
from whois import whois
|
||||||
|
from ipwhois import IPWhois
|
||||||
|
import validators
|
||||||
|
from constants import URL, DOMAIN, IPV4, IPV6
|
||||||
|
|
||||||
|
|
||||||
|
def check(host):
|
||||||
|
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
|
||||||
|
return host_type
|
||||||
|
|
||||||
|
|
||||||
|
# def lookup(host_type):
|
||||||
|
def lookup(host):
|
||||||
|
result = dict(whois(host))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# result = whois(host_type[1])
|
||||||
|
# return result, host_type[0]
|
||||||
|
# obj = IPWhois(host_type[1])
|
||||||
|
# res = obj.lookup_rdap()
|
||||||
|
# return res, host_type[0]
|
3
host_lookup/otx_api.py
Normal file
3
host_lookup/otx_api.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Try to get historical telemetry like this page shows: https://otx.alienvault.com/indicator/ip/8.8.8.8
|
||||||
|
# Apparently this API does not provide this information :( f.e. the below curl request does not provide information about historical OTX telemetry.
|
||||||
|
# curl https://otx.alienvault.com/api/v1/indicators/url/http://www.freputation.com/spreputation_san_ponso/slides/IMG_0068.html/general -H "X-OTX-API-KEY: ec672963e435bb7a09c494534b79a6a7a273a5bde5ea560874cccd72e2bc76fc"
|
9
host_lookup/parse_URI.py
Normal file
9
host_lookup/parse_URI.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# This module should extract any and all URIs (IPs or URLs) from copy and pasted text.
|
||||||
|
|
||||||
|
def parse(text):
|
||||||
|
split_text = text.split()
|
||||||
|
for URI in split_text:
|
||||||
|
print(URI)
|
||||||
|
|
||||||
|
|
||||||
|
|
9
host_lookup/spf_dmarc.py
Normal file
9
host_lookup/spf_dmarc.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from checkdmarc.dmarc import check_dmarc
|
||||||
|
from checkdmarc.spf import check_spf
|
||||||
|
import validators
|
||||||
|
|
||||||
|
|
||||||
|
def lookup(host: str) -> tuple:
|
||||||
|
result_dmarc = check_dmarc(host)
|
||||||
|
result_spf = check_spf(host)
|
||||||
|
return (result_dmarc, result_spf)
|
24
host_lookup/virustotal.py
Normal file
24
host_lookup/virustotal.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import vt
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import virustotal_python
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from pprint import pprint
|
||||||
|
from base64 import urlsafe_b64encode
|
||||||
|
|
||||||
|
# todo: implement my own API request module to then try and cache the response (see -> https://realpython.com/caching-external-api-requests/#requests-cache)
|
||||||
|
|
||||||
|
def vt_lookup(URL):
|
||||||
|
load_dotenv()
|
||||||
|
api_key = os.getenv("VT_API")
|
||||||
|
with virustotal_python.Virustotal(api_key) as vtotal:
|
||||||
|
try:
|
||||||
|
resp = vtotal.request("urls", data={"url": URL}, method="POST")
|
||||||
|
print(resp)
|
||||||
|
# Safe encode URL in base64 format
|
||||||
|
# https://developers.virustotal.com/reference/url
|
||||||
|
url_id = urlsafe_b64encode(URL.encode()).decode().strip("=")
|
||||||
|
report = vtotal.request(f"urls/{url_id}")
|
||||||
|
return report.data
|
||||||
|
except virustotal_python.VirustotalError as err:
|
||||||
|
print(f"Failed to send URL: {URL} for analysis and get the report: {err}")
|
77
host_lookup/virustotal_api_test.py
Normal file
77
host_lookup/virustotal_api_test.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from pprint import pprint
|
||||||
|
from constants import URL, DOMAIN, IPV4, IPV6
|
||||||
|
|
||||||
|
# Would be nice to define some constants, f.e. for the various API urls, the headers, etc.
|
||||||
|
|
||||||
|
|
||||||
|
def environment():
|
||||||
|
load_dotenv()
|
||||||
|
api_key = os.getenv("VT_API")
|
||||||
|
return api_key
|
||||||
|
|
||||||
|
|
||||||
|
# Unfortunately this works for actual URLs, not domains. See: https://docs.virustotal.com/reference/domain-info
|
||||||
|
# This also doesn't work for IPv6 addresses, where the response_dict does not have a 'data' key. So I would have to revamp this module and make separate functions called based on host type (URL, IPv4 and -6, domain).
|
||||||
|
|
||||||
|
|
||||||
|
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_response = requests.get(url, headers=headers)
|
||||||
|
response_dict = json.loads(analysis_response.text)
|
||||||
|
# Probably still need to turn the requests.get into a json like below
|
||||||
|
return response_dict
|
||||||
|
|
||||||
|
|
||||||
|
def analyse_URL(api_key, response_id):
|
||||||
|
analysis_url = "https://www.virustotal.com/api/v3/analyses/{}".format(response_id)
|
||||||
|
headers = {"accept": "application/json", "x-apikey": api_key}
|
||||||
|
analysis_response = requests.get(analysis_url, headers=headers)
|
||||||
|
analysis_dict = json.loads(analysis_response.text)
|
||||||
|
# return analysis_response.text
|
||||||
|
return analysis_dict
|
||||||
|
|
||||||
|
|
||||||
|
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_response = requests.get(analysis_url, headers=headers)
|
||||||
|
analysis_dict = json.loads(analysis_response.text)
|
||||||
|
# Implement this: https://docs.virustotal.com/reference/ip-info
|
||||||
|
return analysis_dict
|
||||||
|
|
||||||
|
|
||||||
|
def analyse(host, host_type):
|
||||||
|
api_key = environment()
|
||||||
|
if host_type == URL:
|
||||||
|
response_id = analysis_object(api_key, host)
|
||||||
|
result = analyse_URL(api_key, response_id)
|
||||||
|
elif host_type == DOMAIN:
|
||||||
|
result = analyse_domain(api_key, host)
|
||||||
|
# elif for IPv4 and IPv6.
|
||||||
|
elif host_type == IPV4 or IPV6:
|
||||||
|
result = analyse_IP(api_key, host)
|
||||||
|
return result
|
32
requirements.txt
Normal file
32
requirements.txt
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
attrs==24.2.0
|
||||||
|
blinker==1.8.2
|
||||||
|
cattrs==24.1.0
|
||||||
|
certifi==2024.7.4
|
||||||
|
cffi==1.17.0
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
checkdmarc==5.5.0
|
||||||
|
click==8.1.7
|
||||||
|
cryptography==43.0.0
|
||||||
|
dnspython==2.0.0
|
||||||
|
expiringdict==1.2.2
|
||||||
|
Flask==3.0.3
|
||||||
|
idna==3.8
|
||||||
|
ipwhois==1.2.0
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
Jinja2==3.1.4
|
||||||
|
MarkupSafe==2.1.5
|
||||||
|
platformdirs==4.2.2
|
||||||
|
publicsuffixlist==1.0.2.20240827
|
||||||
|
pycparser==2.22
|
||||||
|
pyleri==1.4.3
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
python-dotenv==1.0.1
|
||||||
|
python-whois==0.9.4
|
||||||
|
requests==2.32.3
|
||||||
|
requests-cache==1.2.1
|
||||||
|
six==1.16.0
|
||||||
|
timeout-decorator==0.5.0
|
||||||
|
url-normalize==1.4.3
|
||||||
|
urllib3==2.2.2
|
||||||
|
validators==0.33.0
|
||||||
|
Werkzeug==3.0.4
|
6
static/styles/style.css
Normal file
6
static/styles/style.css
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.host_form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.upload_form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
103
templates/IPv4.html
Normal file
103
templates/IPv4.html
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
{% if result %}
|
||||||
|
<div>
|
||||||
|
<table id="data" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>IP resolves to URL</th>
|
||||||
|
<th>creation date</th>
|
||||||
|
<th>registrar</th>
|
||||||
|
<th>registrar's country of residence</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ result.metadata['domain_name'] }}</td>
|
||||||
|
<td>{{ result.metadata['creation_date'] }}</td>
|
||||||
|
<td>{{ result.metadata['registrar'] }}</td>
|
||||||
|
<td>{{ result.metadata['country'] }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="virustotal_stats" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Malicious</th>
|
||||||
|
<th>Suspicious</th>
|
||||||
|
<th>Undetected</th>
|
||||||
|
<th>Harmless</th>
|
||||||
|
<th>Timeout</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
result.vt['data']['attributes']['last_analysis_stats']['malicious']}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
result.vt['data']['attributes']['last_analysis_stats']['suspicious']
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
result.vt['data']['attributes']['last_analysis_stats']['undetected']
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ result.vt['data']['attributes']['last_analysis_stats']['harmless']
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ result.vt['data']['attributes']['last_analysis_stats']['timeout']
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="virustotal_all_vendors" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Vendor name</th>
|
||||||
|
<th>Results</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for vendor,value in
|
||||||
|
result.vt['data']['attributes']['last_analysis_results'].items() %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ vendor }}</td>
|
||||||
|
<td>{{ value['result'] }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="abuseipdb" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Abuse IPDB confidence score</th>
|
||||||
|
<th>Total reports</th>
|
||||||
|
<th>Last reported</th>
|
||||||
|
<th>Tor or not</th>
|
||||||
|
<th>Hostnames</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ result.abuseipdb.data.abuseConfidenceScore }}</td>
|
||||||
|
<td>{{ result.abuseipdb.data.totalReports }}</td>
|
||||||
|
<td>{{ result.abuseipdb.data.lastReportedAt }}</td>
|
||||||
|
<td>{{ result.abuseipdb.data.isTor }}</td>
|
||||||
|
<td>{{result.abuseipdb.data.hostnames }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% elif results %}
|
||||||
|
<div>
|
||||||
|
<p>VISUALIZE</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
80
templates/URL.html
Normal file
80
templates/URL.html
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
{% if result %}
|
||||||
|
<div>
|
||||||
|
<table id="data" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>domain</th>
|
||||||
|
<th>creation date</th>
|
||||||
|
<th>registrar</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ result.metadata['domain_name'] }}</td>
|
||||||
|
<td>{{ result.metadata['creation_date'] }}</td>
|
||||||
|
<td>{{ result.metadata['registrar'] }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="virustotal_stats" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Malicious</th>
|
||||||
|
<th>Suspicous</th>
|
||||||
|
<th>Undetected</th>
|
||||||
|
<th>Harmless</th>
|
||||||
|
<th>Timeout</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{% for value in result.vt.data.attributes.stats.values() %}
|
||||||
|
<td>{{ value }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="virustotal_all_vendors" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Vendor name</th>
|
||||||
|
<th>Results</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for value in result.vt.data.attributes.results.values() if value.result == 'malicious' or value.result == 'malware' %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ value.engine_name }}</td>
|
||||||
|
<td>{{ value.result }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table id="abuseipdb" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Abuse IPDB confidence score</th>
|
||||||
|
<th>Total reports</th>
|
||||||
|
<th>Last reported</th>
|
||||||
|
<th>Tor or not</th>
|
||||||
|
<th>Hostnames</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ result.abuseipdb.data.abuseConfidenceScore }}</td>
|
||||||
|
<td>{{ result.abuseipdb.data.totalReports }}</td>
|
||||||
|
<td>{{ result.abuseipdb.data.lastReportedAt }}</td>
|
||||||
|
<td>{{ result.abuseipdb.data.isTor }}</td>
|
||||||
|
<td>{{result.abuseipdb.data.hostnames }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% elif results %}
|
||||||
|
<div>
|
||||||
|
<p>VISUALIZE</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
109
templates/domain.html
Normal file
109
templates/domain.html
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
{% if result %}
|
||||||
|
<div>
|
||||||
|
<p>domain</p>
|
||||||
|
|
||||||
|
<table id="data" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>creation date</th>
|
||||||
|
<th>registrar</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ result.metadata['domain_name'] }}</td>
|
||||||
|
<td>{{ result.metadata['creation_date'] }}</td>
|
||||||
|
<td>{{ result.metadata['registrar'] }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="spf_dmarc" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>DMARC record</th>
|
||||||
|
<th>DMARC validity</th>
|
||||||
|
<th>SPF record</th>
|
||||||
|
<th>SPF validity</th>
|
||||||
|
<th>SPF keys()</th>
|
||||||
|
<th>SPF dns lookups</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ result.emailsec[0]['record'] }}</td>
|
||||||
|
<td>{{ result.emailsec[0]['valid'] }}</td>
|
||||||
|
<td>{{ result.emailsec[1]['record'] }}</td>
|
||||||
|
<td>{{ result.emailsec[1]['valid'] }}</td>
|
||||||
|
<td>{{ result.emailsec[1].keys() }}</td>
|
||||||
|
{% for item in result.emailsec[1]['parsed']%}
|
||||||
|
<td>{{ item }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table id="virustotal_stats" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Malicious</th>
|
||||||
|
<th>Suspicous</th>
|
||||||
|
<th>Undetected</th>
|
||||||
|
<th>Harmless</th>
|
||||||
|
<th>Timeout</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{% for value in result.vt.data.attributes.last_analysis_stats.values()
|
||||||
|
%}
|
||||||
|
<td>{{ value }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="virustotal_all_vendors" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Vendor name</th>
|
||||||
|
<th>Results</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for value in result.vt.data.attributes.last_analysis_results.values()
|
||||||
|
%}
|
||||||
|
<tr>
|
||||||
|
<td>{{ value.engine_name }}</td>
|
||||||
|
<td>{{ value.result }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="abuseipdb" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Abuse IPDB confidence score</th>
|
||||||
|
<th>Total reports</th>
|
||||||
|
<th>Last reported</th>
|
||||||
|
<th>Tor or not</th>
|
||||||
|
<th>Hostnames</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ result.abuseipdb.data.abuseConfidenceScore }}</td>
|
||||||
|
<td>{{ result.abuseipdb.data.totalReports }}</td>
|
||||||
|
<td>{{ result.abuseipdb.data.lastReportedAt }}</td>
|
||||||
|
<td>{{ result.abuseipdb.data.isTor }}</td>
|
||||||
|
<td>{{result.abuseipdb.data.hostnames }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% elif results %}
|
||||||
|
<div>
|
||||||
|
<p>VISUALIZE</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
8
templates/empty_form.html
Normal file
8
templates/empty_form.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{% with messages=get_flashed_messages() %} {% if messages %}
|
||||||
|
<p class="text-center">
|
||||||
|
{% for message in messages %}
|
||||||
|
<i class="fa-solid fa-circle-exclamation"></i>
|
||||||
|
{{message}}
|
||||||
|
<i class="fa-solid fa-circle-exclamation"></i>
|
||||||
|
{% endfor %} {% endif %} {% endwith %}
|
||||||
|
</p>
|
1
templates/index.html
Normal file
1
templates/index.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
TESTIE
|
48
templates/layout.html
Normal file
48
templates/layout.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Got something to look up? I got you!</title>
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="https://use.fontawesome.com/releases/v6.5.1/css/all.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="https://cdn.datatables.net/1.10.25/css/dataTables.bootstrap5.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="{{ url_for('static', filename='styles/style.css')}}"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block content %} {% endblock %}
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
charset="utf8"
|
||||||
|
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||||
|
></script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
charset="utf8"
|
||||||
|
src="https://cdn.datatables.net/1.10.25/js/jquery.dataTables.js"
|
||||||
|
></script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
charset="utf8"
|
||||||
|
src="https://cdn.datatables.net/1.10.25/js/dataTables.bootstrap5.js"
|
||||||
|
></script>
|
||||||
|
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
97
templates/lookup_options.html
Normal file
97
templates/lookup_options.html
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1 class="text-center">URL or IP lookup</h1>
|
||||||
|
|
||||||
|
<div class="host_form">
|
||||||
|
<form host="/lookup" method="POST">
|
||||||
|
<input type="text" name="host">
|
||||||
|
<input type="SUBMIT" value="Type it in">
|
||||||
|
</form>
|
||||||
|
<p>OR</p>
|
||||||
|
<form action="/lookup" host="/lookup" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file">
|
||||||
|
<input type="SUBMIT" value="Upload a csv">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if host_type == "domain" %}
|
||||||
|
|
||||||
|
{% include "domain.html" %}
|
||||||
|
|
||||||
|
{% elif host_type == "URL" %}
|
||||||
|
|
||||||
|
{% include "URL.html" %}
|
||||||
|
|
||||||
|
{% elif host_type == "IPv4" %}
|
||||||
|
|
||||||
|
{% include "IPv4.html" %}
|
||||||
|
|
||||||
|
{% elif host_type == "IPv6" %}
|
||||||
|
|
||||||
|
{% include "IPv6.html" %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<p class="text-center">Copy paste your URLs or IPs and press submit!</p>
|
||||||
|
{% include "empty_form.html" %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('#virustotal_all_vendors').DataTable({
|
||||||
|
scrollY: 300,
|
||||||
|
<!-- scrollX: 100, -->
|
||||||
|
paging: false,
|
||||||
|
searching: false,
|
||||||
|
info: false,
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('#abuseipdb').DataTable({
|
||||||
|
paging: false,
|
||||||
|
searching: false,
|
||||||
|
info: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('#data').DataTable({
|
||||||
|
paging: false,
|
||||||
|
searching: false,
|
||||||
|
info: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('#virustotal_stats').DataTable({
|
||||||
|
paging: false,
|
||||||
|
searching: false,
|
||||||
|
info: false,
|
||||||
|
columnDefs: [ {
|
||||||
|
targets: '_all',
|
||||||
|
createdCell: function (td, cellData, rowData, row, col) {
|
||||||
|
if (cellData > 0 && col == 0) {
|
||||||
|
$(td).css({'background-color':'red','color':'white'});
|
||||||
|
}
|
||||||
|
if (cellData > 0 && col == 1) {
|
||||||
|
$(td).css({'background-color':'orange','color':'white'});
|
||||||
|
}
|
||||||
|
if (cellData > 0 && col == 2) {
|
||||||
|
$(td).css({'background-color':'grey','color':'white'});
|
||||||
|
}
|
||||||
|
if (cellData > 0 && col == 3) {
|
||||||
|
$(td).css({'background-color':'green','color':'white'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
/// Trying out some more cellData searching, this time adding some text
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
58
templates/lookup_results.html
Normal file
58
templates/lookup_results.html
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Results!</h1>
|
||||||
|
|
||||||
|
<p>What you looked up:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>URL or IP: {{ URI_type }} </li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<table id="data" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>creation date</th>
|
||||||
|
<th>registrar</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ looked_up.domain_name }}</td>
|
||||||
|
<td>{{ looked_up.creation_date }}</td>
|
||||||
|
<td>{{ looked_up.registrar }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table id="virustotal_all_vendors" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Vendor name</th>
|
||||||
|
<th>Results</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for value in virustotal_results.attributes.last_analysis_results.values() %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ value.engine_name }}</td>
|
||||||
|
<td>{{ value.result }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{# Below JavaScript table magic from: https://blog.miguelgrinberg.com/post/beautiful-interactive-tables-for-your-flask-templates #}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('#virustotal_all_vendors').DataTable();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
15
templates/test.html
Normal file
15
templates/test.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<table id="test" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>domain</th>
|
||||||
|
<th>creation date</th>
|
||||||
|
<th>registrar</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ results.metadata['domain_name'] }}</td>
|
||||||
|
<td>{{ results.metadata['creation_date'] }}</td>
|
||||||
|
<td>{{ results.metadata['registrar'] }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
0
templates/upload.html~
Normal file
0
templates/upload.html~
Normal file
22
upload/csv_parse.py
Normal file
22
upload/csv_parse.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import csv
|
||||||
|
from io import StringIO
|
||||||
|
from validators import ipv4, ipv6, url, domain
|
||||||
|
|
||||||
|
|
||||||
|
def extract(uploaded):
|
||||||
|
hosts = []
|
||||||
|
content = uploaded.read()
|
||||||
|
decoded = content.decode("utf-8")
|
||||||
|
file = StringIO(decoded)
|
||||||
|
csv_data = csv.reader(file, delimiter=",")
|
||||||
|
for row in csv_data:
|
||||||
|
for value in row:
|
||||||
|
if url(value):
|
||||||
|
hosts.append(value)
|
||||||
|
elif domain(value):
|
||||||
|
hosts.append(value)
|
||||||
|
elif ipv4(value):
|
||||||
|
hosts.append(value)
|
||||||
|
elif ipv6(value):
|
||||||
|
hosts.append(value)
|
||||||
|
return hosts
|
Loading…
Reference in New Issue
Block a user