Skip to content

Instantly share code, notes, and snippets.

@zPrototype
Last active October 16, 2024 08:28
Show Gist options
  • Save zPrototype/c46c9dd582c4d83817cc25f69b1a922d to your computer and use it in GitHub Desktop.
Save zPrototype/c46c9dd582c4d83817cc25f69b1a922d to your computer and use it in GitHub Desktop.
{
"headers":
[
{"Access-Control-Allow-Origin": "*"},
{"X-Frame-Options": ["1", "1; mode=block"]},
{"Content-Security-Policy": ["default-src 'self'"]},
{"Transfer-Encoding": ["strict"]}
]
}
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