Merge pull request #1858 from beefproject/fix-1851

fixing #1851
This commit is contained in:
jcrew99
2020-01-17 16:25:02 +10:00
committed by GitHub
5 changed files with 150 additions and 9 deletions

View File

@@ -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

View File

@@ -20,10 +20,12 @@ module BeEF
@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 @@ module BeEF
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 @@ module BeEF
@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 @@ module BeEF
#@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

View File

@@ -82,15 +82,27 @@ module BeEF
# 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 @@ module BeEF
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 @@ module BeEF
: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 @@ module BeEF
# 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'),

View File

@@ -18,4 +18,110 @@ RSpec.describe 'BeEF Extension Requester' do
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

View File

@@ -25,3 +25,4 @@ RESTAPI_DNS = "http://" + ATTACK_DOMAIN + ":3000/api/dns"
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"