GithubHelp home page GithubHelp logo

sekgobela-kevin / broote Goto Github PK

View Code? Open in Web Editor NEW
0.0 1.0 0.0 59 KB

General purpose python bruteforce library built on top of perock

License: GNU General Public License v3.0

Python 100.00%
bruteforcer bruteforcing security testing asyncio threads hacking

broote's Introduction

broote

Broote is general purpose python bruteforce library built on top of perock. It aims to make bruteforcing with python easier and more enjoyable with less code. Everything will be handled internally with perock letting you focus on what matters.

No more long loops, managing threads or calculating cartesian product to generate bruteforce data. Broote does a good job in handling them without having to worry.

Steps for using broote library:

  • Define bruteforce data e.g passwords or usernames.
  • Specify target to bruteforce e.g url, webpage form or file with password.
  • Define how to interact/communicate with target(creates response).
  • Define what is considered success, failure or error based on response.
  • Start bruteforce and wait for results.

Broote does not create passwords or usernames but they can be generated with brute.
See perock for little more about broote library.

Dont expect broote to perform better than manually written code.

Install

Broote can be installed with pip in your command-line application.

pip install broote

Usage

Bruteforce data in broote is represented with fields, records and table.

import broote

# Defines field to use with table to create records.
usernames_field = broote.field("username", ["Ben", "Jackson", "Marry"])
passwords_field = broote.field("password", range(10))

# Table contains cartesian product of fields as records.
table = broote.table()
table.add_primary_field(usernames_field)
table.add_field(passwords_field)

See forcetable library for more about fields, records and table.

Forcetable library was integrated into broote but can be used directly without problems.

Primary field is important for making bruteforce much faster. As above, username is expected to be tried with password until there is success or run out of passwords.

Always provide primary field to improve performance.

Target in broote can be anything that points to the system to be bruteforced. That can be url to webpage, file path or any type of object. What matters is being able to use target to perform bruteforce.

Here how we can connect/interact with target pointed by url and then return response.

import requests

def connector(target, record):
    # target - url to webpage.
    # record - Dict like object with data to pass to request
    return requests.post(target, data=record)

def connector(target, record, session=None):
    # Session may be valuable to make things faster or share data.
    # session - optional session object.
    if session:
        return session.post(target, data=record)
    else:
        return requests.post(target, data=record)

Let define success and failure functions to define what is considered successful or failed bruteforce attempt.

import requests

def success(response):
    return b"logged in as " in response.content

def failure(response):
    return b"Username and password does not match" in response.content

Runner is used to execute bruteforce and merge everything together. There are different runners, some of which are concurrent others running one after the other.

thread_runner is used to connect/log to website to make things faster by using threads.

import broote
import requests

# Defines field to use with table to create records.
usernames_field = broote.field("username", ["Ben", "Jackson", "Marry"])
passwords_field = broote.field("password", range(10))

# Table contains cartesian product of fields as records.
table = broote.table()
table.add_primary_field(usernames_field)
table.add_field(passwords_field)


def connector(target, record, session=None):
    # target - url to webpage.
    # record - Dict with data to pass to request.
    # session - optional session object provided by runner.
    return requests.post(target, data=record)

def success(response):
    return b"logged in as " in response.content

def failure(response):
    return b"Username and password does not match" in response.content


# Creates runner executing in multiple threads.
target = "https://example.com/login"
runner = broote.thread_runner(target, table, connector=connector,success=success, failure=failure)

# Starts bruteforce into target as defined by connector().
runner.start()
runner.get_success_records() # [{'username': 'Marry', 'password': 8}]

The url used 'https://example.com/login' does not exists.

Runner is too strict when it comes to target_reached(), success(), failure() and target_error() functions.

Here is what they mean.

Target reached - Determines if target was reached after connecting.
Success - Determines if there was success.
        - Target shoud be reached and no failure or error.
Failure - Determines if attempt failed(e.g wrong password)

Target error - Determines if there was error after reaching target.  
             - Target  needs to be reached as this error originates from 
               target.
Client error - Determines if there was error before reaching target.   
             - Target should not be reached and respose should be exception 
             object.
Error - Determines if there was error when connecting to target.  
      - It should satisfy 'target error' and 'client error'.

Response of None wont be allowed and exception object will be taken as client error.

This shows runner with more functions like target_reached() and target_error() which are also important.

def connector(target, record):
    # Target - url to webpage.
    # Record - Dict with data to pass to request.
    return requests.post(target, data=record)

def target_reached(response):
    return response.status_code == 200

def target_error(response):
    return b"denied" in response.content

def success(response):
    return b"logged in as " in response.content

def failure(response):
    return b"username and password does not match" in response.content


# Creates runner executing in multiple threads.
target = "https://example.com/login"
runner = broote.thread_runner(
    target, 
    table, 
    connector=connector,
    target_reached=target_reached, 
    success=success, 
    failure=failure, 
    target_error=target_error
)

# Starts bruteforce into target as defined by connector().
runner.start()
runner.get_success_records() # [{'username': 'Marry', 'password': 8}]

There are other arguments that can be passed to runner including session or setting maximum success record.

optimize: Bool - Enables optimisations, default True.
               - It makes things faster(better leave it as True)

session: Callable | Any - Callable that creates session or any object
                          to use as session.
                        - If callable then it should be method or function.
                        - It may sometimes be better to share certain
                          object e.g session for web request.


# broote.basic_runner does not support 'max_workers' argument.
max_workers: int - Sets maximum workers execute bruteforce, default=10.
                 - Only supported by concurrent runners.

max_retries: int - Sets retries when target is not reached, default 1.
max_success_records: int - Maximum records to match, default None.
max_primary_success_records: int - Maximim records to match for each primary 
                                   field items.
max_success_primary_items: int - Maximum primary items with success records.

max_multiple_primary_items: int - Allows multiple primary items to be be 
                                  tried at same time.
                                - Performs tricks on top of cartesian product
                                  results.
                                - That means multiple usernames tried at
                                  same time.
                                - If using using file field, ensure 
                                  'read_all' argument is enabled.

excluded_primary_items: Iterable - Primary items to be excluded.

comparer: Callable - Influences how arguments like 'success' gets
                         intepreted against response.
                       - It makes it possible to treat the as other objects
                         other than just functions.
                       - e.g lambda: value, response: value(response)

after_connect - Callable - Function called before connecting to target.
after_connect: Callable - Function called after connecting to target.
                        - Great performing something after connecting to
                          target including creating logs.
                        - e.g lambda: record, response: success(respoce)

response_closer: Callable - Function for closing response.
session_closer: Callable - Function for closing session.

This simple code shows ways of using comparer and after_attempt arguments of runner.

def connector(target, record):
    return requests.post(target, data=record)

def comparer(value, response):
    return value in response.content

def after_connect(record, response):
    if b"logged in as " in response.content:
        username = record.get_item("username")
        password = record.get_item("password")
        print("Logged in as '{}' with '{}'".format(username, password))

# Creates runner executing in multiple threads.
target = "https://example.com/login"
runner = broote.thread_runner(
    target, 
    table, 
    connector=connector,
    target_reached=b"example.com", 
    success=b"logged in as ", 
    failure=b"username and password does not match", 
    target_error=b"denied",
    comparer=comparer
)

Here is another example not using requests library with response being string created from record.

def success(response):
    # Matches Username "Ben" and with password containing '1'
    return "Ben" in response and "1" in response

def connector(target, record):
    return "Target is '{}', record is '{}'".format(target, record)

def after(record, response):
    if success(response):
        print("Success:", record)

runner = broote.basic_runner(
    None, 
    table, 
    connector=connector,
    success=success,
    after_connect=after
)

broote.basic_runner is not concurrent(attempts wait for each other).

Asyncio version is just similar to thread version but difference is that the functions passed need to be awaitable.

async def success(response):
    # Matches Username "Ben" and Password 1
    return "Ben" in response and "1" in response

async def connector(target, record):
    return "Target is '{}', record is '{}'".format(target, record)


runner = broote.async_runner(
    None, 
    table, 
    connector=connector,
    success=success,
    max_workers=400
)

# Async runner can be started just like thread runner.
runner.start()
# runner.astart() is awaitable as compared to runner.start().
asyncio.run(runner.astart())

Broote has capability to execute multiple runners using multi runners.

That allows multiple different runners to be executed at the same time no matter the type of runner.

Each multi runner may do things differenly than others. multi_async_runner will execute runners using asyncio or different async thread if runner is not async.

import broote

async_runner = broote.async_runner(...)
thread_runner = broote.basic_runner(...)

multi_runner = multi_async_runner([async_runner, thread_runner])
multi_runner.start()

Type of runner does not matter to multi runners. multi_basic_runner can execute thread_runner or async_runner without problems. Multi runner can also be used with other multi runner just like regular runner.

Multi runner is not runner and may not contain some features of ordinary runner.

Remember that each runner runs independent of the others.

Influenced by:

Similar to broote

broote's People

Contributors

sekgobela-kevin avatar

Watchers

 avatar

broote's Issues

RuntimeError: Event Loop is Closed

Using broote.async_runner().start() causes errors 'RuntimeError: Event Loop is Closed'.
This does not happen when using await broote.async_runner().astart().

Here is how start() is implemented by broote.async_runner.

def start(self):
    if self._event_loop:
        loop = self._event_loop
    else:
        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            loop = asyncio.new_event_loop()    
    loop.run_until_complete(self._runner.run())
    if not self._event_loop:
        loop.close()

Update to perock v0.2.0

Perock v0.2.0 has fixed bug that resulted in wronly named methods of runner classes.
Broote runner classes also were based on same issue so it needs update.

Integration of forcetable exceptions

Since 'forcetable' is included with broote, also its exceptions need to.
Exception classes for forcetable need to be accessed through broote to avoid importing forctable.
Forcetable need to be made as if its part of broote with everything included.

'ctrl-c' does not quit program when using 'thread_runner'

'thread_runner' in some cases cannot be interupted by 'ctrl-c' on windows.
This has been happening on perock but did not happen on Linux Ubuntu.
I tried to reproduse it but it seems to work for now with KeyboardInterrupt being raised.

Record transformer feature

Record from table is less likely to be used raw as it especially by connector.
There needs a way to tarnform record before connector can use to ensure it is in right format.
The resulting object does not have to be a record by can be another type suitable for that bruteforce activity.

Remember that 'broote' is general purpose meaning its not what it may be used for.
Tranforming record could make it simple to perform bruteforce eliminating the need to define unneccessay funtions.

'after_attempt' argument be renamed to 'after_connect'

'after_attempt' is not that clear of whats going on and is mostly used by perock.
'after_connect' makes it clear that the function will be called after making connection with target.
Words like 'attack' and 'attempt' need to be avoided and be replaced with 'connect'.

Session raises exception if passed directly to runner

There seems to be problem with session if passed direcly without being wrapped to callable.
Broote attemps to wrap session into callable but there seems to be a problem with it.

Here is bug within _broote.py

# Performs transformation on some attributes
if self._session is not None:
    if not _util.is_method_function(self._session):
        self._session = lambda: self._session

Problem had todo with lambda expression which returns session attribute but overide the session attribute.
Its like session is a callable that returns itself , this related to references.

'connect' argument be ranamed to 'connector'

'connector' is more suitable as it shows that function or callable is expected which connects to target.
'connect' is more of like requiring boolean which determines if runner should connect to target.

Runner arguments are not tested

Tests added from previous commits dont test arguments passed to runner.
Runner arguments are very useful and dictate how runner functions and its results.
Current tests only tested methods of runner instances without arguments passed to them.

Integrated tests are also not added but thankfully the unittest does call .start() of runner instances.

Session function not called when passed to runner

session argument should resukt in session called when creating session but the function is set
directly without being called. The bug is result of using 'or' operator instead of 'and' which expects
session to both be method and function.

Here is the source of bug with _runner.py.

@classmethod
def create_session(cls):
    # Creates session object to use when connecting with target
    if self_._session != None:
        is_function = inspect.isfunction(self_._session)
        # 'and' should be replaced by 'or'.
        if is_function and inspect.ismethod(self_._session):
            return self_._session()
        return self_._session
    return super().create_session()

Package `__all__` attribute was not defined.

__all__ helps in filtering symbols when importing everying using asterisk/start import.
This is neccessary for avoiding modules of broote from colliding with another package using broote.
webrute is facing problems since broote has overided one of its modules.
This is caused by that broote start import also imports modules as __all__ was not defined at package level.

Forcetable that is imported by broote also did not define __all__ which risk broote symbols being overiden.
exceptions.py modules are likely to collide since packages may likely contain them.
Forcetable 'exceptions' module is also accessible through broote which can cause problems.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.