Sessions can be detached and killed from the console

git-svn-id: file:///home/svn/framework3/trunk@4437 4d416f70-5f16-0410-b530-b9f4589650da
unstable
HD Moore 2007-02-18 22:35:07 +00:00
parent 06ac34faf1
commit 51d61c161d
12 changed files with 283 additions and 184 deletions

View File

@ -9,47 +9,6 @@ class ConsoleController < ApplicationController
#
def index
# Work around rails stupidity
if(not $webrick_hooked_console)
$webrick.mount_proc("/_console") do |req, res|
res['Content-Type'] = "text/javascript"
m = req.path_info.match(/cid=(\d+)/)
if (m and m[1] and $msfweb.consoles[m[1]])
console = $msfweb.consoles[m[1]]
out = ''
tsp = Time.now.to_i
prompt_old = console.prompt
# Poll the console output for 15 seconds
while( tsp + 15 > Time.now.to_i and out.length == 0 and console.prompt == prompt_old)
out = console.read()
select(nil, nil, nil, 0.10)
end
out = out.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
pro = console.prompt.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
if (console.busy)
pro = '(running)'.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
end
script = "// Metasploit Web Console Data\n"
script += "var con_prompt = unescape('#{pro}');\n"
script += "var con_update = unescape('#{out}');\n"
res.body = script
else
res.body = '// Invalid console ID'
end
end
$webrick_hooked_console = true
end
cid = params[:id]
if (not (cid and $msfweb.consoles[cid]))
@ -58,28 +17,30 @@ class ConsoleController < ApplicationController
return
end
script = "// Metasploit Web Console Data\n"
out = ""
@cid = params[:id]
@console = $msfweb.consoles[@cid]
if(params[:cmd])
out = ''
if (params[:cmd].strip.length > 0)
if(params[:cmd])
@console.write(params[:cmd] + "\n")
end
out = out.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
pro = @console.prompt.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
if (@console.busy)
pro = '(running)'.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
if(params[:read])
out = @console.read() || ''
end
script = "// Metasploit Web Console Data\n"
script += "var con_prompt = unescape('#{pro}');\n"
script += "var con_update = unescape('#{out}');\n"
send_data(script, :type => "text/javascript")
if(params[:special])
case params[:special]
when 'kill'
@console.session_kill
when 'detach'
@console.session_detach
end
end
if(params[:tab])
@ -116,22 +77,24 @@ class ConsoleController < ApplicationController
cmdl = cmd_top[0, depth]
end
out = "\n" + opts.map{ |c| ">> " + c }.join("\n")
out << "\n" + opts.map{ |c| ">> " + c }.join("\n")
end
end
tln = cmdl.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
script += "var con_tabbed = unescape('#{tln}');\n"
end
if(params[:read])
out = out.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
pro = @console.prompt.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
tln = cmdl.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
if (@console.busy)
pro = '(running)'.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
end
script = "// Metasploit Web Console Data\n"
script += "var con_prompt = unescape('#{pro}');\n"
script += "var con_update = unescape('#{out}');\n"
script += "var con_tabbed = unescape('#{tln}');\n"
send_data(script, :type => "text/javascript")
end

View File

@ -14,57 +14,23 @@ class SessionsController < ApplicationController
end
def interact
# Work around rails stupidity
if(not $webrick_hooked_session)
$webrick.mount_proc("/_session") do |req, res|
res['Content-Type'] = "text/javascript"
m = req.path_info.match(/cid=(\d+)/)
if (m and m[1] and $msfweb.sessions[m[1].to_i])
cid = m[1].to_i
$msfweb.connect_session(cid)
out = ''
tsp = Time.now.to_i
# Poll the session output for 15 seconds
while( tsp + 15 > Time.now.to_i and out.length == 0)
out = $msfweb.read_session(cid)
select(nil, nil, nil, 0.10)
end
out = out.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
script = "// Metasploit Web Session Data\n"
script += "var ses_update = unescape('#{out}');\n"
res.body = script
else
res.body = '// Invalid session ID'
end
end
$webrick_hooked_session = true
end
cid = params[:id].to_i
$msfweb.connect_session(cid)
if(params[:cmd])
if (params[:cmd].strip.length > 0)
$msfweb.write_session(cid, params[:cmd] + "\n")
end
script = "// Metasploit Web Session Data\n"
script += "var ses_update = unescape('');\n"
if (params[:read])
$msfweb.connect_session(cid)
out = $msfweb.read_session(cid) || ''
out = out.unpack('C*').map{|c| sprintf("%%%.2x", c)}.join
script = "// Metasploit Web Session Data\n"
script += "var ses_update = unescape('#{out}');\n"
send_data(script, :type => "text/javascript")
end
end
end

View File

@ -54,16 +54,4 @@ require 'msf/base'
$msfweb = Msf::Ui::Web::Driver.new({'LogLevel' => 5})
$msframework = $msfweb.framework
$webrick = nil
$webrick_hooked_console = false
$webrick_hooked_session = false
module WEBrickHooker
def initialize(*args)
$webrick = self
super(*args)
end
end
WEBrick::HTTPServer.class_eval("include WEBrickHooker")

View File

@ -19,13 +19,36 @@ var con_prompt = "";
var con_update = "";
var con_tabbed = "";
// Internal commands
var cmd_internal =
{
help:function() {
console_printline(" Web Console Internal Commands\n");
console_printline("=========================================\n\n");
console_printline(" /help Show this text\n");
console_printline(" /clear Clear the screen\n");
console_printline(" /detach Detach an active session\n");
console_printline(" /kill Abort an active session\n");
console_printline("\n");
},
clear:function() {
console_output.innerHTML = '';
},
detach:function() {
console_printline(">> Detaching from any active session...\n");
new Ajax.Updater("console_update", document.location, {
asynchronous:true,
evalScripts:true,
parameters:"special=detach"
});
},
kill:function() {
console_printline(">> Killing any active session...\n");
new Ajax.Updater("console_update", document.location, {
asynchronous:true,
evalScripts:true,
parameters:"special=kill"
});
}
};
@ -54,9 +77,10 @@ function console_refocus() {
}
function console_read() {
new Ajax.Updater("console_update", '/_console/cid=' + console_id, {
new Ajax.Updater("console_update", document.location, {
asynchronous:true,
evalScripts:true,
parameters:"read=yes",
onComplete:console_read_output
});
}
@ -85,7 +109,7 @@ function console_read_output(req) {
console_update_output(req);
// Reschedule the console reader
setTimeout(console_read, 1);
setTimeout(console_read, 1000);
}
function console_update_output(req) {
@ -99,8 +123,10 @@ function console_update_output(req) {
}
console_prompt.innerHTML = con_prompt;
console_refocus();
if(con_update && con_update.length > 0) {
window.scrollTo(0, 10000000);
}
}
@ -109,7 +135,10 @@ function console_update_tabs(req) {
status_free();
if (con_update.length > 0) {
console_printline(con_update, 'output_line');
}
console_prompt.innerHTML = con_prompt;
console_input.value = con_tabbed;
@ -129,19 +158,23 @@ function console_keypress(e) {
console_printline("\n>> " + console_input.value + "\n\n", 'output_line')
if(cmd_internal[console_input.value]) {
cmd_internal[console_input.value]();
if(console_input.value[0] == '/') {
cmd_name = console_input.value.substring(1);
if(cmd_internal[cmd_name]) {
cmd_internal[cmd_name]();
console_input.value = "";
console_input.focus();
return keystroke_block(e);
}
}
status_busy();
new Ajax.Updater("console_update", document.location, {
asynchronous:true,
evalScripts:true,
parameters:"cmd=" + escape(console_input.value),
parameters:"read=yes&cmd=" + escape(console_input.value),
onComplete:console_update_output
});
@ -181,7 +214,7 @@ function console_keydown(e) {
new Ajax.Updater("console_update", document.location, {
asynchronous:true,
evalScripts:true,
parameters:"tab=" + escape(console_input.value),
parameters:"read=yes&tab=" + escape(console_input.value),
onComplete:console_update_tabs
});
return keystroke_block(e);

View File

@ -54,9 +54,10 @@ function session_refocus() {
}
function session_read() {
new Ajax.Updater("session_update", '/_session/cid=' + session_id, {
new Ajax.Updater("session_update", document.location, {
asynchronous:true,
evalScripts:true,
parameters:"read=yes",
onComplete:session_read_output
});
}
@ -84,8 +85,8 @@ function session_read_output(req) {
// Call the console updated
session_update_output(req);
// Reschedule the console reader
setTimeout(session_read, 1);
// Reschedule the session reader
setTimeout(session_read, 1000);
}
function session_update_output(req) {
@ -127,7 +128,7 @@ function session_keypress(e) {
new Ajax.Updater("session_update", document.location, {
asynchronous:true,
evalScripts:true,
parameters:"cmd=" + escape(session_input.value),
parameters:"read=yes&cmd=" + escape(session_input.value),
onComplete:session_update_output
});

View File

@ -42,11 +42,11 @@ html,body {
border: none;
padding: 0;
background: #000000;
font-size: 11px;
}
.input {
font: inherit;
font-family: fixed, console, terminal;
font-family: monospace, console, fixed, terminal;
background: #000000;
border: 0;
color: white;
@ -57,9 +57,8 @@ html,body {
.prompt {
color: white;
font: inherit;
font-family: monospace, console, fixed, terminal;
text-align: right;
font-family: fixed;
font-size: 11px;
}
@ -68,6 +67,11 @@ html,body {
margin-top: 1em;
}
#console_input {
font-size: 11px;
font-family: monospace, console, fixed, terminal;
}
#console_status {
color: #fff;
font-family: monospace;

View File

@ -43,6 +43,20 @@ module CommandDispatcher
driver.active_module = mod
end
#
# Returns the active session if one has been selected, otherwise nil is
# returned.
#
def active_session
driver.active_session
end
#
# Sets the active session for this driver instance.
#
def active_session=(mod)
driver.active_session = mod
end
#
# Checks to see if the driver is defanged.
#
@ -68,12 +82,6 @@ module CommandDispatcher
#
attr_accessor :driver
#
# The active, interactive session, if any
#
attr_accessor :active_session
end
###

View File

@ -266,6 +266,10 @@ class Driver < Msf::Ui::Driver
# The active module associated with the driver.
#
attr_accessor :active_module
#
# The active session associated with the driver.
#
attr_accessor :active_session
#
# If defanged is true, dangerous functionality, such as exploitation, irb,

View File

@ -105,6 +105,19 @@ class WebConsole
def busy
self.console.busy
end
def session_detach
if(self.console.active_session)
self.console.active_session.detach()
end
end
def session_kill
if(self.console.active_session)
self.console.active_session.kill()
end
end
end

View File

@ -27,8 +27,26 @@ class Client
'vhost' => self.hostname,
'version' => '1.1',
'agent' => "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
'uri_encode_mode' => 'hex-normal',
'uri_full_url' => false
#
# Evasion options
#
'uri_encode_mode' => 'hex-normal', # hex-all, hex-random, u-normal, u-random, u-all
'uri_full_url' => false, # bool
'pad_method_uri_count' => 1, # integer
'pad_uri_version_count' => 1, # integer
'pad_method_uri_type' => 'space', # space, tab, apache
'pad_uri_version_type' => 'space', # space, tab, apache
'method_random_valid' => false, # bool
'method_random_invalid' => false, # bool
'method_random_case' => false, # bool
'version_random_valid' => false, # bool
'version_random_invalid' => false, # bool
'version_random_case' => false, # bool
'uri_dir_self_reference' => false, # bool
'uri_dir_fake_relative' => false, # bool
'uri_use_backslashes' => false, # bool
'pad_fake_headers' => false, # bool
'pad_fake_headers_count' => 16, # integer
}
end
@ -37,7 +55,7 @@ class Client
#
def set_config(opts = {})
opts.each_pair do |var,val|
config[var]=val
self.config[var]=val
end
end
@ -336,6 +354,24 @@ class Client
# Return the uri
#
def set_uri(uri)
if (self.config['uri_dir_self_reference'])
uri.gsub!('/', '/./')
end
if (self.config['uri_dir_fake_relative'])
buf = ""
uri.split('/').each do |part|
cnt = rand(8)+2
1.upto(cnt) { |idx|
buf += "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1)
}
buf += ("/.." * cnt)
buf += "/" + part
end
uri = buf
end
if (self.config['uri_full_url'])
url = self.ssl ? "https" : "http"
url += self.config['vhost']
@ -349,11 +385,26 @@ class Client
#
# Return the cgi
# TODO:
# * Implement self-referential directories
# * Implement bogus relative directories
#
def set_cgi(uri)
if (self.config['uri_dir_self_reference'])
uri.gsub!('/', '/./')
end
if (self.config['uri_dir_fake_relative'])
buf = ""
uri.split('/').each do |part|
cnt = rand(8)+2
1.upto(cnt) { |idx|
buf += "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1)
}
buf += ("/.." * cnt)
buf += "/" + part
end
uri = buf
end
url = uri
if (self.config['uri_full_url'])
@ -370,22 +421,42 @@ class Client
# Return the HTTP method string
#
def set_method(method)
# TODO:
# * Randomize case
# * Replace with random valid method
# * Replace with random invalid method
method
ret = method
if (self.config['method_random_valid'])
ret = ['GET', 'POST', 'HEAD'][rand(3)]
end
if (self.config['method_random_invalid'])
ret = Rex::Text.rand_text_alpha(rand(20)+1)
end
if (self.config['method_random_case'])
ret = Rex::Text.to_rand_base(ret)
end
ret += "\r\n"
end
#
# Return the HTTP version string
#
def set_version(protocol, version)
# TODO:
# * Randomize case
# * Replace with random valid versions
# * Replace with random invalid versions
protocol + "/" + version + "\r\n"
ret = protocol + "/" + version
if (self.config['version_random_valid'])
ret = protocol + "/" + ['1.0', '1.1'][rand(2)]
end
if (self.config['version_random_invalid'])
ret = Rex::Text.rand_text_alphanumeric(rand(20)+1)
end
if (self.config['version_random_case'])
ret = Rex::Text.to_rand_base(ret)
end
ret += "\r\n"
end
#
@ -407,18 +478,44 @@ class Client
# Return the spacing between the method and uri
#
def set_method_uri_spacer
# TODO:
# * Support different space types
" "
len = self.config['pad_method_uri_count']
set = " "
buf = ""
case self.config['pad_method_uri_type']
when 'tab'
set = "\t"
when 'apache'
set = "\t \x0b\x0c\x0d"
end
while(buf.length < len)
buf << set[ rand(set.length) ]
end
return buf
end
#
# Return the spacing between the uri and the version
#
def set_uri_version_spacer
# TODO:
# * Support different space types
" "
len = self.config['pad_uri_version_count']
set = " "
buf = ""
case self.config['pad_uri_version_type']
when 'tab'
set = "\t"
when 'apache'
set = "\t \x0b\x0c\x0d"
end
while(buf.length < len)
buf << set[ rand(set.length) ]
end
return buf
end
#
@ -491,6 +588,15 @@ class Client
def set_extra_headers(headers)
buf = ''
if (self.config['pad_fake_headers'])
1.upto(self.config['pad_fake_headers_count']) do |i|
buf += set_formatted_header(
Rex::Text.rand_text_alphanumeric(rand(32)+1),
Rex::Text.rand_text_alphanumeric(rand(32)+1)
)
end
end
headers.each_pair do |var,val|
buf += set_formatted_header(var, val)
end

View File

@ -349,6 +349,17 @@ module Text
end
end
#
# Converts a string to random case
#
def self.to_rand_case(str)
buf = str.dup
0.upto(str.length) do |i|
buf[i,1] = rand(2) == 0 ? str[i,1].upcase : str[i,1].downcase
end
return buf
end
#
# Converts a hex string to a raw string
#
@ -478,6 +489,7 @@ module Text
#
##
# Generates a random character.
def self.rand_char(bad, chars = AllChars)
rand_text(1, bad, chars)

View File

@ -78,7 +78,6 @@ module Interactive
reset_ui()
ensure
# Mark this as completed
self.completed = true
end
@ -183,7 +182,7 @@ protected
while self.interacting
# Select input and rstream
sd = Rex::ThreadSafe.select([ _local_fd, _remote_fd(stream) ], nil, nil, 0.50)
sd = Rex::ThreadSafe.select([ _local_fd, _remote_fd(stream) ], nil, nil, 0.25)
# Cycle through the items that have data
# From the stream? Write to user_output.
@ -195,6 +194,8 @@ protected
_stream_read_local_write_remote(stream)
end
} if (sd)
Thread.pass
end
end