rewrote from scratch the TunnelingProxy: now a simple multi-threaded TCPserver. Doesn't use anymore webrick/httpproxy
This commit is contained in:
@@ -24,14 +24,17 @@ module API
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Proxy::API::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
def self.pre_http_start(http_hook_server)
|
||||
proxy = BeEF::Extension::Proxy::HttpProxyZombie.instance
|
||||
proxy.start
|
||||
config = BeEF::Core::Configuration.instance
|
||||
config = BeEF::Core::Configuration.instance
|
||||
Thread.new{
|
||||
http_hook_server.semaphore.synchronize{
|
||||
BeEF::Extension::Proxy::Proxy.new
|
||||
}
|
||||
}
|
||||
print_success "HTTP Proxy: http://#{config.get('beef.extension.proxy.address')}:#{config.get('beef.extension.proxy.port')}"
|
||||
end
|
||||
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/proxy', false, BeEF::Extension::Requester::Handler)
|
||||
beef_server.mount('/proxy', BeEF::Extension::Requester::Handler)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#
|
||||
# Copyright 2011 Wade Alcorn wade@bindshell.net
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Proxy
|
||||
|
||||
class HttpProxyBase < WEBrick::HTTPProxyServer
|
||||
|
||||
# call BeEF::HttpProxyZombie.instance
|
||||
include Singleton
|
||||
|
||||
attr_reader :config
|
||||
|
||||
def initialize
|
||||
@configuration = BeEF::Core::Configuration.instance
|
||||
@config[:Logger] = WEBrick::Log.new($stdout, WEBrick::Log::ERROR)
|
||||
@config[:ServerType] = Thread
|
||||
super(@config)
|
||||
end
|
||||
|
||||
# remove beef hook if it exists
|
||||
def remove_hook(res)
|
||||
print_debug "[PROXY] Removing beef hook from page if present"
|
||||
res.body.gsub!(%r'<script.*?http.*?exploit.js.*?</script>', '')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -21,18 +21,12 @@ module Proxy
|
||||
|
||||
@short_name = 'proxy'
|
||||
@full_name = 'proxy'
|
||||
@description = 'The proxy allow to tunnel HTTP requests to the hooked domain through the victim browser'
|
||||
@description = 'The tunneling proxy allow to tunnel HTTP requests to the hooked domain through the victim browser'
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'webrick/httpproxy'
|
||||
require 'webrick/httputils'
|
||||
require 'webrick/httprequest'
|
||||
require 'webrick/httpresponse'
|
||||
require 'extensions/requester/models/http'
|
||||
require 'extensions/proxy/base'
|
||||
require 'extensions/proxy/zombie'
|
||||
require 'extensions/proxy/api'
|
||||
require 'extensions/proxy/handlers/zombie/handler'
|
||||
require 'extensions/proxy/proxy'
|
||||
require 'extensions/proxy/api'
|
||||
@@ -1,130 +0,0 @@
|
||||
#
|
||||
# Copyright 2011 Wade Alcorn wade@bindshell.net
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Proxy
|
||||
module Handlers
|
||||
module Zombie
|
||||
|
||||
class Handler
|
||||
|
||||
attr_reader :guard
|
||||
@response = nil
|
||||
H = BeEF::Core::Models::Http
|
||||
|
||||
# This function will forward requests to the target and
|
||||
# the browser will perform the request. Then the results
|
||||
# will be sent back.
|
||||
def forward_request(hooked_browser_id, req, res)
|
||||
|
||||
# validate that the raw request is correct and can be used
|
||||
req_parts = req.to_s.split(/ |\n/) # break up the request
|
||||
verb = req_parts[0]
|
||||
raise 'Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported' if not BeEF::Filters.is_valid_verb?(verb) #check verb
|
||||
# antisnatchor: is_valid_url supposes that the uri is relative, while here we're passing an absolute one
|
||||
#uri = req_parts[1]
|
||||
#raise 'Invalid URI' if not BeEF::Filters.is_valid_url?(uri) #check uri
|
||||
version = req_parts[2]
|
||||
raise 'Invalid HTTP version' if not BeEF::Filters.is_valid_http_version?(version) # check http version - HTTP/1.0
|
||||
# antisnatchor: the following checks are wrong. the req_parts array can always contains elements at different postions.
|
||||
# for example proxying Opera, the req_parts[3] is the User-Agent header...
|
||||
# host_str = req_parts[3]
|
||||
# raise 'Invalid HTTP host header' if not BeEF::Filters.is_valid_host_str?(host_str) # check host string - Host:
|
||||
# host = req_parts[4]
|
||||
# host_parts = host.split(/:/)
|
||||
# hostname = host_parts[0]
|
||||
# raise 'Invalid hostname' if not BeEF::Filters.is_valid_hostname?(hostname) #check the target hostname
|
||||
# hostport = host_parts[1] || nil
|
||||
# if !hostport.nil?
|
||||
# raise 'Invalid hostport' if not BeEF::Filters.nums_only?(hostport) #check the target hostport
|
||||
# end
|
||||
|
||||
# Saves the new HTTP request to the db for processing by browser.
|
||||
# IDs are created and incremented automatically by DataMapper.
|
||||
http = H.new(
|
||||
:request => req,
|
||||
:method => req.request_method.to_s,
|
||||
:domain => req.host,
|
||||
:port => req.port,
|
||||
:path => req.path.to_s,
|
||||
:request_date => Time.now,
|
||||
:hooked_browser_id => hooked_browser_id
|
||||
)
|
||||
http.save
|
||||
|
||||
# Starts a new thread scoped to this Handler instance, in order to minimize performance degradation
|
||||
# while waiting for the HTTP response to be stored in the db.
|
||||
print_info("[PROXY] Thread started in order to process request ##{http.id} to [#{req.path.to_s}] on domain [#{req.host}:#{req.port}]")
|
||||
@response_thread = Thread.new do
|
||||
while H.first(:id => http.id).has_ran != "complete"
|
||||
sleep 0.5
|
||||
end
|
||||
@response = H.first(:id => http.id)
|
||||
end
|
||||
|
||||
@response_thread.join
|
||||
print_info("[PROXY] Response for request ##{http.id} to [#{req.path.to_s}] on domain [#{req.host}:#{req.port}] correctly processed")
|
||||
|
||||
res.body = @response['response_data']
|
||||
|
||||
# set the original response status code
|
||||
res.status = @response['response_status_code']
|
||||
|
||||
headers = @response['response_headers']
|
||||
#print_debug("====== original HTTP response headers =======\n#{headers}")
|
||||
|
||||
# The following is needed to forward back some of the original HTTP response headers obtained via XHR calls.
|
||||
# Original XHR response headers are stored in extension_requester_http table (response_headers column),
|
||||
# but we are forwarding back only some of them (Server, X-.. - like X-Powered-By -, Content-Type, ... ).
|
||||
# Some of the original response headers need to be removed, like encoding and cache related: for example
|
||||
# about encoding, the original response headers says that the content-length is 1000 as the response is gzipped,
|
||||
# but the final content-length forwarded back by the proxy is clearly bigger. Date header follows the same way.
|
||||
headers_hash = Hash.new
|
||||
if(res.status != -1 && res.status != 0)
|
||||
headers.each_line do |line|
|
||||
# stripping the Encoding, Cache and other headers
|
||||
header_key = line.split(': ')[0]
|
||||
header_value = line.split(': ')[1]
|
||||
if !header_key.nil? &&
|
||||
header_key != "Content-Encoding" &&
|
||||
header_key != "Content-Length" &&
|
||||
header_key != "Keep-Alive" &&
|
||||
header_key != "Cache-Control" &&
|
||||
header_key != "Vary" &&
|
||||
header_key != "Pragma" &&
|
||||
header_key != "Connection" &&
|
||||
header_key != "Expires" &&
|
||||
header_key != "Accept-Ranges" &&
|
||||
header_key != "Date"
|
||||
if header_value.nil?
|
||||
#headers_hash[header_key] = ""
|
||||
else
|
||||
headers_hash[header_key] = header_value.gsub!(/[\n]+/,"")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# note: override_headers is a (new) method of WebRick::HTTPResponse (the BeEF patch one: core\ruby\patches\webrick\httpresponse.rb)
|
||||
res.override_headers(headers_hash)
|
||||
end
|
||||
res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
151
extensions/proxy/proxy.rb
Normal file
151
extensions/proxy/proxy.rb
Normal file
@@ -0,0 +1,151 @@
|
||||
#
|
||||
# Copyright 2011 Wade Alcorn wade@bindshell.net
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Proxy
|
||||
class Proxy
|
||||
|
||||
HB = BeEF::Core::Models::HookedBrowser
|
||||
H = BeEF::Core::Models::Http
|
||||
@response = nil
|
||||
|
||||
# Multi-threaded Tunneling Proxy: listens on host:port configured in extensions/proxy/config.yaml
|
||||
# and forwards requests to the hooked browser using the Requester component.
|
||||
def initialize
|
||||
@conf = BeEF::Core::Configuration.instance
|
||||
@proxy_server = TCPServer.new(@conf.get('beef.extension.proxy.address'), @conf.get('beef.extension.proxy.port'))
|
||||
|
||||
loop do
|
||||
proxy = @proxy_server.accept
|
||||
Thread.new proxy, &method(:handle_request)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_request socket
|
||||
request_line = socket.readline
|
||||
|
||||
method = request_line[/^\w+/]
|
||||
url = request_line[/^\w+\s+(\S+)/, 1]
|
||||
version = request_line[/HTTP\/(1\.\d)\s*$/, 1]
|
||||
|
||||
# We're overwriting the URI::Parser UNRESERVED regex to prevent BAD URI errors when sending attack vectors (see tolerant_parser)
|
||||
tolerant_parser = URI::Parser.new(:UNRESERVED => BeEF::Core::Configuration.instance.get("beef.extension.requester.uri_unreserved_chars"))
|
||||
uri = tolerant_parser.parse(url)
|
||||
|
||||
raw_request = request_line
|
||||
content_length = 0
|
||||
|
||||
loop do
|
||||
line = socket.readline
|
||||
|
||||
if line =~ /^Content-Length:\s+(\d+)\s*$/
|
||||
content_length = $1.to_i
|
||||
end
|
||||
|
||||
if line.strip.empty?
|
||||
# read data still in the socket, exactly <content_length> bytes
|
||||
if content_length >= 0
|
||||
raw_request += "\r\n" + socket.read(content_length)
|
||||
end
|
||||
break
|
||||
else
|
||||
raw_request += line
|
||||
end
|
||||
end
|
||||
|
||||
# Saves the new HTTP request to the db. It will be processed by the PreHookCallback of the requester component.
|
||||
# IDs are created and incremented automatically by DataMapper.
|
||||
http = H.new(
|
||||
:request => raw_request,
|
||||
:method => method,
|
||||
:domain => uri.host,
|
||||
:port => uri.port,
|
||||
:path => uri.path,
|
||||
:request_date => Time.now,
|
||||
:hooked_browser_id => self.get_tunneling_proxy
|
||||
)
|
||||
http.save
|
||||
print_debug("[PROXY] --> Forwarding request ##{http.id}: domain[#{http.domain}:#{http.port}], method[#{http.method}], path[#{http.path}]")
|
||||
|
||||
# Wait for the HTTP response to be stored in the db.
|
||||
# TODO: re-implement this with EventMachine or with the Observer pattern.
|
||||
while H.first(:id => http.id).has_ran != "complete"
|
||||
sleep 0.5
|
||||
end
|
||||
@response = H.first(:id => http.id)
|
||||
print_debug "[PROXY] <-- Response for request ##{@response.id} to [#{@response.path}] on domain [#{@response.domain}:#{@response.port}] correctly processed"
|
||||
|
||||
response_body = @response['response_data']
|
||||
response_status = @response['response_status_code']
|
||||
headers = @response['response_headers']
|
||||
|
||||
# The following is needed to forward back some of the original HTTP response headers obtained via XHR calls.
|
||||
# Original XHR response headers are stored in extension_requester_http table (response_headers column),
|
||||
# but we are forwarding back only some of them (Server, X-.. - like X-Powered-By -, Content-Type, ... ).
|
||||
# Some of the original response headers need to be removed, like encoding and cache related: for example
|
||||
# about encoding, the original response headers says that the content-length is 1000 as the response is gzipped,
|
||||
# but the final content-length forwarded back by the proxy is clearly bigger. Date header follows the same way.
|
||||
response_headers = ""
|
||||
if (response_status != -1 && response_status != 0)
|
||||
headers.each_line do |line|
|
||||
# stripping the Encoding, Cache and other headers
|
||||
header_key = line.split(': ')[0]
|
||||
header_value = line.split(': ')[1]
|
||||
if !header_key.nil? &&
|
||||
header_key != "Content-Encoding" &&
|
||||
header_key != "Keep-Alive" &&
|
||||
header_key != "Cache-Control" &&
|
||||
header_key != "Vary" &&
|
||||
header_key != "Pragma" &&
|
||||
header_key != "Connection" &&
|
||||
header_key != "Expires" &&
|
||||
header_key != "Accept-Ranges" &&
|
||||
header_key != "Date"
|
||||
if header_value.nil?
|
||||
#headers_hash[header_key] = ""
|
||||
else
|
||||
# update Content-Length with the valid one
|
||||
if header_key == "Content-Length"
|
||||
header_value = response_body.size
|
||||
response_headers += "Content-Length: #{header_value}\r\n"
|
||||
else
|
||||
response_headers += line
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
res = "HTTP/#{version} #{response_status}\r\n#{response_headers}\r\n\r\n#{response_body}"
|
||||
socket.write(res)
|
||||
socket.close
|
||||
end
|
||||
|
||||
def get_tunneling_proxy
|
||||
proxy_browser = HB.first(:is_proxy => true)
|
||||
if (proxy_browser != nil)
|
||||
proxy_browser_id = proxy_browser.id.to_s
|
||||
else
|
||||
proxy_browser_id = 1
|
||||
print_debug "[PROXY] Proxy browser not set. Defaulting to browser id #1"
|
||||
end
|
||||
proxy_browser_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
#
|
||||
# Copyright 2011 Wade Alcorn wade@bindshell.net
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Proxy
|
||||
|
||||
class HttpProxyZombie < BeEF::Extension::Proxy::HttpProxyBase
|
||||
|
||||
HB = BeEF::Core::Models::HookedBrowser
|
||||
|
||||
def initialize
|
||||
@configuration = BeEF::Core::Configuration.instance
|
||||
@config = {}
|
||||
@config[:BindAddress] = @configuration.get('beef.extension.proxy.address')
|
||||
@config[:Port] = @configuration.get('beef.extension.proxy.port')
|
||||
@config[:ServerName] = "BeEF " + @configuration.get('beef.version')
|
||||
@config[:ServerSoftware] = "BeEF " + @configuration.get('beef.version')
|
||||
super
|
||||
end
|
||||
|
||||
def service(req, res)
|
||||
proxy_browser = HB.first(:is_proxy => true)
|
||||
if(proxy_browser != nil)
|
||||
proxy_browser_id = proxy_browser.id.to_s
|
||||
#print_debug "[PROXY] Current proxy browser id is #" + proxy_browser_id
|
||||
else
|
||||
proxy_browser_id = 1
|
||||
print_debug "[PROXY] Proxy browser not set. Defaulting to browser id #1"
|
||||
end
|
||||
|
||||
forwarder = BeEF::Extension::Proxy::Handlers::Zombie::Handler.new
|
||||
res = forwarder.forward_request(proxy_browser_id, req, res)
|
||||
res
|
||||
# remove beef hook if it exists
|
||||
#remove_hook(res)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user