diff --git a/core/main/ar-migrations/015_create_http.rb b/core/main/ar-migrations/015_create_http.rb index 76d491cd1c..184f8844bf 100644 --- a/core/main/ar-migrations/015_create_http.rb +++ b/core/main/ar-migrations/015_create_http.rb @@ -3,7 +3,7 @@ class CreateHttp < ActiveRecord::Migration[6.0] def change create_table :https do |t| - t.references :hooked_browser + t.text :hooked_browser_id # The http request to perform. In clear text. t.text :request # Boolean value as string to say whether cross-domain requests are allowed diff --git a/extensions/requester/api/hook.rb b/extensions/requester/api/hook.rb index 6a381a59d0..4a072b0459 100644 --- a/extensions/requester/api/hook.rb +++ b/extensions/requester/api/hook.rb @@ -20,10 +20,12 @@ def requester_run(hb, body) @body = body # Generate all the requests and output them to the hooked browser output = [] + print_debug hb.to_json BeEF::Core::Models::Http.where(:hooked_browser_id => hb.session, :has_ran => "waiting").each { |h| output << self.requester_parse_db_request(h) } + return if output.empty? config = BeEF::Core::Configuration.instance ws = BeEF::Core::Websocket::Websocket.instance @@ -52,6 +54,7 @@ def requester_run(hb, body) end # if we use XHR-polling, add the component to the main hook file else + build_missing_beefjs_components 'beef.net.requester' # Send the command to perform the requests to the hooked browser add_to_body output @@ -95,6 +98,9 @@ def requester_parse_db_request(http_db_object) @host = http_db_object.domain @port = http_db_object.port + print_debug "http_db_object:" + print_debug http_db_object.to_json + #@note: retrieve HTTP headers values needed later, and the \r\n that indicates the start of the post-data (if any) req_parts.each_with_index do |value, index| if value.match(/^Content-Length:\s+(\d+)/) @@ -155,6 +161,9 @@ def requester_parse_db_request(http_db_object) #@note: parse HTTP headers Hash, adding them to the object that will be used by beef.net.requester.send headers.keys.each { |key| http_request_object['headers'][key] = headers[key] } + + print_debug "result http_request_object" + print_debug http_request_object.to_json http_request_object end diff --git a/extensions/requester/rest/requester.rb b/extensions/requester/rest/requester.rb index ab104e9a5b..73137a7e3f 100644 --- a/extensions/requester/rest/requester.rb +++ b/extensions/requester/rest/requester.rb @@ -82,15 +82,27 @@ class RequesterRest < BeEF::Core::Router::Router # Return a response by ID get '/response/:id' do begin + + # super debugging + + error = {} + + error[:code]=0 + id = params[:id] raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id) + error[:code]=1 responses = H.find(id) || nil + error[:code]=2 halt 404 if responses.nil? - + error[:code]=3 result = {} result[:success] = 'true' + error[:code]=4 + result[:result] = response2hash(responses) + error[:code]=5 result.to_json rescue InvalidParamError => e @@ -98,7 +110,11 @@ class RequesterRest < BeEF::Core::Router::Router halt 400 rescue StandardError => e print_error "Internal error while retrieving response with id #{id} (#{e.message})" - halt 500 + + error[:id] = id + error[:message] = e.message + error.to_json + # halt 500 end end @@ -195,6 +211,9 @@ class RequesterRest < BeEF::Core::Router::Router :allow_cross_domain => "true", ) + print_debug "added new http request for #{zombie.session}" + print_debug http.to_json + if verb.eql?('POST') || verb.eql?('PUT') req_parts.each_with_index do |value, index| if value.match(/^Content-Length/i) @@ -238,18 +257,24 @@ def request2hash(http) # Convert a response object to Hash def response2hash(http) - if http.response_data.length > (1024 * 100) # more thank 100K - response_data = http.response_data[0..(1024*100)] - response_data += "\n<---------- Response Data Truncated---------->" - else - response_data = http.response_data + + response_data = "" + + if not http.response_data.nil? + if http.response_data.length > (1024 * 100) # more thank 100K + response_data = http.response_data[0..(1024*100)] + response_data += "\n<---------- Response Data Truncated---------->" + end end + response_headers = "" + response_headers = http.response_headers if not http.response_headers.nil? + { :id => http.id, :request => http.request.force_encoding('UTF-8'), :response => response_data.force_encoding('UTF-8'), - :response_headers => http.response_headers.force_encoding('UTF-8'), + :response_headers => response_headers.force_encoding('UTF-8'), :proto => http.proto.force_encoding('UTF-8'), :domain => http.domain.force_encoding('UTF-8'), :port => http.port.force_encoding('UTF-8'), diff --git a/spec/beef/extensions/requester_spec.rb b/spec/beef/extensions/requester_spec.rb index 114fde979f..70a4c7ecb9 100644 --- a/spec/beef/extensions/requester_spec.rb +++ b/spec/beef/extensions/requester_spec.rb @@ -18,4 +18,110 @@ expect(requester).to respond_to(:requester_parse_db_request) end + # default skipped because browser hooking not working properly in travis-CI + xit 'requester works' do + # start beef server + + @config = BeEF::Core::Configuration.instance + @config.set('beef.credentials.user', "beef") + @config.set('beef.credentials.passwd', "beef") + + #generate api token + BeEF::Core::Crypto::api_token + + # load up DB + # Connect to DB + ActiveRecord::Base.logger = nil + OTR::ActiveRecord.migrations_paths = [File.join('core', 'main', 'ar-migrations')] + OTR::ActiveRecord.configure_from_hash!(adapter:'sqlite3', database:'beef.db') + # Migrate (if required) + context = ActiveRecord::Migration.new.migration_context + + + if context.needs_migration? + puts "migrating db" + ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration).migrate + end + + + http_hook_server = BeEF::Core::Server.instance + http_hook_server.prepare + @pids = fork do + BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server) + end + @pid = fork do + http_hook_server.start + end + # wait for server to start + sleep 1 + + https = BeEF::Core::Models::Http + + ### hook a new victim, use rest API to send request ########### + + api = BeefRestClient.new('http', ATTACK_DOMAIN, '3000', BEEF_USER, BEEF_PASSWD) + response = api.auth() + @token = response[:token] + puts "authenticated. api token: #{@token}" + puts 'hooking a new victim, waiting a few seconds...' + victim = BeefTest.new_victim + sleep 3 + + + response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {:token => @token}} + puts "hooks response: #{response}" + hb_details = JSON.parse(response.body) + puts "hb_details is empty: #{hb_details.empty?}" + while hb_details["hooked-browsers"]["online"].empty? + # get victim session + response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {:token => @token}} + puts "hooks response: #{response}" + hb_details = JSON.parse(response.body) + puts "json: #{hb_details}" + puts "online hooked browsers empty: #{hb_details["hooked-browsers"]["online"].empty?}" + + + end + + hb_session = hb_details["hooked-browsers"]["online"]["0"]["session"] + + puts "hooked browser: #{hb_session}" + + # clear all previous victim requests + cleared = https.where(:hooked_browser_id => hb_session).delete_all + puts "cleared #{cleared} previous request entries" + + # send a random request to localhost port 3000 + randreq = (0...8).map { (65 + rand(26)).chr }.join + + response = RestClient.post "#{RESTAPI_REQUESTER}/send/#{hb_session}?token=#{@token}", "proto=http&raw_request=GET%20%2Ftest#{randreq}%20HTTP%2F1.1%0AHost%3A%20localhost%3A3000%0A" + + + sleep 0.5 + sent_request = RestClient.get "#{RESTAPI_REQUESTER}/requests/#{hb_session}?token=#{@token}" + + puts "request sent: #{sent_request.to_json}" + sent_request = JSON.parse(sent_request) + reqid = sent_request["requests"][0]["id"] + + puts "getting response for id #{reqid}" + + response = RestClient.get "#{RESTAPI_REQUESTER}/response/#{reqid}?token=#{@token}" + + expect(response) + + ############################################################### + + # cleanup: delete test browser entries + https.where(:hooked_browser_id => hb_session).delete_all + + # kill the server + Process.kill("KILL",@pid) + Process.kill("KILL",@pids) + + puts "waiting for server to die.." + sleep 1 + + end + end diff --git a/spec/support/constants.rb b/spec/support/constants.rb index d8ab5913c2..9a14996ad8 100644 --- a/spec/support/constants.rb +++ b/spec/support/constants.rb @@ -25,3 +25,4 @@ RESTAPI_SENG = "http://" + ATTACK_DOMAIN + ":3000/api/seng" RESTAPI_ADMIN = "http://" + ATTACK_DOMAIN + ":3000/api/admin" RESTAPI_WEBRTC = "http://" + ATTACK_DOMAIN + ":3000/api/webrtc" +RESTAPI_REQUESTER = "http://" + ATTACK_DOMAIN + ":3000/api/requester"