Skip to content

Commit f878215

Browse files
author
Felipe Molina
committed
Version 1.0 - SecTor edition
1 parent 083412a commit f878215

22 files changed

+1989
-1188
lines changed

ATTRIBUTION.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The following third-party repositories have been used for this project:
2+
* Plotly: https://github.com/plotly - For the library and geographical datasets - MIT License - It can be read here: https://github.com/plotly/datasets/blob/master/LICENSE

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dnspython = "*"
1818
pyyaml = "*"
1919
geoip2 = "*"
2020
brotli = "*"
21+
mongoengine = "*"
2122

2223
[dev-packages]
2324

Pipfile.lock

Lines changed: 620 additions & 605 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

async_poll_headers.py

Lines changed: 184 additions & 35 deletions
Large diffs are not rendered by default.

async_poll_whois_data.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from datetime import datetime
88
from optparse import OptionParser
99
import pandas as pd
10-
from tldextract import tldextract
1110
from dashboard.utils.utils import get_config,get_headers_collection
1211
import asyncio
1312
from time import time
@@ -68,7 +67,12 @@ def update_whois(db_document,whois_result,options,collection,mapping_df):
6867
if ("country" in whois_result["whois"].keys() and whois_result["whois"]['country'] is not None):
6968
try:
7069
logging.debug("Assigning the country %s to this record." % whois_result["whois"]["country"])
71-
collection.update_one({'_id': db_document["_id"]}, { '$set': {'whois': {'IPv4': options.ip, 'whois_data': whois_result['whois']} , 'country': whois_result["whois"]['country'], 'continent': mapping_df[mapping_df["TLD"]==whois_result['whois']['country'].lower()].Continent.values[0]} })
70+
collection.update_one({'_id': db_document["_id"]},
71+
{ '$set': {'whois': {'IPv4': options.ip, 'whois_data': whois_result['whois']} ,
72+
'country': whois_result["whois"]['country'],
73+
'continent': mapping_df[mapping_df["TLD"]==whois_result['whois']['country'].lower()].Continent.values[0]
74+
}
75+
})
7276
updated_whois+=1
7377
except Exception as ex:
7478
logging.error("Error updating this record in the DB with country and continent information: %s" % ex)

config.yml

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
11
db_environments:
2-
majestic:
2+
majestic_snapshots:
33
user: root
44
password: Headers123!
55
host: 127.0.0.1
66
port: 27017
77
database: headers
8-
headers_coll: headers_get
8+
headers_coll: header_scans
99
orphans_coll: orphan
10-
umbrella:
11-
user: root
12-
password: Headers123!
13-
host: 127.0.0.1
14-
port: 27017
15-
database: headers
16-
headers_coll: headers_umbrella
17-
orphans_coll: orphans
1810
general:
19-
scrapeops: <your-scrapeops-api-key>
11+
scrapeops: 855523ae-8c95-4594-91a7-8b01de2a028f
2012
abusable_domains:
2113
- '*.amazonaws.com':
2214
- exfil
@@ -28,18 +20,24 @@ general:
2820
- exec
2921
- 'cdn.jsdelivr.net':
3022
- exec
23+
- 'www.facebook.com':
24+
- exfil
3125
- '*.facebook.com':
3226
- exfil
3327
- '*.hotjar.com':
3428
- exfil
35-
- 'ask.hotjar.com':
29+
- 'ask.hotjar.io':
3630
- exfil
3731
- '*.herokuapp.com':
3832
- exfil
3933
- exec
4034
- '*.firebaseapp.com':
4135
- exfil
4236
- exec
37+
# It is possible to exfiltrate to googletagmanager.com by using custom events as well
38+
# https://www.analyticsmania.com/post/google-tag-manager-custom-event-trigger/
39+
# Integrate the custom event submision to Hotjar following this:
40+
# https://help.hotjar.com/hc/en-us/articles/4412561401111-How-to-Send-Events-with-Google-Tag-Manager
4341
- '*.google-analytics.com':
4442
- exfil
4543
- '*.azurestaticapps.net':
@@ -51,19 +49,19 @@ general:
5149
vulns_explanation:
5250
UNDEFINED: Undefined vulnerability
5351
NOCSP: No CSP policy was defined
54-
UNSAFEINLINE: The value 'unsafe-inline' was found in the directive {}.
55-
UNSAFEEVAL: The value 'unsafe-eval' was found in the directive {}.
56-
LENIENTSCHEME: The policy contained a lenient scheme handler, such as https:// or http:// within the directive {}. This could allow attackers to include any external resource as a source of the affected directive, as long as it follows the protocol indicated in the handle.
57-
CSPRO: Only the header 'Content-Security-Policy-Report-Only' was found. No policy is Content Security Policy is enforced in this case.
58-
THIRDPARTYABUSE: 'Third-party domains that could be abused were found in the directive {}: {}'
59-
DEFAULTSRC: The directive 'default-src' was not found. This is a critical fallback directive for cases where specific directives are not defined, such as script-src, object-src, or font-src
60-
FRAMEANCESTORS: The directive 'frame-ancestors' was not found. This would allow an attacker to embed this page into another one with <frame> and similar elements for clickjacking attacks.
61-
REPORTTO: The directive 'report-to' was not found. It is recommended to report all CSP error to centralised infrastructure for early detection of XSS attempts.
62-
BASEURI: The directive 'base-uri' was not found. This would allow an attacker to inject a malicious <base> element to produce all relative paths to be pointed at the malicious base URI.
63-
UPGRIR: The directive 'upgrade-insecure-request' was not found. This directive indicates the browser to upgrade all resources included in the site from http:// to https://.
64-
NDSCRIPTSRC: The directives 'script-src' and 'default-src' were not found.
65-
NDCONNECTSRC: The directives 'connect-src' and 'default-src' were not found.
66-
NDFRAMESRC: The directives 'frame-src' and 'default-src' were not found.
67-
NDCHILDSRC: The directives 'child-src' and 'default-src' were not found.
68-
NDOBJECTSRC: The directives 'object-src' and 'default-src' were not found.
69-
ORPHANDOMAIN: The domain '{}', present in the directive {}, is not registered.
52+
UNSAFEINLINE: The value 'unsafe-inline' found in '{}'.
53+
UNSAFEEVAL: The value 'unsafe-eval' found in '{}'.
54+
LENIENTSCHEME: The policy contained a lenient handler in '{}'.
55+
CSPRO: Header 'Content-Security-Policy-Report-Only' was found, but 'Content-Security-Policy' was not.
56+
THIRDPARTYABUSE: Detected in '{}' - {}
57+
NODEFAULTSRC: The directive 'default-src' was not found.
58+
NOFRAMEANCESTORS: The directive 'frame-ancestors' was not found.
59+
NOREPORTTO: Neither 'report-to' nor 'report-uri' were found.
60+
NOBASEURI: The directive 'base-uri' was not found.
61+
NOUPGRIR: The directive 'upgrade-insecure-request' was not found.
62+
NOSCRIPTSRC: The directives 'script-src' and 'default-src' were not found.
63+
NOCONNECTSRC: The directives 'connect-src' and 'default-src' were not found.
64+
NOFRAMESRC: The directives 'frame-src' and 'default-src' were not found.
65+
NOCHILDSRC: The directives 'child-src' and 'default-src' were not found.
66+
NOOBJECTSRC: The directives 'object-src' and 'default-src' were not found.
67+
ORPHANDOMAIN: Domain '{}', in '{}' of the '{}' header, is not found (NXDOMAIN and no WHOIS)

dashboard/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def update_target_collection(collection_name):
8888
children=[
8989
dcc.Dropdown(
9090
options=get_environments(),
91-
value="majestic",
91+
value="majestic_snapshots",
9292
id="collection-dropdown"
9393
),
9494
html.H2("Limit of data to load from database"),
@@ -115,4 +115,4 @@ def update_target_collection(collection_name):
115115
])
116116

117117
if __name__ == '__main__':
118-
app.run(debug=True,host="0.0.0.0")
118+
app.run(debug=False,host="0.0.0.0")

dashboard/data/countries.continents.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ No,Country or Area,ISO-3,M49 Code,Region 1,Region 2,Continent
194194
193,Samoa,WSM,882,Polynesia,,Oceania
195195
194,San Marino,SMR,674,Southern Europe,,Europe
196196
195,Sao Tome and Principe,STP,678,Middle Africa,Sub-Saharan Africa,Africa
197-
196,Sark,,680,Northern Europe,Channel Islands,Europe
197+
196,Sark,CRQ,680,Northern Europe,Channel Islands,Europe
198198
197,Saudi Arabia,SAU,682,Western Asia,,Asia
199199
198,Senegal,SEN,686,Western Africa,Sub-Saharan Africa,Africa
200200
199,Serbia,SRB,688,Southern Europe,,Europe

dashboard/pages/csp_directives.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
find_limit=10000
1818
data=pd.DataFrame()
1919

20-
config=get_config()
20+
config=get_config(environment="majestic_snapshots")
2121
collection = get_headers_collection(config)
2222
total_documents = collection.count_documents({})
2323
n_top_directive_values = 15
@@ -61,10 +61,21 @@ def reload_all_graphs(stored_data,selected_header,selected_directive,n_limit_dir
6161
find_limit=stored_data["find_limit"]
6262
print("Values - showing %s documents in graphs" % find_limit)
6363
# data = pd.DataFrame(list(collection.aggregate([{'$limit': find_limit},{'$match': {'headers.{}'.format(selected_header) : {'$exists': 1}}},{'$project': {"url":1, "headers":1 }}])))
64-
data_df = pd.DataFrame(list(collection.find({},{"url":1,"headers":1,"csp":1}).limit(find_limit)))
64+
data_df = pd.DataFrame(list(collection.aggregate([
65+
{ '$sort': { "scans.globalRank": 1 } },
66+
{ '$limit': find_limit },
67+
{ '$addFields': { 'last_scan': { '$first': { '$sortArray': { 'input': "$scans", 'sortBy': { 'date': -1 } } } } } },
68+
{ '$addFields': { 'headers_kv': { '$objectToArray': "$last_scan.headers" }, "csp_kv": {'$objectToArray': "$last_scan.csp"} } },
69+
{ '$match': {
70+
"headers_kv.k": { '$regex': '^{}$'.format(selected_header), '$options': "i" },
71+
"csp_kv.k": {'$regex': "^{}$".format(selected_directive), '$options': "i" }
72+
}
73+
},
74+
{'$project': {"url": 1, "headers": "$last_scan.headers", "csp": "$last_scan.csp" }}])))
75+
6576
# Beautify the "headers" Series
6677
# data_df["headers"] = data_df["headers"].map(lambda x: array_to_dict(x,tolower=True))
67-
data_df["headers_lower"]=data_df["headers"].map(lambda x: dict((k.lower(), v.lower()) for k,v in x.items()) if x is not None else None)
78+
data_df["headers_lower"]=data_df["headers"].map(lambda x: dict((k.lower(), v.lower()) for k,v in x.items()) if x is not None else {})
6879
# Prepare CSP data to visualise sites and directives, etc
6980
# csp_cspro=data_df["headers_lower"].map(lambda x: parse_csp(x,lower=True))
7081
# csp_columns_df=pd.DataFrame(csp_cspro.tolist(),columns=["csp","cspro"])
@@ -75,7 +86,7 @@ def reload_all_graphs(stored_data,selected_header,selected_directive,n_limit_dir
7586
csp_data=data_df[data_df["csp"].notnull()]
7687
# csp_data=csp_data[csp_data["csp"].map(lambda x: "" not in x.keys())]
7788
# Freeing up some memory
78-
csp_columns_df=None
89+
# csp_columns_df=None
7990

8091
# List unique header names
8192
header_names,counts_headers = np.unique(np.hstack(data_df["headers_lower"].map(lambda x: list(x.keys())).values),return_counts=True)

dashboard/pages/header_names.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
find_limit=10000
1818
data_df=pd.DataFrame()
1919

20-
config=get_config()
20+
config=get_config(environment="majestic_snapshots")
2121
collection = get_headers_collection(config)
2222
total_documents = collection.count_documents({})
2323
n_top_header_names = 20
@@ -35,18 +35,23 @@
3535
)
3636
def reload_all_graphs(stored_data,n_limit_header_names,n_limit_directive_names,collection_data):
3737
print("collection_data: "+collection_data)
38-
config=get_config(collection_data)
38+
config=get_config(environment=collection_data)
3939
collection = get_headers_collection(config)
4040
find_limit=stored_data["find_limit"]
4141

4242
print("Names - showing %s documents in graphs" % find_limit)
43-
data_df = pd.DataFrame(list(collection.find({},{"url":1,"headers":1,"csp":1}).limit(find_limit)))
43+
data_df = pd.DataFrame(list(collection.aggregate([
44+
{ '$sort': { "scans.globalRank": 1 } },
45+
{'$limit': find_limit},
46+
{'$addFields': { 'last_scan': { '$first': { '$sortArray': { 'input': "$scans", 'sortBy': { 'date': -1 } } } } } },
47+
{'$project': {"url":1,"headers":"$last_scan.headers","csp": "$last_scan.csp"}}
48+
])))
4449
print("Data pulled form DB. Shaping it with Pandas")
4550
# Beautify the "headers" Series
4651
# Make all the headers lowercase
4752
print("Beautifying headers")
4853
# data_df["headers"] = data_df["headers"].map(lambda x: array_to_dict(x,tolower=True))
49-
data_df["headers_lower"]=data_df["headers"].map(lambda x: dict((k.lower(), v.lower()) for k,v in x.items()) if x is not None else None)
54+
data_df["headers_lower"]=data_df["headers"].map(lambda x: dict((k.lower(), v.lower()) for k,v in x.items()) if x is not None else {})
5055
# Prepare CSP data to visualise sites and directives, etc. Indicate that all headers have been changed to lowercase
5156
# print("Parsing CSP headers")
5257
# csp_cspro=data_df["headers_lower"].map(lambda x: parse_csp(x,lower=True))
@@ -58,7 +63,7 @@ def reload_all_graphs(stored_data,n_limit_header_names,n_limit_directive_names,c
5863
csp_data=data_df[data_df["csp"].notnull()]
5964
# csp_data=csp_data[csp_data["csp"].map(lambda x: "" not in x.keys())]
6065
# Freeing up some memory
61-
csp_columns_df=None
66+
# csp_columns_df=None
6267

6368
# In case I needed to look at the csp ro stats, here's how I would get them
6469
# print("Filtering null CSP-RO headers and empty directives")

0 commit comments

Comments
 (0)