diff --git a/data/msfweb/images/logo.jpg b/data/msfweb/images/logo.jpg new file mode 100644 index 0000000000..ef76ce5fb9 Binary files /dev/null and b/data/msfweb/images/logo.jpg differ diff --git a/data/msfweb/index.rhtml b/data/msfweb/index.rhtml new file mode 100644 index 0000000000..8aa4f00ced --- /dev/null +++ b/data/msfweb/index.rhtml @@ -0,0 +1,15 @@ +<% + if ($:.include?(driver.server_local_directory) == false) + $:.unshift(driver.server_local_directory) + require 'msfweb_common' + end + %> + +<%= Msf::Ui::Web::Common.header %> + +
+
+ Welcome to the Metasploit Framework. +
+ +<%= Msf::Ui::Web::Common.footer %> diff --git a/data/msfweb/msfweb_common.rb b/data/msfweb/msfweb_common.rb new file mode 100644 index 0000000000..b790a69dcc --- /dev/null +++ b/data/msfweb/msfweb_common.rb @@ -0,0 +1,60 @@ +module Msf +module Ui +module Web + +### +# +# This class implements helper methods for sharing across web pages. +# +### +module Common + + def self.header(active = "none") + " + + + Metasploit Framework Web Console v<%= framework.version %> + + + +
+ +
+ msfweb +
+ + + + + +
+ + + + + + +
+ EXPLOITS + + PAYLOADS + + SESSIONS +
+
+ " + end + + def self.footer + " +
+ + + " + end + +end + +end +end +end diff --git a/data/msfweb/style.css b/data/msfweb/style.css new file mode 100644 index 0000000000..7ecbbb2c2b --- /dev/null +++ b/data/msfweb/style.css @@ -0,0 +1,243 @@ +html { + margin: 0px; + padding: 0px; +} + +body +{ + background: white; + font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif; + color: black; + font-size: 10pt; + margin: 0.1cm 0.5cm 0.1cm 0.5cm; +} + +A { + font-size: 10pt; + text-decoration: none; + color: navy; + font-weight: bold; +} + + +A:hover { + text-decoration: underline; +} + +.tabDark { + background: #dddddd; +} +A.tabDark { + +} + +.tabLight { + background: #eeeeee; +} +A.tabLight { + +} + +.tabActive { + +} +A.tabActive { + text-decoration: underline; +} + + +.sectionTitle { + color: black; + font-size: 10pt; + font-weight: bold; +} + +.listBody { + background: white; + color: black; + font-size: 10pt; +} + +A.nav { + font-size: 10pt; + font-weight: bold; +} + +.nav { + font-size: 10pt; + font-weight: normal; + font-weight: bold; +} + +.navHead { + font-size: 10pt; + font-weight: bold; +} + +.copy { + font-size: 10pt; + font-variant: small-caps; +} + +.modHead { + font-size: 10pt; + font-weight: bold; + color: white; +} + +.shellcode { + font-size: 10pt; + font-weight: normal; + color: black; +} + +.boldText { + font-size: 10pt; + color: black; + font-weight: bold; +} + +.moduleOutput { + font-size: 10pt; + color: black; +} + +.moduleInfo { + font-size: 10pt; + color: black; +} + +.textNormal { + font-size: 10pt; + color: black; +} + +.textBold { + font-size: 10pt; + color: black; + font-weight: bold; +} + +.textBoldDark { + font-size: 10pt; + color: grey; + font-weight: bold; +} + +.textBoldBright { + font-size: 10pt; + color: black; + font-weight: bold; +} + + +.textNormalColorA { + background: #eeeeee; + font-size: 10pt; + font-weight: normal; +} + +.textBoldColorA { + background: #eeeeee; + font-size: 10pt; + font-weight: bold; +} + + +.textNormalColorB { + background: #dddddd; + font-size: 10pt; + font-weight: normal; +} + +.textBoldColorB { + background: #dddddd; + font-size: 10pt; + font-weight: bold; +} + +.tblOuter { + background: black; +} + +.tblInner { + background: white; +} + + +select { + color: black; + background: #eeeeee; + font-weight: bold; + padding: 2px 2px 2px 2px; +} + +input { + font-weight: bold; + color: black; + background: #eeeeee; + color: black; + padding: 2px 10px 2px 10px; + border: 1px solid grey; +} + +input.button { + border-left: 1px solid grey; + border-top: 1px solid grey; + border-bottom: 2px solid black; + border-right: 2px solid black; + padding: 1px 5px 1px 5px; +} + +.iconset +{ + background: white; + padding: 2px 2px 2px 2px; + border: 1px solid white; +} + +.moduleList +{ + white-space: nowrap; +} + +.moduleIcons +{ + border-top: 1px solid #aaaaaa; + border-left: 1em solid #aaaaaa; + padding: 0.5em 0em 0em 0.25em; +} + +.moduleName +{ + border-top: 1px solid #aaaaaa; + padding: 0.5em 0.5em 0em 0em; +} + +.moduleSpacer +{ + padding: 10px 0px 0px 0px; + margin: 0; +} + +.CommandBar { + +} + +#CommandBarList { + padding: 0 1px 1px; + margin-left: 0; +} + +#CommandBarList li { + list-style: none; + margin: 0; + display: inline; +} + +#CommandBarList li a { +} + +#CommandBarList li a:hover { + border-bottom: 1px solid #black; +} diff --git a/lib/msf/base/config.rb b/lib/msf/base/config.rb index f6a45ccdbb..5d49393bb3 100644 --- a/lib/msf/base/config.rb +++ b/lib/msf/base/config.rb @@ -37,7 +37,8 @@ class Config < Hash 'ModuleDirectory' => "modules", 'LogDirectory' => "logs", 'SessionLogDirectory' => "logs/sessions", - 'PluginDirectory' => "plugins" + 'PluginDirectory' => "plugins", + 'DataDirectory' => "data" } ## @@ -94,6 +95,13 @@ class Config < Hash def self.user_module_directory self.new.user_module_directory end + + # + # Calls the instance method. + # + def self.data_directory + self.new.data_directory + end # # Calls the instance method. @@ -186,6 +194,13 @@ class Config < Hash config_directory + FileSep + "modules" end + # + # Returns the data directory + # + def data_directory + install_root + FileSep + self['DataDirectory'] + end + # # Initializes configuration, creating directories as necessary. # diff --git a/lib/msf/core/framework.rb b/lib/msf/core/framework.rb index 096610394b..edbe371840 100644 --- a/lib/msf/core/framework.rb +++ b/lib/msf/core/framework.rb @@ -83,6 +83,13 @@ class Framework return modules.recon end + # + # Returns the framework version in Major.Minor format. + # + def version + Version + end + # # Event management interface for registering event handler subscribers and # for interacting with the correlation engine. diff --git a/lib/msf/ui/web/driver.rb b/lib/msf/ui/web/driver.rb index 69561484cc..1d99e0c80f 100644 --- a/lib/msf/ui/web/driver.rb +++ b/lib/msf/ui/web/driver.rb @@ -1,3 +1,4 @@ +require 'rex/proto/http' require 'msf/core' require 'msf/base' require 'msf/ui' @@ -20,6 +21,27 @@ class Driver < Msf::Ui::Driver ConfigCore = "framework/core" ConfigGroup = "framework/ui/web" + # + # The msfweb resource handler that wrappers the default Erb handler. + # + class ResourceHandler < Rex::Proto::Http::Handler::Erb + def initialize(server, root_path, framework, driver, opts = {}) + opts['ErbCallback'] = ::Proc.new { |erb, cli, request, response| + query_string = request.qstring + meta_vars = request.meta_vars + erb.result(binding) + } + + super(server, root_path, opts) + + self.framework = framework + self.driver = driver + end + + attr_accessor :framework # :nodoc: + attr_accessor :driver # :nodoc: + end + # # The default port to listen for HTTP requests on. # @@ -33,7 +55,17 @@ class Driver < Msf::Ui::Driver # # The default root directory for requests. # - DefaultRoot = "/msfweb" + DefaultRoot = "/" + + # + # The default local directory for msfweb. + # + DefaultLocalDirectory = Msf::Config.data_directory + File::SEPARATOR + "msfweb" + + # + # The default index script. + # + DefaultIndex = "index.rhtml" # # Initializes a web driver instance and prepares it for listening to HTTP @@ -67,12 +99,16 @@ class Driver < Msf::Ui::Driver ilog("Web server started on #{host}:#{port}", LogSource) - service.add_resource( + # Mount the server root directory on the web server instance. We pass + # it a custom ErbCallback so that we can have it run in a context that + # has the framework instance defined. + service.mount( server_root, - 'Directory' => true, - 'Proc' => Proc.new { |cli, req| - on_request(cli, req) - }) + ResourceHandler, + false, + server_local_directory, + framework, + self) # Wait for the termination event to be set. term_event.wait @@ -93,12 +129,26 @@ class Driver < Msf::Ui::Driver end # - # Returns the root resource name, such as '/msfweb' + # Returns the root resource name, such as '/msfweb'. # def server_root opts['ServerRoot'] || DefaultRoot end + # + # Returns the server index, such as 'index.rhtml'. + # + def server_index + opts['ServerIndex'] || DefaultIndex + end + + # + # Returns the local directory that will hold the files to be serviced. + # + def server_local_directory + opts['ServerLocalDirectory'] || DefaultLocalDirectory + end + # # The framework instance associated with this driver. # diff --git a/lib/rex/proto/http/handler/erb.rb b/lib/rex/proto/http/handler/erb.rb index 3200f06b21..7e97372135 100644 --- a/lib/rex/proto/http/handler/erb.rb +++ b/lib/rex/proto/http/handler/erb.rb @@ -43,6 +43,9 @@ class Handler::Erb < Handler wlog("Erb::on_request: Dangerous request performed: #{resource}", LogSource) return + # If the request is for the root directory, use the document index file. + elsif (resource == '/') + resource += opts['DocumentIndex'] || 'index.rhtml' end begin @@ -51,22 +54,28 @@ class Handler::Erb < Handler # Calculate the actual file path on disk. file_path = root_path + resource - puts "file path is #{file_path}" - # Serialize the contents of the file data = ::IO.readlines(file_path).join - # Evaluate the data and set the output as the response body. - resp.body = evaluate(ERB.new(data), cli, req, resp) + # Set the content-type to text/html by default. We do this before + # evaluation so that the script can change it. + resp['Content-Type'] = server.mime_type(resource) - # Set the content-type to text/html by default. - resp['Content-Type'] = opts['MimeType'] + # If the requested file is a ruby html file, evaluate it. + if (File.extname(file_path) == ".rhtml") + # Evaluate the data and set the output as the response body. + resp.body = evaluate(ERB.new(data), cli, req, resp) + # Otherwise, just set the body to the data that was read. + else + resp.body = data + end rescue - elog("Erb::on_request: #{$!}\n\n#{$@.join("\n")}", LogSource) + elog("Erb::on_request: #{$!}", LogSource) - puts "exception: #{$!} #{$@.join("\n")}" + # Send a standard 404 message. + server.send_e404(cli, req) - resp = Response::E404.new + resp = nil end # Send the response to the @@ -83,8 +92,8 @@ class Handler::Erb < Handler def evaluate(erb, cli, request, response) # If the thing that created this handler wanted us to use a callback # instead of the default behavior, then let's do that. - if (opts['Callback']) - opts['Callback'].call(erb, cli, request, response) + if (opts['ErbCallback']) + opts['ErbCallback'].call(erb, cli, request, response) else Module.new.module_eval { query_string = request.qstring diff --git a/lib/rex/proto/http/server.rb b/lib/rex/proto/http/server.rb index 91760fa5ec..a05faf82d8 100644 --- a/lib/rex/proto/http/server.rb +++ b/lib/rex/proto/http/server.rb @@ -69,8 +69,35 @@ class Server include Proto + # + # A hash that associated a file extension with a mime type for use as the + # content type of responses. + # + ExtensionMimeTypes = + { + "rhtml" => "text/html", + "html" => "text/html", + "htm" => "text/htm", + "jpg" => "image/jpeg", + "jpeg" => "image/jpeg", + "jpeg" => "image/jpeg", + "gif" => "image/gif", + "png" => "image/png", + "bmp" => "image/bmp", + "txt" => "text/plain", + "css" => "text/css", + } + + # + # The default server name that will be returned in the Server attribute of + # a response. + # DefaultServer = "Rex" + # + # Initializes an HTTP server as listening on the provided port and + # hostname. + # def initialize(port = 80, listen_host = '0.0.0.0') self.listen_host = listen_host self.listen_port = port @@ -170,6 +197,38 @@ class Server resp['Server'] = DefaultServer end + # + # Returns the mime type associated with the supplied file. Right now the + # set of mime types is fairly limited. + # + def mime_type(file) + type = nil + + if (file =~ /\.(.+?)$/) + type = ExtensionMimeTypes[$1.downcase] + end + + type || "text/plain" + end + + # + # Sends a 404 error to the client for a given request. + # + def send_e404(cli, request) + resp = Response::E404.new + + resp.body = + "" + + "404 Not Found</title" + + "</head><body>" + + "<h1>Not found</h1>" + + "The requested URL #{request.resource} was not found on this server.<p><hr>" + + "</body></html>" + + # Send the response to the client like what + cli.send_response(resp) + end + attr_accessor :listen_port, :listen_host protected @@ -233,7 +292,6 @@ protected end } -begin if (p) # Create an instance of the handler for this resource handler = p[0].new(self, *p[2]) @@ -242,9 +300,10 @@ begin if (p[0].relative_resource_required?) # Substituted the mount point root in the request to make things # relative to the mount point. - request.relative_resource = request.resource.gsub(root, '') + request.relative_resource = request.resource.gsub(/^#{root}/, '') request.relative_resource = '/' + request.relative_resource if (request.relative_resource !~ /^\//) end + # If we found the resource handler for this resource, call its # procedure. @@ -266,27 +325,6 @@ begin if (cli.keepalive == false) close_client(cli) end -rescue - puts "bleh #{$!} #{$@.join("\n")}" -end - end - - # - # Sends a 404 error to the client for a given request. - # - def send_e404(cli, request) - resp = Response::E404.new - - resp.body = - "<html><head>" + - "<title>404 Not Found</title" + - "</head><body>" + - "<h1>Not found</h1>" + - "The requested URL #{request.resource} was not found on this server.<p><hr>" + - "</body></html>" - - # Send the response to the client like what - cli.send_response(resp) end end diff --git a/msfweb b/msfweb index 5c5f16ae03..8469ec891d 100755 --- a/msfweb +++ b/msfweb @@ -31,8 +31,5 @@ arguments.parse(ARGV) { |opt, idx, val| end } -# Create the driver instance. -driver = Msf::Ui::Web::Driver.new(opts) - -# Run with it. -driver.run +# Create the driver instance and run it. +Msf::Ui::Web::Driver.new(opts).run