Last active
October 16, 2024 08:28
-
-
Save zPrototype/c46c9dd582c4d83817cc25f69b1a922d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"headers": | |
[ | |
{"Access-Control-Allow-Origin": "*"}, | |
{"X-Frame-Options": ["1", "1; mode=block"]}, | |
{"Content-Security-Policy": ["default-src 'self'"]}, | |
{"Transfer-Encoding": ["strict"]} | |
] | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import pathlib | |
import os | |
import logging | |
import argparse | |
import requests | |
import validators | |
import json | |
import urllib3 | |
from rich.console import Console | |
from urllib.parse import urlparse | |
# Disable annoying warnings when invalid ssl cert | |
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
# Setting the directory for the config file to reside in | |
CONFIG_DIR = os.path.join(os.path.dirname(__file__), "config/") | |
# Bootstrap args, logger and json config | |
def get_arguments(): | |
parser = argparse.ArgumentParser() | |
group = parser.add_mutually_exclusive_group(required=True) | |
group.add_argument("-u", "--url", help="Enter a domain you want to scan eg. https://tesla.com") | |
group.add_argument("-f", "--file", help="Enter a file containing urls you want to scan") | |
parser.add_argument("-o", "--output-dir", help="Specify the output directory", type=pathlib.Path, | |
default=os.getcwd()) | |
return parser.parse_args() | |
def get_logger(logfile_dir: str): | |
log_formatter = logging.Formatter("%(asctime)-15s [%(levelname)8s] [%(threadName)s] [%(name)-12s] - %(message)s") | |
log = logging.getLogger() | |
log.setLevel(logging.DEBUG) | |
file_handler = logging.FileHandler(os.path.join(logfile_dir, "veracheck.log"), "w+") | |
file_handler.setFormatter(log_formatter) | |
log.addHandler(file_handler) | |
return log | |
def load_header_config() -> list[str]: | |
with open(os.path.join(CONFIG_DIR, "headers.json"), "r") as hhandle: | |
return json.load(hhandle).get("headers") | |
# Define globals | |
MISSING_HEADERS = [] | |
WRONG_HEADERS = [] | |
CORRECT_HEADERS = [] | |
# Load config, args and logger | |
# Define other stuff we need | |
REQUIRED_HEADERS = load_header_config() | |
CONSOLE = Console() | |
HEADER_EXPORT_TYPE = [[str],[str]] | |
ARGS = get_arguments() | |
os.makedirs(ARGS.output_dir, exist_ok=True) | |
LOG = get_logger(ARGS.output_dir) | |
# Cmon I had to do it | |
def print_banner() -> None: | |
print(""" | |
@@@ @@@ @@@@@@@@ @@@@@@@ @@@@@@ @@@@@@@ @@@ @@@ @@@@@@@@ @@@@@@@ @@@ @@@ | |
@@@ @@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@ @@@ @@@@@@@@ @@@@@@@@ @@@ @@@ | |
@@! @@@ @@! @@! @@@ @@! @@@ !@@ @@! @@@ @@! !@@ @@! !@@ | |
!@! @!@ !@! !@! @!@ !@! @!@ !@! !@! @!@ !@! !@! !@! @!! | |
@!@ !@! @!!!:! @!@!!@! @!@!@!@! !@! @!@!@!@! @!!!:! !@! @!@@!@! | |
!@! !!! !!!!!: !!@!@! !!!@!!!! !!! !!!@!!!! !!!!!: !!! !!@!!! | |
:!: !!: !!: !!: :!! !!: !!! :!! !!: !!! !!: :!! !!: :!! | |
::!!:! :!: :!: !:! :!: !:! :!: :!: !:! :!: :!: :!: !:! | |
:::: :: :::: :: ::: :: ::: ::: ::: :: ::: :: :::: ::: ::: :: ::: | |
: : :: :: : : : : : : :: :: : : : : : :: :: :: :: : : ::: | |
""") | |
print("Developed by 0xPrototype") | |
# Return any input as list so we can always iterate and don't have to cover two cases of input | |
def process_input() -> list[str]: | |
if ARGS.file: | |
with open(ARGS.file, "r") as handle: | |
urls = handle.readlines() | |
urls = list(map(lambda d: d.strip(), urls)) | |
else: | |
urls = [ARGS.url] | |
return urls | |
# Check if URLs are in the correct format e.g. https://tesla.com | |
def validate_input_url(urllist: list[str]) -> bool: | |
for url in urllist: | |
if not validators.url(url): | |
print("You seemed to have entered an invalid URL.") | |
print("Check your URLs to ensure they follow the proper URL scheme of <Protocol>://<domain>.<tld>") | |
LOG.warning(f"Invalid URL: {url} supplied! Aborting..,") | |
return False | |
return True | |
# Get initial response from server to parse received headers | |
def get_response_headers(url: str, sess: requests.sessions.Session) -> list[tuple[str, str]]: | |
try: | |
res = sess.get(url, verify=False) | |
parsed_existing_header = [(key, val) for key, val in dict(res.headers).items()] | |
return parsed_existing_header | |
except Exception as e: | |
LOG.error(f"Exception occurred during request to {url}. Error is {e}") | |
return | |
# Little bit of json transform to get just the keys as strings and not dict_keys | |
def get_required_header_names_from_json() -> tuple[list[str], list[str]]: | |
r_header_names = [] | |
for r_header in REQUIRED_HEADERS: | |
r_header_names.append(list(r_header)[0]) | |
return r_header_names | |
# Initial check if a header is present to narrow down the value compare (probably costs more performance than it gives lol) | |
def check_is_header_present(r_header_names: list[str], existing_headers: list[tuple[str, str]]) -> list: | |
present_headers = [] | |
for r_header_name in r_header_names: | |
r_isset = 0 | |
for full_header in existing_headers: | |
# full_header = ("Header Name", "Header Value") | |
if r_header_name == full_header[0]: | |
present_headers.append(full_header) | |
r_isset = 1 | |
if not r_isset: | |
MISSING_HEADERS.append(r_header_name) | |
return present_headers | |
# Compare the values from the exising headers against spec | |
def check_header_value(present_headers: list[str]) -> None: | |
for req in REQUIRED_HEADERS: | |
v_is_correct = 0 | |
for p_header in present_headers: | |
if req.get(p_header[0]) is None: | |
continue | |
if p_header[1] in req.get(p_header[0]): | |
CORRECT_HEADERS.append(f"{p_header[0]}: {p_header[1]}") | |
v_is_correct = 1 | |
continue | |
if not v_is_correct: | |
WRONG_HEADERS.append(f"{p_header[0]}: {p_header[1]}") | |
continue | |
# This is ugly af | |
def do_generate_output(domain_name: str, existing_headers: list[tuple]) -> None: | |
with open(os.path.join(ARGS.output_dir, f"{domain_name}.txt"), "w") as correct_f_handle: | |
correct_f_handle.writelines("Correct Header:\n") | |
correct_f_handle.writelines("\n".join(CORRECT_HEADERS)) | |
correct_f_handle.write("\n\nWrong Header Value:\n") | |
correct_f_handle.write("\n".join(WRONG_HEADERS)) | |
correct_f_handle.write("\n\nMissing Header:\n") | |
correct_f_handle.write("\n".join(MISSING_HEADERS)) | |
# This shit ugly af too but oh well I wanted to use tuples... | |
with open(os.path.join(ARGS.output_dir, f"all_headers_{domain_name}.txt"), "w") as all_f_handle: | |
all_f_handle.write("\n".join("{}: {}".format(x[0], x[1]) for x in existing_headers)) | |
# There has to be a better solution.. but it works | |
def reset_globals() -> None: | |
global MISSING_HEADERS | |
global WRONG_HEADERS | |
global CORRECT_HEADERS | |
MISSING_HEADERS = [] | |
WRONG_HEADERS = [] | |
CORRECT_HEADERS = [] | |
def main() -> None: | |
print_banner() | |
urls = process_input() | |
r_header_names = get_required_header_names_from_json() | |
for url in urls: | |
reset_globals() # ugly af but oh well don't touch it if it works | |
domain = domain = urlparse(url).netloc | |
sess = requests.Session() | |
existing_headers = get_response_headers(url, sess) | |
if existing_headers is None: | |
LOG.warning("No headers fetched! Continuing!") | |
continue | |
present_headers = check_is_header_present(r_header_names, existing_headers) | |
check_header_value(present_headers) | |
print() | |
CONSOLE.print(f"[bold]Summary for: {url}") | |
CONSOLE.print("[bold]Correct Headers:") | |
[CONSOLE.print(f"[green]{json.dumps(x, indent=4)}") for x in CORRECT_HEADERS] | |
print() | |
CONSOLE.print("[bold]Wrong Values:") | |
[CONSOLE.print(f"[yellow]{json.dumps(x, indent=4)}") for x in WRONG_HEADERS] | |
print() | |
CONSOLE.print("[bold]Missing Headers:") | |
[CONSOLE.print(f"[red]{x}") for x in MISSING_HEADERS] | |
do_generate_output(domain, existing_headers) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment