First commit

This commit is contained in:
geoffrey 2024-06-18 21:46:23 +02:00
commit 816918f041
19 changed files with 1036 additions and 0 deletions

2
.gitignore vendored Normal file

@ -0,0 +1,2 @@
.**.swp
__pycache__/**

15
README.md Normal file

@ -0,0 +1,15 @@
# Introduction
This tool can help SOC analyst to identify any threat
# Implementation
First, you should create a virtualenv:
```
$ virtualenv ~/venv/baoSOC
$ source ~/venv/baoSOC/bin/activate
```
And install all packages the tool need:
```
$ pip install -r requirements.txt
```

2
config Normal file

@ -0,0 +1,2 @@
api_key: f4c451920a7e41ec344e16e6d36a1b7951bf23a8d224b796cb08301e65bf3114
dnsbl: dnsbl.txt

13
config.py Normal file

@ -0,0 +1,13 @@
#!/usr/bin/venv python
# -*- coding: utf-8 -*-
VT_ATTRIBUTES_MAPPING = {
'asn': 'str',
'as_owner': 'int',
'continent': 'str',
'country': 'str',
'last_analysis_date': 'date',
'regional_internet_registry': 'str',
'network': 'str',
'ip': 'str'
}

278
dns.py Normal file

@ -0,0 +1,278 @@
#!/usr/bin/venv python
# -*- coding: utf-8 -*-
from argparse import ArgumentParser
from scapy.all import *
from scapy.layers.dns import DNSQR, DNSRR, DNS
import matplotlib.pyplot as plt
import numpy as np
import requests
import re
from reports import generateHtmlReport, createReportsDirectory, getTodayDate
from tunneling import tunnelingDNSAttacks
from config import VT_ATTRIBUTES_MAPPING
import whois
import dns.resolver
class DNS:
def __init__(self, api_key):
pass
def whois(self, fqdn):
report = dict()
w = whois.whois(fqdn)
report['domain_name'] = w.domain_name
report['expiration_date'] = w.expiration_date
report['creation_date'] = w.creation_date
report['updated_date'] = w.updated_date
#report['data'] = w.text
report['ns'] = w.name_servers
report['admin_name'] = w.admin_name
return report
def resolver(self, fqdn):
report = dict()
res_query = dns.resolver.resolve(fqdn)
for rdata in res_query:
print(rdata.target)
def _getType(t):
"""
This function identify the message type according to the RFC 1035:
https://datatracker.ietf.org/doc/html/rfc1035#section-3.2.2
"""
rtype = {
1: 'A', 2: 'NS', 3: 'MD', 4: 'MF',
5: 'CNAME', 6: 'SOA', 7: 'MB', 8: 'MG', 9: 'MR',
10: 'NULL', 11: 'WKS', 12: 'PTR', 13: 'HINFO', 14: 'MINFO',
15: 'MX', 16: 'TXT',
28: 'AAAA'
}
try:
return rtype[t]
except KeyError:
return t
def readPcapFile(pcap):
"""
This function read the pcap file with scapy
"""
data = None
try:
data = rdpcap(pcap)
except Scapy_Exception:
print(f"Failed to read {pcap}")
return None
return data
def _privateIP(ip):
"""
This function check if the ip is from RFC 1918
https://stackoverflow.com/questions/2814002/private-ip-address-identifier-in-regular-expression
"""
res = False
rfs1918 = [
'(^10\.)',
'(^172\.1[6-9]\.)',
'(^172\.2[0-9]\.)',
'(^172\.3[0-1]\.)',
'(^192\.168\.)',
]
for privateIP in rfs1918:
if re.search(privateIP, ip):
res = True
return res
def getIPVirusTotal(api_key, ip, report):
"""
This function get information of the IP
"""
url = f"https://www.virustotal.com/api/v3/ip_addresses/{ip}"
headers = {
'x-apikey': api_key,
}
res = requests.get(url, headers=headers).json()
data = dict()
data['ip'] = ip
if 'error' in res:
report.append({
'error': res['error']['message'],
'ip': ip
})
return
vt = res['data']['attributes']
for entry in VT_ATTRIBUTES_MAPPING.keys():
if entry in vt:
try:
data[entry] = vt[entry]
except KeyError:
data[entry] = 'Unknown'
report.append(data)
def main():
args = checkArguments()
report = {}
report['queries'] = list()
report['domains'] = list()
report['graphics'] = dict()
report['ip'] = list()
report['vt'] = list()
report['dnstunneling'] = dict()
if not args.file:
print("Please, specify the option --file")
exit(0)
if not args.config:
print("Please, specify the config file ith the parameter --config")
exit(0)
# Read the config file
config = readConfigFile(args.config)
if config is None:
print("Failed to read the config file")
exit(0)
# Get the API key for VirusTotal
checkVt = True
try:
api_key = config["api_key"]
except KeyError:
print("Can't find the key in the config file. Bypass the check to VirusTotal")
checkVt = False
#exit(0)
# Check if DNS BlackList is specified
try:
dnsbl = config["dnsbl"]
except KeyError:
print("Can't find the dnsbl in the config file")
exit(0)
data = readPcapFile(args.file)
if data is None:
print("Failed to read the pcap file")
exit(0)
# For number of request into the domain
numRequest = dict()
numIPDst = dict()
allIPs = list()
allDomains = list()
print("Parsing DNS capture")
for d in data:
if d.haslayer(DNS):
# For DNS query
if isinstance(d.qd, DNSQR):
query = d.qd.qname
qtype = _getType(d.qd.qtype)
src = d[IP].src
dst = d[IP].dst
# print(f"{d[DNS].id}; SRC: {src}; DST: {dst}, Query: {query}; TYPE: {qtype}; Size: {len(d[DNS])}")
report['queries'].append({
'id': d[DNS].id,
'src': src,
'dst': dst,
'query': query,
'type': qtype,
'len': len(d[DNS]),
})
# Count the number of request for a domain
if query not in numRequest:
numRequest[query] = 1
else:
numRequest[query] += 1
# Count the number of IP dst
if dst not in numIPDst:
numIPDst[dst] = 1
else:
numIPDst[dst] += 1
if not _privateIP(dst) and dst not in allIPs:
allIPs.append(dst)
allDomains.append({
'query': query,
'src': src,
'len': len(d[DNS]),
})
# For DNS response
if isinstance(d.an, DNSRR):
ans = d.an.rrname
atype = _getType(d.an.type)
src = d[IP].src
dst = d[IP].dst
# print(f"{d[DNS].id}; SRC: {src}; DST: {dst}, Answer: {ans}; TYPE: {atype}")
report['queries'].append({
'id': d[DNS].id,
'src': src,
'dst': dst,
'answer': ans,
'type': atype,
'len': len(d[DNS]),
})
# Create directory for the report
print("Generating directory for reports")
reportName = createReportsDirectory()
# Draw matplotlib
today = getTodayDate()
fname_domain = f'domain_{today}.png'
fname_ip = f'ip_{today}.png'
report['graphics']['domain'] = fname_domain
report['graphics']['ip'] = fname_ip
x = list(numRequest.keys())[0:20]
y = list(numRequest.values())[0:20]
plt.figure(figsize=(15, 5))
plt.barh(x, y)
#plt.show()
plt.savefig(
fname=f"reports/{today}/{fname_domain}",
dpi='figure',
format='png'
)
x = list(numIPDst.keys())[0:20]
y = list(numIPDst.values())[0:20]
plt.figure(figsize=(10, 5))
plt.barh(x, y, 0.5)
#plt.show()
plt.savefig(
fname=f"reports/{today}/{fname_ip}",
dpi='figure',
format='png'
)
# Identify DNS Tunneling attacks
print("Analyzing DNS capture for identifying DNS Tunneling")
tunnelingDNSAttacks(report['dnstunneling'], allDomains, dnsbl)
# For external IP, use curl to VirusTotal for analyzing the IP
checkVt = False
if checkVt:
print("Getting IP informations to VirusTotal")
for ip in allIPs:
getIPVirusTotal(api_key, ip, report['vt'])
# We generating the report
print("Generating the report")
generateHtmlReport(report)
print(f"Report generated at this directory: {reportName}")
if __name__ == "__main__":
main()

97
main.py Normal file

@ -0,0 +1,97 @@
#!/usr/bin/venv python
# -*- coding: utf-8 -*-
from argparse import ArgumentParser
import requests
import re
from config import VT_ATTRIBUTES_MAPPING
from vt import VT
from dns import DNS
def checkArguments():
parser = ArgumentParser(description="baoSOC")
parser.add_argument('-c', '--config', help='Config file')
parser.add_argument('--hash', help='Hash file', action='store_true')
parser.add_argument('--dns', help='WhoIs')
return parser.parse_args()
def usage():
print("------------------------------")
print("| baoSOC |")
print("------------------------------\n")
print("A tool for SOC analyst\n")
print("Usage: main.py [COMMAND]")
print("-c PATH, --config PATH\t\tConfig file - mandatory")
print("--hash FILE\t\t\tHash the file and check in VirusTotal")
print("--dns FQDN\t\t\tGet information regarding the domain with whois and VirusTotal")
def mainMenu():
print("\n baoSOC ")
print(" What would you like to do? ")
print("\n OPTION 1: Sanitise URL For emails ")
print(" OPTION 2: Decoders (PP, URL, SafeLinks) ")
print(" OPTION 3: Reputation Checker")
print(" OPTION 4: DNS Tools")
print(" OPTION 5: Hashing Function")
print(" OPTION 6: Phishing Analysis")
print(" OPTION 7: URL scan")
print(" OPTION 9: Extras")
print(" OPTION 0: Exit Tool")
def readConfigFile(config):
"""
This function read the config file
"""
data = {}
try:
with open(config, 'r') as f:
lines = f.readlines()
# Split each line into te dictionary
for line in lines:
l = line.split(":")
lineParsed = l[1].replace(" ", "")
lineParsed = lineParsed.replace("\n", "")
data[l[0]] = lineParsed
except FileNotFoundError:
return None
return data
def main():
args = checkArguments()
if not args.config:
usage()
exit(1);
# Read the config file
config = readConfigFile(args.config)
if config is None:
print("Failed to read the config file")
exit(0)
#vt = VT(config['api_key'])
#report = list()
#print(vt.getIPVirusTotal("1.1.1.1", report))
if args.dns:
dns = DNS(config['api_key'])
print("IP information:\n")
print("\nReport with Whois:\n")
report = dns.whois(args.dns)
for key in report.keys():
if isinstance(report[key], list):
print(f"{key}:")
for value in report[key]:
print(f"\t{value}")
else:
print(f"{key}: {report[key]}")
print("\nReport with VirusTotal:\n")
if __name__ == "__main__":
main()

157
reports.py Normal file

@ -0,0 +1,157 @@
#!/usr/bin/venv python
# -*- coding: utf-8 -*-
from datetime import datetime
from os import mkdir
from config import VT_ATTRIBUTES_MAPPING
import jinja2
def generateHtmlReport(data):
env = jinja2.Environment(
loader=jinja2.FileSystemLoader("reports/templates"),
autoescape=jinja2.select_autoescape()
)
_queriesReport(data['queries'], env)
_graphicsReport(data['graphics'], env)
_vtReport(data['vt'], env)
_dnsTunnelingReports(data['dnstunneling'], env)
def _indexReport():
pass
def _queriesReport(queries, env):
"""
This function generate the report for queries
"""
today = getTodayDate()
dataJinja2 = dict()
dataJinja2['title'] = 'Queries'
dataJinja2['year'] = '2023'
dataJinja2['queries'] = queries
tmpl = env.get_template('queries.html.j2')
render = tmpl.render(data=dataJinja2)
with open(f"reports/{today}/reports_queries.html", "w") as f:
f.write(render)
def _graphicsReport(graphics, env):
today = getTodayDate()
dataJinja2 = dict()
dataJinja2['title'] = 'Graphics'
dataJinja2['year'] = '2023'
dataJinja2['graphics'] = graphics
tmpl = env.get_template('graphics.html.j2')
render = tmpl.render(data=dataJinja2)
with open(f"reports/{today}/reports_graphics.html", "w") as f:
f.write(render)
def _vtReport(vt, env):
today = getTodayDate()
# For testing
#vt = list()
#vt.append({
# 'ip': '1.2.3.4',
# 'asn': 3215,
# 'as_owner': 'Orange',
# 'continent': 'EU',
# 'country': 'FR',
# 'last_analysis_date': 1686839532,
# 'regional_internet_registry': 'RIPE NCC',
# 'network': '1.2.3.0/24'
#})
#vt.append({
# 'ip': '2.2.2.1',
# 'asn': 3215,
# 'as_owner': 'Orange',
# 'continent': 'EU',
# 'country': 'FR',
# 'last_analysis_date': 1686839532,
# 'regional_internet_registry': 'RIPE NCC',
# 'network': '2.2.2.0/24'
#})
#vt.append({
# 'ip': '3.3.3.1',
# 'asn': 3215,
# 'as_owner': 'Orange',
# 'continent': 'EU',
# 'country': 'FR',
# 'last_analysis_date': 1686839532,
# 'regional_internet_registry': 'RIPE NCC',
# 'network': '3.3.3.0/24'
#})
dataJinja2 = dict()
dataJinja2['title'] = 'VirusTotal'
dataJinja2['year'] = '2023'
dataJinja2['vt'] = list()
tmpl = env.get_template('vt.html.j2')
body = str()
for entry in vt:
vtEntry = dict()
if 'error' not in entry:
for vt in VT_ATTRIBUTES_MAPPING.keys():
try:
vtAttributes = VT_ATTRIBUTES_MAPPING[vt]
if 'date' in vtAttributes:
value = datetime.fromtimestamp(int(entry[vt]))
else:
value = entry[vt]
vtEntry[vt] = value
except KeyError:
pass
dataJinja2['vt'].append({
'ip': entry['ip'],
'data': vtEntry
})
render = tmpl.render(data=dataJinja2)
with open(f"reports/{today}/reports_vt.html", "w") as f:
f.write(render)
def _dnsTunnelingReports(dnstunneling, env):
today = getTodayDate()
dataJinja2 = dict()
dataJinja2['title'] = 'DNS Tunneling'
dataJinja2['year'] = '2023'
dataJinja2['dnstunneling'] = dnstunneling
tmpl = env.get_template('dnsTunneling.html.j2')
render = tmpl.render(data=dataJinja2)
with open(f"reports/{today}/reports_dns_tunneling.html", "w") as f:
f.write(render)
def createReportsDirectory():
"""
This function will create the reports directory
Return the report name or None if failed
"""
today = getTodayDate()
name = f"reports/{today}"
try:
mkdir(name)
except FileExistsError:
print("Reports directory already created")
return name
return name
def getTodayDate():
"""
This function genrate the today datetime at this format:
year_month_day
"""
return datetime.now().isoformat()[0:10].replace("-", "_")

File diff suppressed because one or more lines are too long

6
reports/templates/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

@ -0,0 +1,56 @@
<!doctype html>
<html lang="en" data-bs-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.112.5">
<title>{{ data['title'] }}</title>
<link rel="canonical" href="https://getbootstrap.com/docs/5.3/examples/jumbotron/">
<link href="../templates/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<main>
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<span class="d-flex align-items-center text-body-emphasis text-decoration-none; fs-4">Report</span>
</header>
{% include 'nav.html.j2' %}
<div class="p-5 mb-4 bg-body-tertiary rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">Report - DNS Tunneling</h1>
<p class="col-md-8 fs-4">Report of the day... blablabla</p>
</div>
</div>
<h2>DNS Blacklist</h2>
{% for item in data['dnstunneling']['blackDomain'] %}
<h3>{{ item }}</h3>
<p>Sources</p>
<ul>
{% for src in data['dnstunneling']['blackDomain'][item] %}
<li>{{ src }}</li>
{% endfor %}
</ul>
{% endfor %}
<h2>DNS datagram oversized</h2>
<p>In this section, we analyzed all sources who done DNS request with datagram oversized (200 bytes) and that can be a DNS tunneling.</p>
{% for item in data['dnstunneling']['oversized'] %}
<h3>{{ item }}</h3>
<p>Sources</p>
<ul>
{% for entry in data['dnstunneling']['oversized'][item] %}
<li>{{ entry['source'] }} ({{ entry['len'] }} bytes)</li>
{% endfor %}
</ul>
{% endfor %}
{% include 'footer.html.j2' %}

@ -0,0 +1,11 @@
<footer class="pt-3 mt-4 text-body-secondary border-top">
&copy; {{ data['year'] }}
</footer>
</div>
</main>
<script src="./templates/bootstrap.bundle.min.js"></script>
</body>
</html>

@ -0,0 +1,36 @@
<!doctype html>
<html lang="en" data-bs-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.112.5">
<title>{{ data['title'] }}</title>
<link rel="canonical" href="https://getbootstrap.com/docs/5.3/examples/jumbotron/">
<link href="../templates/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<main>
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<span class="d-flex align-items-center text-body-emphasis text-decoration-none; fs-4">Report</span>
</header>
{% include 'nav.html.j2' %}
<div class="p-5 mb-4 bg-body-tertiary rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">Report - graphics</h1>
<p class="col-md-8 fs-4">Report of the day... blablabla</p>
</div>
</div>
<h3>Domains (20 first entries)</h3>
<img src="{{ data['graphics']['domain'] }}" title="Graphic" />
<h3>Destinations IP (20 first entries)</h3>
<img src="{{ data['graphics']['ip'] }}" title="Graphic" />
{% include 'footer.html.j2' %}

@ -0,0 +1,113 @@
<!doctype html>
<html lang="en" data-bs-theme="auto">
<head><script src="../assets/js/color-modes.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.112.5">
<title>Jumbotron example · Bootstrap v5.3</title>
<link rel="canonical" href="https://getbootstrap.com/docs/5.3/examples/jumbotron/">
<link href="./templates/bootstrap.min.css" rel="stylesheet">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
.b-example-divider {
width: 100%;
height: 3rem;
background-color: rgba(0, 0, 0, .1);
border: solid rgba(0, 0, 0, .15);
border-width: 1px 0;
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
}
.b-example-vr {
flex-shrink: 0;
width: 1.5rem;
height: 100vh;
}
.bi {
vertical-align: -.125em;
fill: currentColor;
}
.nav-scroller {
position: relative;
z-index: 2;
height: 2.75rem;
overflow-y: hidden;
}
.nav-scroller .nav {
display: flex;
flex-wrap: nowrap;
padding-bottom: 1rem;
margin-top: -1px;
overflow-x: auto;
text-align: center;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
.btn-bd-primary {
--bd-violet-bg: #712cf9;
--bd-violet-rgb: 112.520718, 44.062154, 249.437846;
--bs-btn-font-weight: 600;
--bs-btn-color: var(--bs-white);
--bs-btn-bg: var(--bd-violet-bg);
--bs-btn-border-color: var(--bd-violet-bg);
--bs-btn-hover-color: var(--bs-white);
--bs-btn-hover-bg: #6528e0;
--bs-btn-hover-border-color: #6528e0;
--bs-btn-focus-shadow-rgb: var(--bd-violet-rgb);
--bs-btn-active-color: var(--bs-btn-hover-color);
--bs-btn-active-bg: #5a23c8;
--bs-btn-active-border-color: #5a23c8;
}
.bd-mode-toggle {
z-index: 1500;
}
</style>
</head>
<body>
<main>
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<span class="d-flex align-items-center text-body-emphasis text-decoration-none; fs-4">Report</span>
</header>
<div class="p-5 mb-4 bg-body-tertiary rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">Report</h1>
<p class="col-md-8 fs-4">Report of the day... blablabla</p>
</div>
</div>
{% for plugin in data['plugins'] %}
{% include plugin %}
{% endfor %}
{% include 'footer.html.j2' %}

@ -0,0 +1,16 @@
<nav>
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="reports_queries.html">Queries</a>
</li>
<li class="nav-item">
<a class="nav-link" href="reports_graphics.html">Graphics</a>
</li>
<li class="nav-item">
<a class="nav-link" href="reports_vt.html">VirusTotal</a>
</li>
<li class="nav-item">
<a class="nav-link" href="reports_dns_tunneling.html">DNS Tunneling</a>
</li>
</ul>
</nav>

@ -0,0 +1,43 @@
<!doctype html>
<html lang="en" data-bs-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.112.5">
<title>{{ data['title'] }}</title>
<link rel="canonical" href="https://getbootstrap.com/docs/5.3/examples/jumbotron/">
<link href="../templates/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<main>
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<span class="d-flex align-items-center text-body-emphasis text-decoration-none; fs-4">Report</span>
</header>
{% include 'nav.html.j2' %}
<div class="p-5 mb-4 bg-body-tertiary rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">Report - queries</h1>
<p class="col-md-8 fs-4">Report of the day... blablabla</p>
</div>
</div>
{% for item in data['queries'] %}
Packet id: {{ item['id'] }}<br />
<span style="margin-left:15pt;">Src: {{ item['src'] }};
Dest: {{ item['dst'] }};
{% if 'query' in item %}
Query: {{ item['query'] }};
{% elif 'answer' in item %}
Answer: {{ item['answer'] }};
{% endif %}
Type: {{ item['type'] }}
Len: {{ item['len'] }}</span><br /><br />
{% endfor %}
{% include 'footer.html.j2' %}

@ -0,0 +1,44 @@
<!doctype html>
<html lang="en" data-bs-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.112.5">
<title>{{ data['title'] }}</title>
<link rel="canonical" href="https://getbootstrap.com/docs/5.3/examples/jumbotron/">
<link href="../templates/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<main>
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<span class="d-flex align-items-center text-body-emphasis text-decoration-none; fs-4">Report</span>
</header>
{% include 'nav.html.j2' %}
<div class="p-5 mb-4 bg-body-tertiary rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">Report</h1>
<p class="col-md-8 fs-4">Get IP information for each public IP addresses from the DNS capture. Get informations from <a href='https://www.virustotal.com' title='VirusTotal'/>VirusTotal</a></p>
</div>
</div>
{% for entry in data['vt'] %}
<h3>{{ entry['ip']}}</h3>
{% if 'error' in entry %}
<p style='color:red'>{{ entry['error'] }}</p>
{% else %}
<ul>
{% for vt in entry['data'] %}
<li><strong>{{ vt }}</strong>: {{ entry['data'][vt] }}</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
{% include 'footer.html.j2' %}

10
requirements.txt Normal file

@ -0,0 +1,10 @@
pip
scapy
ArgumentParser
matplotlib
numpy
vt-py
requests
jinja2
python-whois
git+https://github.com/rthalley/dnspython.git

82
tunneling.py Normal file

@ -0,0 +1,82 @@
#!/usr/bin/venv python
# -*- coding: utf-8 -*-
def tunnelingDNSAttacks(report, queries, dnsbl):
"""
This function identify if we have a DNS tunneling
We can identify the payload size (512 bytes) and for a specific domain, how many occurs it appear.
For the payload size, according to the RFC 1035: https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.4
The maximum UDP message is 512 or less.
If we detect a lot of request to a specific domain, that can be a DNS tunneling
"""
report['blackDomain'] = _blackDomain(queries, dnsbl)
report['oversized'] = _oversized(queries)
def _blackDomain(queries, dnsbl) -> list:
""""
This function identify if a dns domain is in a blacklist
Return the report
"""
report = dict()
# Read the black domain list
dataDnsbl = list()
with open(dnsbl, 'r') as f:
for line in f:
dataDnsbl.append(line.replace("\n", ""))
for query in queries:
domain = _splitDomain(query['query'])
# Check if in dns blacklist
if domain in dataDnsbl:
if domain not in report:
report[domain] = list()
report[domain].append(query['src'])
else:
if query['src'] not in report[domain]:
report[domain].append(query['src'])
return report
def _oversized(queries) -> dict:
"""
This function analyzed the payload size and if it's oversize, that can be a DNS tunneling
"""
report = dict()
for query in queries:
if query['len'] > 220:
domain = _splitDomain(query['query'])
if domain not in report:
report[domain] = list()
report[domain].append({
'source': query['src'],
'len': query['len'],
})
else:
if query['src'] not in report[domain]:
report[domain].append({
'source': query['src'],
'len': query['len'],
})
return report
def _splitDomain(query) -> str:
"""
This function split the query for identifying the domain
"""
# Split to get the country code and the TLD
domainSplitted = str(query).split(".")
l = len(domainSplitted)
tld = domainSplitted[l - 3:l - 2]
cc = domainSplitted[l - 2 :l - 1]
try:
domain = f"{tld[0]}.{cc[0]}"
except IndexError:
print(f"Failed to parse the domain {query}")
domain = None
return domain

48
vt.py Normal file

@ -0,0 +1,48 @@
#!/ur/bin/env python3
import requests
class VT:
def __init__(self, api_key):
self._url = "https://www.virustotal.com/api/v3"
self._headers = {
'x-apki-key': api_key,
}
def getIPVirusTotal(self, ip, report):
res = requests.get(
f"{self._url}/ip_addresses/{ip}",
headers=self._headers
).json()
data = dict()
data['ip'] = ip
if 'error' in res:
report.append({
'error': res['error']['message'],
'ip': ip
})
return
vt = res['data']['attributes']
for entry in VT_ATTRIBUTES_MAPPING.keys():
if entry in vt:
try:
data[entry] = vt[entry]
except KeyError:
data[entry] = 'Unknown'
report.append(data)
def getRateFromHash(self, h, report):
headers = self._headers
headers['resource'] = h
res = requests.get(
f"{self._url}/file/report",
headers=headers
).json()
data = dict()