#!/usr/bin/env python

import re
import sys
import json
import argparse
import urllib.parse

def generate_template(base_url, features):

    # we could all need that
    imports = [
        "os", "io", "re", "sys",
        "json", "time", "base64", "requests",
        "subprocess", "urllib.parse",
        "user_agent"
    ]

    partial_imports = {
        "bs4": ["BeautifulSoup"],
        "hackingscripts.utils": ["util"],
        "hackingscripts.tools.exploits": ["rev_shell"],
        "urllib3.exceptions": ["InsecureRequestWarning"]
    }

    class_definitions = []

    main_code = []
    methods = []
    ip_address_arg = next(filter(lambda f: re.match(r"ip_address=(.*)", f), features), None)
    ip_address = "util.get_address()" if not ip_address_arg else "'" + ip_address_arg[1] + "'"

    variables = {
        "IP_ADDRESS": ip_address, 
        "BASE_URL": f'"{base_url}" if "LOCAL" not in sys.argv else "http://127.0.0.1:1337"',
        "USER_AGENT": "user_agent.generate_user_agent()"
    }
    
    proxy_arg = next(filter(lambda f: re.match(r"proxy=(.*)", f), features), None)
    if proxy_arg or "burp" in features:
        proxy_url = "http://127.0.0.1:8080" if not proxy_arg else proxy_arg[1]
        variables["PROXIES"] = json.dumps({"http": proxy_url, "https": proxy_url})
        proxy = """
    if \"proxies\" not in kwargs:
        kwargs[\"proxies\"] = PROXIES
"""
    else:
        proxy = ""

    if "vhost" in features or "subdomain" in features:
        url_parts = urllib.parse.urlparse(base_url)
        host_name = url_parts.netloc
        variables["HOST_NAME"] = f"'{host_name}' if \"LOCAL\" not in sys.argv else \"127.0.0.1:1337\""
        vhost_param = ", vhost=None"
        full_url = f"f'{url_parts.scheme}://{{vhost}}.{{HOST_NAME}}{{uri}}' if vhost else BASE_URL + uri"
    else:
        vhost_param = ""
        full_url = "BASE_URL + uri"
        
    methods.insert(0, f"""def request(method, uri{vhost_param}, **kwargs):
    if not uri.startswith("/") and uri != "":
        uri = "/" + uri

    client = requests
    if "session" in kwargs:
        client = kwargs["session"]
        del kwargs["session"]
    
    if "allow_redirects" not in kwargs:
        kwargs["allow_redirects"] = False
    
    if "verify" not in kwargs:
        kwargs["verify"] = False

    headers = kwargs.get("headers", {{}})
    headers["User-Agent"] = USER_AGENT
    kwargs["headers"] = headers
    {proxy}
    url = {full_url}
    return client.request(method, url, **kwargs)
""")

    if "register" in features or "account" in features:
        main_code.append("""if not register(USER):
        exit(1)
""")
        methods.append("""
def register(user):
    res = request("POST", "/register", data={"username": user.username, "password": user.password})
    if res.status_code != 200:
        print("[-] Error registering")
        return False
    
    return True
""")

    if "login" in features or "account" in features:
        main_code.append("""session = login(USER)
    if not session:
        exit(1)
""")
        variables["USER"] = 'User("username", "password")'
        class_definitions.append("""
class User:
    def __init__(self, username, password):
        self.username = username
        self.password = password
    def tobase64(self):
        return base64.b64encode(f"{self.username}:{self.password}".encode()).decode()
    def get_basic_auth(self):
        return "Basic " + self.tobase64()
""")

        methods.append("""
def login(user):
    session = requests.Session()
    res = request("POST", "/login", data={"username": user.username, "password": user.password}, session=session)
    if res.status_code != 200:
        print("[-] Error logging in")
        return None
    
    return session
""")

    if "sqli" in features:
        partial_imports["hackingscripts.utils.sqli"] = ["MySQLi", "PostgreSQLi", "BlindSQLi", "ReflectedSQLi"]
        class_definitions.append("""
class ReflectedSQLiPoC(MySQLi, ReflectedSQLi):
    def __init__(self):
        # TODO: specify reflected columns with their types
        super().__init__([None, str, int])
    def reflected_sqli(self, columns: list, table=None, condition=None, offset=None, verbose=False):
        # TODO: build query and extract columns from response
        return None
""")
        class_definitions.append("""
class BlindSQLiPoC(MySQLi, BlindSQLi):
    def blind_sqli(self, condition: str, verbose=False) -> bool:
        # TODO: build query and evaluate condition
        return False
""")

        main_code.append("""poc = ReflectedSQLiPoC()
    print(poc.get_current_user())  
""")
 
    if "http-server" in features or "web-server" in features or "httpserver" in features or "webserver" in features:
        partial_imports["hackingscripts.tools.server.webserver"] = ["HttpServer"]
        main_code.append("""http_server = HttpServer("0.0.0.0", 3000)
    http_server.enable_logging()
    http_server.add_route("/dynamic", on_request)
    http_server.serve_data("/static", b"static-content")
    http_server.start_background()
""")

        methods.append("""
def on_request(req):
    # TODO: auto generated method stub
    return 200, b"", { "X-Custom-Header": "1" }     
""")

    if len(main_code) == 0:
        main_code = ["pass"]

    main = f"""
if __name__ == "__main__":
    {'\n    '.join(main_code)}
"""

    imports = "\n".join(f"import {i}" for i in sorted(imports, key=len))
    imports += "\n" + "\n".join(sorted(list(f"from {p} import {', '.join(i)}" for p, i in partial_imports.items()), key=len))
    class_definitions = "\n".join(class_definitions)
    variables = "\n".join(f"{k} = {v}" for k, v in variables.items())
    header = f"""#!/usr/bin/env python

#
# THE BASE OF THIS FILE WAS AUTOMATICALLY GENERATED BY {' '.join(sys.argv)}
# For more information, visit: https://git.romanh.de/Roman/HackingScripts
# 

{imports}
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
{class_definitions}
{variables}

"""

    return header + "".join(methods) + main

if __name__ == "__main__":

    parser = argparse.ArgumentParser(
        description="Exploit Template for web attacks",
        formatter_class=argparse.RawTextHelpFormatter
    )

    available_features = [
        "ip_address=[...]: Local IP-Address for reverse connections",
        "burp|proxy=[...]: Tunnel traffic through a given proxy or Burp defaults",
        "subdomain|vhost: Allow to specify a subdomain for outgoing requests",
        "register|account: Generate an account registration method stub",
        "login|account: Generate an account login method stub",
        "sqli: Generate an template SQL-Injection class",
        "http-server|web-server: Generate code for starting an in-memory http server"
    ]

    parser.add_argument("url", type=str, help="Target URL")
    parser.add_argument(
        "-f",
        "--features",
        nargs="*",
        type=str,
        default=[],
        help="Optional list of features:\n- " + "\n- ".join(available_features)
    )

    args = parser.parse_args()

    url = args.url
    if "://" not in url:
        url = "http://" + url

    features = args.features
    template = generate_template(url, features)
    print(template)

