Updates for the web interface

git-svn-id: file:///home/svn/framework3/trunk@4287 4d416f70-5f16-0410-b530-b9f4589650da
unstable
HD Moore 2007-01-19 08:46:06 +00:00
parent 180dbb09e0
commit 4bd5580784
9 changed files with 242 additions and 222 deletions

View File

@ -2,13 +2,29 @@
# Description: The AJAX console controller of msfweb v.3. Handles commands,
# operations and sessions carried over the web interface.
class ConsoleController < ApplicationController
def process_cmd
end
#
# Show the working shell and related facilities.
#
def idex
end
#
# Show the working shell and related facilities.
#
def index
cid = params[:id]
if (not (cid and $msfweb.consoles[cid]))
cid = $msfweb.create_console
redirect_to :id => cid
return
end
@cid = params[:id]
@console = $msfweb.consoles[@cid]
if(params[:cmd])
if (params[:cmd].strip.length > 0)
@console.write(params[:cmd] + "\n")
end
send_data(@console.read(), :type => "application/octet-stream")
end
end
end

View File

@ -13,19 +13,30 @@
</head>
<body onload="console_init()">
<div id="console_window">
<pre>
<%= Msf::Ui::Banner.to_s %>
=[ msf <%= Msf::Framework::Version %>
+ -- --=[ <%= $msframework.exploits.length %> exploits - <%= $msframework.payloads.length %> payloads
+ -- --=[ <%= $msframework.encoders.length %> encoders - <%= $msframework.nops.length %> nops
=[ <%= $msframework.exploits.length %> aux
</pre>
<pre>
<div id="console_output" class="output">
<%=h @console.read %>
</div>
<textarea id="console_input" class="input" wrap="off" onkeydown="console_keydown(event)" rows="1"></textarea></div>
</pre>
<textarea
rows="1"
id="console_prompt"
class="prompt"
disabled="true"
></textarea>
<textarea
id="console_input"
class="input"
wrap="off"
onkeydown="return console_keydown(event)"
rows="1"
></textarea>
</div>
</body>
</html>

View File

@ -48,6 +48,5 @@ require 'rex'
require 'msf/ui'
require 'msf/base'
Msf::Config.init
Msf::Logging.enable_log_source('msfweb', 5)
$msframework = Msf::Simple::Framework.create
$msfweb = Msf::Ui::Web::Driver.new({'LogLevel' => 5})
$msframework = $msfweb.framework

View File

@ -8,6 +8,7 @@ var console_history = new Array(); // Commands history
var console_hindex = 0; // Index to current command history
var console_input; // Object to console input
var console_output; // Object to console output
var console_prompt; // Object to console prompt
function console_refocus() {
console_input.blur();
@ -24,6 +25,7 @@ function console_printline(s, type) {
}
}
var prompt = 'msf> ';
var console_commands = {
print : function print(s) {
console_printline(s, "info");
@ -49,12 +51,33 @@ function console_execute() {
}
}
function console_update_output(req) {
console_printline(req.responseText);
console_input.focus();
}
function console_keydown(e) {
if (e.keyCode == 8) {
window.title = console_input.value;
}
if (e.keyCode == 13) { // enter
console_history.push(console_input.value);
try { console_execute(); } catch(er) { alert(er); };
setTimeout(function() { console_input.value = "msf> "; }, 0);
console_printline("[-] Unknown command: " + console_input.value);
new Ajax.Updater("console_update", document.location, {
asynchronous:true,
evalScripts:true,
parameters:"cmd=" + escape(console_input.value),
onComplete:console_update_output
});
console_input.value = "";
console_input.focus();
console_prompt = prompt;
return false;
} else if (e.keyCode == 38) { // up
// TODO: place upper cmd in history on console_input.value
} else if (e.keyCode == 40) { // down
@ -70,7 +93,9 @@ function console_init() {
console_input = document.getElementById("console_input");
console_output = document.getElementById("console_output");
console_input.innerHTML = "msf&gt; ";
console_prompt = document.getElementById("console_prompt");
console_prompt.value = prompt;
console_input.focus();
return true;
}

View File

@ -9,30 +9,30 @@
html,body {
width: 100%;
height: 100%;
overflow: hidden;
background: #444;
background: #000000;
color: #fff;
font-family: monospace;
}
#console_window {
width: 600px;
width: 100%;
margin: 1em;
background: #000000;
padding: 1em;
}
#output {
width: 100%;
white-space: pre;
white-space: -moz-pre-wrap;
background: #000000;
}
#input {
width: 100%;
border: none;
padding: 0;
overflow: auto;
background: #000000;
}
@ -40,7 +40,19 @@ html,body {
border: none;
font: inherit;
font-weight: bold;
margin-top: .5em;
color: #fff;
padding: 0;
margin-top: 1em;
background: #000000;
}
.prompt {
width: 3em;
margin-top: 1em;
border: none;
padding: 0;
background: #000000;
color: white;
font-weight: bold;
text-align: right;
}

View File

@ -9,6 +9,98 @@ module Web
require 'msf/ui/web/comm'
###
#
# This class implements a console instance for use by the web interface
#
###
class WebConsole
attr_accessor :pipe
attr_accessor :console
attr_accessor :console_id
attr_accessor :last_access
attr_accessor :framework
class WebConsolePipe < Rex::IO::BidirectionalPipe
attr_accessor :input
attr_accessor :output
attr_accessor :prompt
attr_accessor :killed
def eof?
self.pipe_input.eof?
end
def intrinsic_shell?
true
end
def supports_readline
false
end
def _print_prompt
end
def pgets
self.pipe_input.gets
end
end
def initialize(framework, console_id)
# Configure the framework
self.framework = framework
# Configure the ID
self.console_id = console_id
# Create a new pipe
self.pipe = WebConsolePipe.new
self.pipe.input = self.pipe.pipe_input
# Create a read subscriber
self.pipe.create_subscriber('msfweb')
# Initialize the console with our pipe
self.console = Msf::Ui::Console::Driver.new(
'msf>',
'>',
{
'Framework' => self.framework,
'LocalInput' => self.pipe,
'LocalOutput' => self.pipe,
}
)
Thread.new { self.console.run }
update_access()
end
def update_access
self.last_access = Time.now
end
def read
update_access
self.pipe.read_subscriber('msfweb')
end
def write(buf)
update_access
self.pipe.write_input(buf)
end
def shutdown
self.pipe.killed = true
self.pipe.close
end
end
###
#
# This class implements a user interface driver on a web interface.
@ -16,65 +108,14 @@ require 'msf/ui/web/comm'
###
class Driver < Msf::Ui::Driver
attr_accessor :framework # :nodoc:
attr_accessor :consoles # :nodoc:
attr_accessor :last_console # :nodoc:
ConfigCore = "framework/core"
ConfigGroup = "framework/ui/web"
@@Eid = 0
#
# Returns the next unique exploit identifier.
#
def self.next_eid
@@Eid += 1
@@Eid.to_s
end
#
# 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.
#
DefaultPort = 55555
#
# The default host to listen for HTTP requests on.
#
DefaultHost = "127.0.0.1"
#
# The default root directory for requests.
#
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
# requests. The constructor takes a hash of options that can control how
@ -87,6 +128,9 @@ class Driver < Msf::Ui::Driver
# Set the passed options hash for referencing later on.
self.opts = opts
# Initalize the consoles set
self.consoles = {}
# Initialize configuration
Msf::Config.init
@ -95,144 +139,55 @@ class Driver < Msf::Ui::Driver
# Initialize attributes
self.framework = Msf::Simple::Framework.create
# Initialize the termination event.
self.term_event = Rex::Sync::Event.new
# Include common helper stuff. If there is no common stuff to be
# included, then we'll just catch the exception and move on with our
# life.
begin
if ($:.include?(server_local_directory) == false)
$:.unshift(server_local_directory)
require 'msfweb_common'
end
rescue
end
end
#
# Starts the HTTP server and waits for termination.
#
def run
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
port = (opts['ServerPort'] || DefaultPort).to_i,
host = (opts['ServerHost'] || DefaultHost))
ilog("Web server started on #{host}:#{port}", LogSource)
# 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,
ResourceHandler,
false,
server_local_directory,
framework,
self)
# Add a resource that will be responsible for waiting for channels to
# have input that needs to be read from and written back to http
# clients. Since this call will block for an extended period of time,
# we set the long call flag to cause it to run in the context of a
# thread.
service.add_resource("/internal/comm_read",
'Proc' => Proc.new { |client, request|
begin
Comm.read_channels(client, request)
rescue ::Exception
dlog("comm_read: #{$!}")
end
},
'LongCall' => true)
# Add a resource for writing to channels.
service.add_resource("/internal/comm_write",
'Proc' => Proc.new { |client, request|
begin
Comm.write_channel(client, request)
rescue ::Exception
dlog("comm_write: #{$!}")
end
})
# Add a resource that will be used to create a channel for a given
# resource.
service.add_resource("/internal/comm_create",
'Proc' => Proc.new { |client, request|
begin
Comm.create_channel(client, request)
rescue ::Exception
dlog("comm_create: #{$!}")
end
})
# Initialize the console count
self.last_console = 0
# Give the comm an opportunity to set up so that it can receive
# notifications about session creation and so on.
# notifications about session creation and so on.
Comm.setup(framework)
end
# Wait for the termination event to be set.
term_event.wait
# Stop the source and clean it up.
Rex::ServiceManager.stop_service(service)
service.deref
def create_console
# Destroy any unused consoles
clean_consoles
console = WebConsole.new(self.framework, self.last_console)
self.last_console += 1
self.consoles[console.console_id.to_s] = console
console.console_id.to_s
end
def write_console(id, buf)
self.consoles[id] ? self.consoles.write(buf) : nil
end
def read_console(id)
self.consoles[id] ? self.consoles.read() : nil
end
def clean_consoles(timeout=300)
self.consoles.each_pair do |id, con|
if (con.last_access + timeout < Time.now)
con.shutdown
self.consoles.delete(id)
end
end
end
#
# Stub
#
def run
true
end
#
# Sets the event that will cause the web service to terminate.
#
def terminate
term_event.set
end
#
# 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.
#
attr_reader :framework
protected
attr_writer :framework # :nodoc:
attr_accessor :opts # :nodoc:
#
# The internal event used to cause the web service to stop.
#
attr_accessor :term_event
#
# The internal service context.
#
attr_accessor :service
#
# Initializes logging for the web server.
# Initializes logging for the web interface
#
def initialize_logging
level = (opts['LogLevel'] || 0).to_i

View File

@ -5,15 +5,17 @@ require 'rex/ui/text/output'
require 'rex/ui/text/output/buffer'
require 'rex/ui/text/input/buffer'
class BidirectionalPipe
include Rex::UI::Text::Output
class BidirectionalPipe < Rex::Ui::Text::Input
def initialize
@subscribers_out = {}
@pipe_input = Rex::Ui::Text::Input::Buffer.new
end
def pipe_input
@pipe_input
end
def close
@pipe_input.close
end

View File

@ -13,9 +13,12 @@ require 'rex/io/stream_abstraction'
###
class Input::Buffer < Rex::Ui::Text::Input
def initialize
@sock = Rex::IO::StreamAbstraction.new
class BufferSock
include Rex::IO::StreamAbstraction
end
def initialize
@sock = BufferSock.new
@sock.initialize_abstraction
end
@ -23,10 +26,6 @@ class Input::Buffer < Rex::Ui::Text::Input
@sock.cleanup_abstraction
end
def supports_readline
false
end
def sysread(len = 1)
@sock.rsock.sysread(1)
end

View File

@ -38,7 +38,7 @@ module Shell
# Initialize the prompt
self.init_prompt = prompt
self.prompt_char = prompt_char
# Initialize the user interface handles
init_ui(Input::Stdio.new, Output::Stdio.new)
end
@ -47,6 +47,7 @@ module Shell
# Initializes the user interface input/output classes.
#
def init_ui(in_input = nil, in_output = nil)
# Initialize the input and output methods
self.input = in_input
self.output = in_output