Skip to content

Instantly share code, notes, and snippets.

@ckxng
Last active January 3, 2021 14:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ckxng/236f7bba03320570bf482848791bbe29 to your computer and use it in GitHub Desktop.
Save ckxng/236f7bba03320570bf482848791bbe29 to your computer and use it in GitHub Desktop.
emergency shell script
#!/usr/bin/perl -WT
# Using taint and strict. All variables must be declared and all input must be
# processed before use. The most aggressive settings are used for warnings.
# This script will fail if it is misused or perscribed conditions are not met.
use warnings FATAL => 'all';
use strict;
=head1 NAME
emergency-shell
=cut
package Ckxng::EmergencyShell;
=head1 SYNOPSIS
% sudo emergency-shell
This shell is for emergency use only.
If you require routine access to run commands with elevated priviliges, please
update config management or open a request for the specific access required.
You are required to enter a ticket number related to your activities. This use
of the emergency shell will be logged if you continue. Ctrl-C to exit.
Ticket number (AAA-123 or INC123): JIRA-1234
Script started, file is /var/log/eshell/history.username.2021-01-03.374910.JIRA-1234.typescript
JIRA-1234! root:/home/username# env
=head1 DESCRIPTION
This script safely creates an interactive shell which is logged, three
different ways: syslog (via. logger), disk (via. bash history), and full
session recording to disk (via. script). Logs are written to a common
directory, but posix permissions prevent users other than the effective user
from reading the logs on disk.
=head1 INSTALL
This script was designed for Linux systems, and has only been tested on Ubuntu
and CentOS operating systems.
The following binaries must be available:
=over 4
=item * /bin/bash
=item * /usr/bin/stat
=item * /usr/bin/tty
=item * /usr/bin/logger
=item * /usr/bin/script
=back
The directory I</var/log/eshell> should be created with mode 1777.
mkdir /var/log/eshell
chmod 1777 /var/log/eshell
=head1 SUBROUTINES
=head2 B<prompt>()
Warn the user that this script is for emergency use only and collect the ticket
number. JIRA-type ticket numbers are accepted, as well as tickets beginning
with "INC" followed by a number.
If an invalid ticket is detected, or Ctrl-C is captured, then quit without
logging anything.
=cut
sub prompt {
print <<BANNER;
This shell is for emergency use only.
If you require routine access to run commands with elevated priviliges, please
update config management or open a request for the specific access required.
You are required to enter a ticket number related to your activities. This use
of the emergency shell will be logged if you continue. Ctrl-C to exit.
BANNER
print 'Ticket number (AAA-123 or INC123): ';
my $ticket;
if(<> =~ /^([a-zA-Z]+\-[0-9]+|[iI][nN][cC][0-9]+)$/) {
$ticket = $1;
} else {
print "Invalid ticket number. Quitting.\n";
exit 1;
}
$ENV{SHELLTICKET} = $ticket;
}
=head2 B<envsetup>()
B<prompt>() must complete successfully before calling this subroutine.
Otherwise, blanks will be seen anywhere that SHELLTICKET is expected and perl
will complain.
Strip away most existing environment variapbles to prevent tampering and setup
the environment for the shell session. This is also where logging is
configured in bash and where the log prefix string is created for logger to
send to syslog.
=cut
sub envsetup {
# strip all environment variables except HOME or SHELLTICKET (which we set)
# to prevent leakage and tampering with shell logging mechanisms
for my $key(keys(%ENV)) {
delete $ENV{$key} unless $key =~ /^(HOME|SHELLTICKET)$/;
}
# must be set when in taint mode before using a subshell
$ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin';
# discover the current time and save the values into the environment
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
$ENV{SHELLDATETIME} = sprintf('%04d-%02d-%02dT%02d:%02d:%02d', $year+1900, $mon+1, $mday, $hour, $min, $sec);
$ENV{SHELLDATE} = sprintf('%04d-%02d-%02d', $year+1900, $mon+1, $mday);
# discover the currnet owner of the tty, and set it if the value is compliant
# with POSIX standard IEEE Std 1003.1-2001 3.426 and 3.276
my $user = `/usr/bin/stat -c %U \$(/usr/bin/tty)`;
if($user =~ /^([a-zA-Z_\.][a-zA-Z0-9\-_\.]*)$/) {
$ENV{SHELLUSER} = $1;
} else {
die "Unable to determine tty owner using stat and tty commands\n";
}
# discover the current PID of this process, and use it as the shellid for
# logging purposes
$ENV{SHELLID} = $$;
# log the following essential information with each syslog message. (note
# that the tty user is already captured by default when using logger)
$ENV{SHELLLOGPREFIX} = sprintf('SHELL=%s TICKET=%s', $ENV{SHELLID}, $ENV{SHELLTICKET});
# setup the environment for later use by bash
$ENV{PS1} = sprintf('%s! \u:\w\$ ', $ENV{SHELLTICKET});
$ENV{HISTFILE} = sprintf('/var/log/eshell/history.%s.%s.%s.%s', $ENV{SHELLUSER}, $ENV{SHELLDATE}, $ENV{SHELLID}, $ENV{SHELLTICKET});
$ENV{PROMPT_COMMAND} = sprintf('RETURN=$?; history -a; /usr/bin/logger "%s HISTORY: $(history 1)"', $ENV{SHELLLOGPREFIX});
# set the umask before creating any files. this prevents users other than
# the effective user from reading logs
umask 0077;
}
=head2 B<openhistory>()
B<prompt>() and B<envsetup>() must complete sucessfully before calling this
subroutine. Otherwise, log files might be created in the current working
directory, or logfile creation might fail. Blanks will also appear in the
written log file, and perl will complain.
Due to a quirk in how the history command within bash operates, we have a
clever opportunity to log some extra metadata at the beginning of the history
session. When PROMPT_COMMAND executes for the first time, before the user has
actually executed a command, the embedded I<history 1> command will retrieve
the previous command and log it via. I<logger>. By creating our own history
file, and logging this line to it, we are able to inject this entry as the
first line logged. If we did not do this, a blank line would be logged as the
first entry instead.
=cut
sub openhistory {
# add an initial logging line to the history file
open HIST, sprintf('>%s', $ENV{HISTFILE}) or die sprintf('HISTFILE %s cannot be opened', $ENV{HISTFILE});
printf HIST "# History for USER=%s DATETIME=%s %s\n", $ENV{SHELLUSER}, $ENV{SHELLDATETIME}, $ENV{SHELLLOGPREFIX};
close HIST;
}
=head2 B<runshell>()
B<prompt>() and B<envsetup>() must complete sucessfully before calling this
subroutine, and B<openhistory>() is recommended. If this is not done, then
SHELLLOGPREFIX will be blank, and more importantly, so will HISTFILE, which
will cause script to write files to the local directory, or fail. Not running
B<envsetup>() will also prevent the I<umask> from being set correctly, which
will have security ramifications.
Run a I<bash> shell (without reading rc files) from within the context of the
I<script> command. This will log all input and output.If a user's input or
outut contains PII or PHI, then this file could be sensitive. It is imperitive
that B<envsetup> set the I<umask> prior to running these commands or else users
other than this effective user might be able to read the logs.
=cut
sub runshell {
# log this execution and open the shell
my $error = system '/usr/bin/logger', sprintf('%s Starting emergency shell', $ENV{SHELLLOGPREFIX});
die "Failed to log using logger\n" if $error;
$error = system '/usr/bin/script', '-c', '/bin/bash --norc', '-f', sprintf('-t%s.timing', $ENV{HISTFILE}), sprintf('%s.typescript', $ENV{HISTFILE});
die "Failed to run bash shell using script\n" if $error;
$error = system '/usr/bin/logger', sprintf('%s Stopping emergency shell', $ENV{SHELLLOGPREFIX});
die "Failed to log using logger\n" if $error;
}
=head2 B<main>()
Run each of the subroutines in the correct sequence when this module is called
as a script.
=cut
sub main {
# run all subroutines
prompt();
envsetup();
openhistory();
runshell();
}
main() unless caller;
=head1 COPYRIGHT
Copyright (c) 2021. Cameron King.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=head1 AUTHOR
Cameron King <http://cameronking.me>
=cut
1;
NAME
emergency-shell
SYNOPSIS
% sudo emergency-shell
This shell is for emergency use only.
If you require routine access to run commands with elevated priviliges, please
update config management or open a request for the specific access required.
You are required to enter a ticket number related to your activities. This use
of the emergency shell will be logged if you continue. Ctrl-C to exit.
Ticket number (AAA-123 or INC123): JIRA-1234
Script started, file is /var/log/eshell/history.username.2021-01-03.374910.JIRA-1234.typescript
JIRA-1234! root:/home/username# env
DESCRIPTION
This script safely creates an interactive shell which is logged, three
different ways: syslog (via. logger), disk (via. bash history), and full
session recording to disk (via. script). Logs are written to a common
directory, but posix permissions prevent users other than the effective
user from reading the logs on disk.
INSTALL
This script was designed for Linux systems, and has only been tested on
Ubuntu and CentOS operating systems.
The following binaries must be available:
* /bin/bash
* /usr/bin/stat
* /usr/bin/tty
* /usr/bin/logger
* /usr/bin/script
The directory */var/log/eshell* should be created with mode 1777.
mkdir /var/log/eshell
chmod 1777 /var/log/eshell
SUBROUTINES
prompt()
Warn the user that this script is for emergency use only and collect the
ticket number. JIRA-type ticket numbers are accepted, as well as tickets
beginning with "INC" followed by a number.
If an invalid ticket is detected, or Ctrl-C is captured, then quit
without logging anything.
envsetup()
prompt() must complete successfully before calling this subroutine.
Otherwise, blanks will be seen anywhere that SHELLTICKET is expected and
perl will complain.
Strip away most existing environment variapbles to prevent tampering and
setup the environment for the shell session. This is also where logging
is configured in bash and where the log prefix string is created for
logger to send to syslog.
openhistory()
prompt() and envsetup() must complete sucessfully before calling this
subroutine. Otherwise, log files might be created in the current working
directory, or logfile creation might fail. Blanks will also appear in
the written log file, and perl will complain.
Due to a quirk in how the history command within bash operates, we have
a clever opportunity to log some extra metadata at the beginning of the
history session. When PROMPT_COMMAND executes for the first time, before
the user has actually executed a command, the embedded *history 1*
command will retrieve the previous command and log it via. *logger*. By
creating our own history file, and logging this line to it, we are able
to inject this entry as the first line logged. If we did not do this, a
blank line would be logged as the first entry instead.
runshell()
prompt() and envsetup() must complete sucessfully before calling this
subroutine, and openhistory() is recommended. If this is not done, then
SHELLLOGPREFIX will be blank, and more importantly, so will HISTFILE,
which will cause script to write files to the local directory, or fail.
Not running envsetup() will also prevent the *umask* from being set
correctly, which will have security ramifications.
Run a *bash* shell (without reading rc files) from within the context of
the *script* command. This will log all input and output.If a user's
input or outut contains PII or PHI, then this file could be sensitive.
It is imperitive that envsetup set the *umask* prior to running these
commands or else users other than this effective user might be able to
read the logs.
main()
Run each of the subroutines in the correct sequence when this module is
called as a script.
COPYRIGHT
Copyright (c) 2021. Cameron King. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
AUTHOR
Cameron King <http://cameronking.me>
ubuntu@evergreenastronaut:~$ sudo ./emergency-shell
This shell is for emergency use only.
If you require routine access to run commands with elevated priviliges, please
update config management or open a request for the specific access required.
You are required to enter a ticket number related to your activities. This use
of the emergency shell will be logged if you continue. Ctrl-C to exit.
Ticket number (AAA-123 or INC123): JIRA-1234
Script started, file is /var/log/eshell/history.ubuntu.2021-01-03.395451.JIRA-1234.typescript
JIRA-1234! root:/home/ubuntu# echo run a command
run a command
JIRA-1234! root:/home/ubuntu# echo run another command
run another command
JIRA-1234! root:/home/ubuntu# exit
exit
Script done, file is /var/log/eshell/history.ubuntu.2021-01-03.395451.JIRA-1234.typescript
ubuntu@evergreenastronaut:~$
# History for USER=ubuntu DATETIME=2021-01-03T14:23:01 SHELL=395451 TICKET=JIRA-1234
echo run a command
echo run another command
exit
0.003560 30
3.033723 1
0.061984 1
0.076380 1
0.046076 1
0.223954 1
0.208059 1
0.114652 1
0.051358 1
0.211239 1
0.100356 1
0.117366 1
0.157549 1
0.089730 1
0.176789 1
0.139236 1
0.106526 1
0.086671 1
0.124839 1
0.135567 17
0.001789 30
0.605897 1
0.038596 1
0.755005 1
0.047924 1
0.233848 1
0.165533 1
0.066058 1
0.057611 1
0.185927 1
0.099346 1
0.092502 1
0.061427 1
0.085258 1
0.119080 1
0.057180 1
0.054575 1
0.119789 1
0.102141 1
0.148284 1
0.201591 1
0.069085 1
0.082920 1
0.116040 1
0.152838 1
0.055638 2
0.000129 19
0.000070 2
0.002300 30
0.603975 1
0.217612 1
0.121065 1
0.138238 1
0.126667 8
Script started on 2021-01-03 14:23:01+00:00 [TTY="/dev/pts/0" COLUMNS="80" LINES="25"]
JIRA-1234! root:/home/ubuntu# echo run a command
run a command
JIRA-1234! root:/home/ubuntu# echo run another command
run another command
JIRA-1234! root:/home/ubuntu# exit
exit
Script done on 2021-01-03 14:23:11+00:00 [COMMAND_EXIT_CODE="0"]
ubuntu@evergreenastronaut:~$ ls -l /var/log/eshell
total 12
-rw------- 1 root root 134 Jan 3 14:23 history.ubuntu.2021-01-03.395451.JIRA-1234
-rw------- 1 root root 599 Jan 3 14:23 history.ubuntu.2021-01-03.395451.JIRA-1234.timing
-rw------- 1 root root 339 Jan 3 14:23 history.ubuntu.2021-01-03.395451.JIRA-1234.typescript
-- Logs begin at Sat 2020-10-31 06:10:59 UTC, end at Sun 2021-01-03 14:23:11 UTC. --
Jan 03 14:22:57 evergreenastronaut sudo[395450]: ubuntu : TTY=pts/0 ; PWD=/home/ubuntu ; USER=root ; COMMAND=./emergency-shell
Jan 03 14:22:57 evergreenastronaut sudo[395450]: pam_unix(sudo:session): session opened for user root by ubuntu(uid=0)
Jan 03 14:23:01 evergreenastronaut ubuntu[395455]: SHELL=395451 TICKET=JIRA-1234 Starting emergency shell
Jan 03 14:23:01 evergreenastronaut ubuntu[395460]: SHELL=395451 TICKET=JIRA-1234 HISTORY: 1 # History for USER=ubuntu DATETIME=2021-01-03T14:23:01 SHELL=395451 TICKET=JIRA-1234
Jan 03 14:23:06 evergreenastronaut ubuntu[395462]: SHELL=395451 TICKET=JIRA-1234 HISTORY: 2 echo run a command
Jan 03 14:23:10 evergreenastronaut ubuntu[395464]: SHELL=395451 TICKET=JIRA-1234 HISTORY: 3 echo run another command
Jan 03 14:23:11 evergreenastronaut ubuntu[395465]: SHELL=395451 TICKET=JIRA-1234 Stopping emergency shell
Jan 03 14:23:11 evergreenastronaut sudo[395450]: pam_unix(sudo:session): session closed for user root
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment