From 750ed359807e88e3b3841bfc02a011bbf963cea8 Mon Sep 17 00:00:00 2001 From: Marc Schoechlin Date: Sat, 21 Oct 2017 20:38:15 +0200 Subject: [PATCH] A new attempt --- README.md | 98 +++++------------ helpers/auditshell | 30 ++++-- helpers/auditshell_create_sessionfiles | 57 ---------- helpers/auditshell_script-upstream.patch | 131 ----------------------- helpers/auditshell_script.patch | 126 ---------------------- helpers/usr.local.bin.auditshell | 41 +++++++ 6 files changed, 90 insertions(+), 393 deletions(-) delete mode 100755 helpers/auditshell_create_sessionfiles delete mode 100644 helpers/auditshell_script-upstream.patch delete mode 100644 helpers/auditshell_script.patch create mode 100644 helpers/usr.local.bin.auditshell diff --git a/README.md b/README.md index 249a3c0..73efcc6 100644 --- a/README.md +++ b/README.md @@ -118,91 +118,43 @@ SEE ALSO # Auditshell -Auditshell submits the typescript and the timings of a patched util-linux/script binary to syslog which prevents modification by regular terminal users. -The logged information can also be forwarded to secured logging servers using standard syslog logfile distribution. - +Auditshell replaces bash by audited replacement which writes scriptreplay data to /var/log/auditshell. +The apparmor profile prevents audited users to read, change or delete the recorded sessions and to change thei shell. ## Installation of "auditshell" The following instructions describe the procedure how to install a audit shell in combination with the scriptreplay utility. - * Install tools - - ```bash - cp scriptreplay helpers/auditshell helpers/auditshell_create_sessionfiles /usr/local/bin/ - chown root:root /usr/local/bin/{scriptreplay,auditshell,auditshell_create_sessionfiles} - chmod 755 /usr/local/bin/{scriptreplay,auditshell,auditshell_create_sessionfiles} - ``` - * Install Build dependencies - - ```bash - apt-get install libtoolize libtool autopoint pkg-config make gcc - zypper install libtool gettext-tools pkg-config make gcc autoconf automake + * Install the tools and the apparmor configuration + ``` + cp scriptreplay helpers/auditshell /usr/local/bin/ + cp helpers/usr.local.bin.auditshell /etc/apparmor.d/usr.local.bin.auditshell + chmod 755 /usr/local/bin/auditshell /usr/local/bin/scriptreplay + chown root:root /usr/local/bin/auditshell /usr/local/bin/scriptreplay + ``` + * Create a directory for auditfiles + ``` + mkdir /var/log/auditshell/ + chmod 1777 /var/log/auditshell/ ``` - * Patch and install custom "script" implementation - - ```bash - cd helpers/ - wget https://www.kernel.org/pub/linux/utils/util-linux/v2.23/util-linux-2.23.tar.gz - tar zxvf util-linux-2.23.tar.gz - cd util-linux-2.23/ - patch -p1 < ../auditshell_script.patch - ./configure --without-ncurses --disable-nls - make - cp script /usr/local/bin/ - chown root:root /usr/local/bin/script - chmod 755 /usr/local/bin/script + * Enable apparmor profle + ``` + aa-enforce /usr/local/bin/auditshell + ``` + * Change shell of user which should be audited ``` - * Syslog configuration: - * Disable string escaping on system which are using rsyslogd (i.e. Ubuntu systems with rsyslogd) - * Redirect the auditshell logs to another logfile using syslog configuration - * Change shell of user - - ```bash chsh -s /usr/local/bin/auditshell ``` - * Prevent regular users to change their shell - * Remove all shells from /etc/shells - * Remove all perrmissions from the the /usr/bin/chsh binary - * Use the FAKE_SHELL variable in /etc/login.defs ## Watch auditshell sessions - * Start session, and execute commands - * Extract session files - - ```bash - /usr/local/bin/auditshell_create_sessionfiles /var/log/messages /tmp/foo + * Identify a session ``` - * Replay session - - ```bash - scriptreplay -t /tmp/foo/2013-09-11_18-47-45.user1.11931.timing \ - /tmp/foo/2013-09-11_18-47-45.user1.11931.typescript + cd /var/log/auditshell + ls -1d * + ``` + * View a session + ``` + scriptreplay -t 2017-10-21_10-19-17.marc.2159/timing* -s 2017-10-21_10-19-17.marc.2159/typescript* ``` - -## Logging configuration - -### Syslog-NG Configuration - - - * Edit /etc/syslog-ng/syslog-ng.conf - ``` - # define audit shell filter - filter f_auditshell { match('^auditshell'); }; - # enhance existing messages filter by f_auditshell to ignore messages matched by f_auditshell - filter f_messages { not facility(news, mail) and not filter(f_iptables) and not filter(f_auditshell); }; - - # define a log-sink for auditshell - destination auditshell { - file ("/var/log/auditshell/$YEAR-$MONTH/$FACILITY-$YEAR-$MONTH-$DAY" - owner(root) group(root) perm(0600) dir_perm(0700) create_dirs(yes) - ); - }; - log { source(src); filter(f_auditshell); destination(auditshell); }; - ``` - * Restart Syslogd - ``` - /etc/init.d/syslog restart - ``` diff --git a/helpers/auditshell b/helpers/auditshell index c7023e4..c47247f 100755 --- a/helpers/auditshell +++ b/helpers/auditshell @@ -2,6 +2,11 @@ IDENT="`date --date="today" "+%Y-%m-%d_%H-%M-%S"`.`whoami`.$$" +LOGDIR="/var/log/auditshell/${IDENT}" +TYPESCRIPT="${LOGDIR}/typescript.${IDENT}" +TIMING="${LOGDIR}/timing.${IDENT}" + + # This is a file transfer, no audit shell neccessary if (echo "$@"|egrep -q "^-c.*scp.*$");then logger -t auditshell.filetransfer.${IDENT} <<< "/bin/sh $@" @@ -12,8 +17,18 @@ elif (echo "$@"|egrep -q "^-c.*$");then exec /bin/bash "$@" fi -TYPESCRIPT="auditshell.typescript.${IDENT}" -TIMING="auditshell.timing.${IDENT}" + +if [ "$AUDITSHELL" ];then + echo "INFO: already in a auditshell session" + exit 1 +fi + +mkdir $LOGDIR +RET="$?" +if [ "$RET" != "0" ];then + echo "ERROR: Creation of dir '$LOGDIR' failed, exitcode $RET" + exit 1 +fi export SHELL=/bin/bash @@ -31,7 +46,10 @@ AUDIT KEY: $IDENT EOF -/usr/local/bin/script -d -e -f -q -t 5 \ - 5> >(base64|logger -t $TYPESCRIPT) \ - 2> >(base64|logger -t $TIMING) -echo "Finish" +export AUDITSHELL="$IDENT" +umask 0077 +logger -t auditshell.session.${IDENT} <<< "Starting auditshell session for user $USER" +script -f -e -q --timing=$TIMING $TYPESCRIPT -c "/bin/bash -l" +logger -t auditshell.session.${IDENT} <<< "Finished auditshell session for user $USER" + +echo "AUDITSHELL FINISHED" diff --git a/helpers/auditshell_create_sessionfiles b/helpers/auditshell_create_sessionfiles deleted file mode 100755 index bf23113..0000000 --- a/helpers/auditshell_create_sessionfiles +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings; -use FileHandle; - -my $file = shift(); -my $dir = shift(); - -if ( (!defined $file) || (!defined $file) ){ - print "auditshell_create_sessionfiles \n"; - exit(1); -} - -chdir($dir); - -unless(chdir($dir)) -{ - die "Error: Can't change directory!: $!"; -} - -open( INFILE, "<$file" ) || die "input-file '$file' could not be opened"; - -my $fdcache = {}; - -while (my $zeile = ) { - if ($zeile =~m /auditshell\.(typescript|timing)\.(.*?): (.*)$/){ - chomp($zeile); - my $type = $1; - my $ident = $2; - my $line = $3; - - if ( !exists $fdcache->{$ident}){ - $fdcache->{$ident} = {}; - print "Create $ident.typescript.base64\n"; - $fdcache->{$ident}->{typescript} = FileHandle->new("> $ident.typescript.base64"); - print "Create $ident.timing.base64\n"; - $fdcache->{$ident}->{timing} = FileHandle->new("> $ident.timing.base64"); - } - - my $fd = $fdcache->{$ident}->{$type}; - print $fd $line."\n"; - } -} - -close(INFILE); - -foreach my $ident(keys %{$fdcache}){ - close $fdcache->{$ident}->{typescript}; - close $fdcache->{$ident}->{timing}; - system("base64 -d $ident.typescript.base64 |gzip -c > $ident.typescript.gz"); - system("base64 -d $ident.timing.base64 |gzip -c > $ident.timing.gz"); - unlink("$ident.timing.base64"); - unlink("$ident.typescript.base64"); - print "removed $ident.typescript.base64, created $ident.typescript.gz\n"; - print "removed $ident.timing.base64, created $ident.timing.gz\n"; -} diff --git a/helpers/auditshell_script-upstream.patch b/helpers/auditshell_script-upstream.patch deleted file mode 100644 index e3f2ff0..0000000 --- a/helpers/auditshell_script-upstream.patch +++ /dev/null @@ -1,131 +0,0 @@ -diff --git a/term-utils/script.1 b/term-utils/script.1 -index 5c366b129..33789a927 100644 ---- a/term-utils/script.1 -+++ b/term-utils/script.1 -@@ -86,14 +86,21 @@ or symbolic link. The command will follow a symbolic link. - Be quiet (do not write start and done messages to either standard output - or the typescript file). - .TP --\fB\-t\fR, \fB\-\-timing\fR[=\fIfile\fR] --Output timing data to standard error, or to -+\fB\-t\fR, \fB\-\-timing\fR[=\fIfile|descriptor\fR] -+Output timing data to standard error, or to a - .I file -+or -+.I descriptor - when given. This data contains two fields, separated by a space. The first - field indicates how much time elapsed since the previous output. The second - field indicates how many characters were output this time. This information - can be used to replay typescripts with realistic typing and output delays. - .TP -+\fB\-d\fR, \fB\-\-descriptor\fR -+Use this option in combination with -+.I --timing -+for using the given resource as a file descriptor number as destination for timing data -+.TP - \fB\-V\fR, \fB\-\-version\fR - Display version information and exit. - .TP -diff --git a/term-utils/script.c b/term-utils/script.c -index d1ef07203..4b56b671d 100644 ---- a/term-utils/script.c -+++ b/term-utils/script.c -@@ -116,7 +116,8 @@ struct script_control { - #endif - unsigned int - append:1, /* append output */ -- rc_wanted:1, /* return child exit value */ -+ tsdesc:1, /* output typescript to file descriptor */ -+ rc_wanted:1, /* return child exit value */ - flush:1, /* flush after each write */ - quiet:1, /* suppress most output */ - timing:1, /* include timing file */ -@@ -169,6 +170,7 @@ static void __attribute__((__noreturn__)) usage(FILE *out) - " --force use output file even when it is a link\n" - " -q, --quiet be quiet\n" - " -t, --timing[=] output timing data to stderr (or to FILE)\n" -+ " -d, --descriptor[=] output timing data to a defined descriptor number\n" - " -V, --version output version information and exit\n" - " -h, --help display this help and exit\n\n"), out); - -@@ -195,8 +197,14 @@ static void __attribute__((__noreturn__)) done(struct script_control *ctl) - - if (ctl->isterm) - tcsetattr(STDIN_FILENO, TCSADRAIN, &ctl->attrs); -- if (!ctl->quiet && ctl->typescriptfp) -- printf(_("Script done, file is %s\n"), ctl->fname); -+ if (!ctl->quiet && ctl->typescriptfp) { -+ if (ctl->tsdesc == 1) { -+ printf(_("Script done, file descriptor number is %s\n"), ctl->fname); -+ }else{ -+ printf(_("Script done, file is %s\n"), ctl->fname); -+ } -+ } -+ - #ifdef HAVE_LIBUTEMPTER - if (ctl->master >= 0) - utempter_remove_record(ctl->master); -@@ -407,6 +415,7 @@ static void do_io(struct script_control *ctl) - int ret, ignore_stdin = 0, eof = 0; - time_t tvec = script_time((time_t *)NULL); - char buf[128]; -+ int fdnum_typescript = 0; - enum { - POLLFD_SIGNAL = 0, - POLLFD_MASTER, -@@ -420,7 +429,18 @@ static void do_io(struct script_control *ctl) - }; - - -- if ((ctl->typescriptfp = -+ -+ if (ctl->tsdesc == 1){ -+ fdnum_typescript = atoi(ctl->fname); -+ if (fdnum_typescript == 0){ -+ warn(_("file descriptor is not a number")); -+ fail(ctl); -+ } -+ if ((ctl->typescriptfp = fdopen (fdnum_typescript, "w")) == NULL) { -+ warn(_("cannot open fd %s"), ctl->fname); -+ fail(ctl); -+ } -+ }else if ((ctl->typescriptfp = - fopen(ctl->fname, ctl->append ? "a" UL_CLOEXECSTR : "w" UL_CLOEXECSTR)) == NULL) { - warn(_("cannot open %s"), ctl->fname); - fail(ctl); -@@ -678,6 +698,7 @@ int main(int argc, char **argv) - {"force", no_argument, NULL, FORCE_OPTION,}, - {"quiet", no_argument, NULL, 'q'}, - {"timing", optional_argument, NULL, 't'}, -+ {"descriptor", no_argument, NULL, 'd' }, - {"version", no_argument, NULL, 'V'}, - {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0} -@@ -698,8 +719,10 @@ int main(int argc, char **argv) - - script_init_debug(); - -- while ((ch = getopt_long(argc, argv, "ac:efqt::Vh", longopts, NULL)) != -1) -+ while ((ch = getopt_long(argc, argv, "dac:efqt::Vh", longopts, NULL)) != -1) - switch (ch) { -+ case 'd': -+ ctl.tsdesc = 1; - case 'a': - ctl.append = 1; - break; -@@ -748,8 +771,13 @@ int main(int argc, char **argv) - ctl.shell = _PATH_BSHELL; - - getmaster(&ctl); -- if (!ctl.quiet) -- printf(_("Script started, file is %s\n"), ctl.fname); -+ if (!ctl.quiet) { -+ if (ctl.tsdesc == 1) { -+ printf(_("Script started, file descriptor number is %s\n"), ctl.fname); -+ }else{ -+ printf(_("Script started, file is %s\n"), ctl.fname); -+ } -+ } - fixtty(&ctl); - - #ifdef HAVE_LIBUTEMPTER diff --git a/helpers/auditshell_script.patch b/helpers/auditshell_script.patch deleted file mode 100644 index bbe3533..0000000 --- a/helpers/auditshell_script.patch +++ /dev/null @@ -1,126 +0,0 @@ -diff --git a/term-utils/script.1 b/term-utils/script.1 -index 1e430d8..1808a37 100644 ---- a/term-utils/script.1 -+++ b/term-utils/script.1 -@@ -84,14 +84,21 @@ or symbolic link. The command will follow a symbolic link. - \fB\-q\fR, \fB\-\-quiet\fR - Be quiet. - .TP --\fB\-t\fR, \fB\-\-timing\fR[=\fIfile\fR] --Output timing data to standard error, or to -+\fB\-t\fR, \fB\-\-timing\fR[=\fIfile|descriptor\fR] -+Output timing data to standard error, or to a - .I file -+or -+.I descriptor - when given. This data contains two fields, separated by a space. The first - field indicates how much time elapsed since the previous output. The second - field indicates how many characters were output this time. This information - can be used to replay typescripts with realistic typing and output delays. - .TP -+\fB\-d\fR, \fB\-\-descriptor\fR -+Use this option in combination with -+.I --timing -+for using the given resource as a file descriptor number as destination for timing data -+.TP - \fB\-V\fR, \fB\-\-version\fR - Output version information and exit. - .TP -diff --git a/term-utils/script.c b/term-utils/script.c -index 242b815..778fbbe 100644 ---- a/term-utils/script.c -+++ b/term-utils/script.c -@@ -101,12 +101,14 @@ int l; - char line[] = "/dev/ptyXX"; - #endif - int aflg = 0; -+int dflg = 0; - char *cflg = NULL; - int eflg = 0; - int fflg = 0; - int qflg = 0; - int tflg = 0; - int forceflg = 0; -+int fdnum_typescript = 0; - - int die; - int resized; -@@ -171,6 +173,7 @@ main(int argc, char **argv) { - { "force", no_argument, NULL, FORCE_OPTION, }, - { "quiet", no_argument, NULL, 'q' }, - { "timing", optional_argument, NULL, 't' }, -+ { "descriptor", no_argument, NULL, 'd' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - { NULL, 0, NULL, 0 } -@@ -182,8 +185,10 @@ main(int argc, char **argv) { - textdomain(PACKAGE); - atexit(close_stdout); - -- while ((ch = getopt_long(argc, argv, "ac:efqt::Vh", longopts, NULL)) != -1) -+ while ((ch = getopt_long(argc, argv, "dac:efqt::Vh", longopts, NULL)) != -1) - switch(ch) { -+ case 'd': -+ dflg = 1; - case 'a': - aflg = 1; - break; -@@ -229,7 +234,22 @@ main(int argc, char **argv) { - fname = DEFAULT_OUTPUT; - die_if_link(fname); - } -- if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL) { -+ -+ -+ if (dflg == 1){ -+ -+ fdnum_typescript = atoi(fname); -+ -+ if (fdnum_typescript == 0){ -+ warn(_("file descriptor is not a number")); -+ fail(); -+ } -+ -+ if ((fscript = fdopen (fdnum_typescript, "w")) == NULL) { -+ warn(_("cannot open fd %s"), fname); -+ fail(); -+ } -+ }else if((fscript = fopen(fname, aflg ? "a" : "w")) == NULL) { - warn(_("cannot open %s"), fname); - fail(); - } -@@ -239,8 +259,15 @@ main(int argc, char **argv) { - shell = _PATH_BSHELL; - - getmaster(); -- if (!qflg) -+ -+ if (!qflg) { -+ if (dflg == 1){ -+ printf(_("Script started, file descriptor number is %s\n"), fname); -+ }else{ - printf(_("Script started, file is %s\n"), fname); -+ } -+ } -+ - fixtty(); - - #ifdef HAVE_LIBUTEMPTER -@@ -495,8 +520,16 @@ done(void) { - master = -1; - } else { - tcsetattr(STDIN_FILENO, TCSADRAIN, &tt); -- if (!qflg) -+ -+ if (!qflg) { -+ if (dflg == 1){ -+ printf(_("Script done, file descriptor number is %s\n"), fname); -+ }else{ - printf(_("Script done, file is %s\n"), fname); -+ } -+ } -+ -+ - #ifdef HAVE_LIBUTEMPTER - if (master >= 0) - utempter_remove_record(master); diff --git a/helpers/usr.local.bin.auditshell b/helpers/usr.local.bin.auditshell new file mode 100644 index 0000000..efef8a1 --- /dev/null +++ b/helpers/usr.local.bin.auditshell @@ -0,0 +1,41 @@ + +# Apparmor profile for the auditshell + +#include + +/usr/local/bin/auditshell { + + #include + + /** lrwix, + + /bin/bash cx, + + profile /bin/bash { + #include + #include + + network inet tcp, + /** lrwix, + + # TCP/UDP network access + network inet stream, + network inet6 stream, + network inet dgram, + network inet6 dgram, + network netlink raw, + + deny /usr/bin/chsh lrwx, + deny /var/log/auditshell/ lrwx, + deny /var/log/auditshell/** lrwx, + + + #include +# dbus send +# bus=system +# path="/org/freedesktop/resolve1" +# interface="org.freedesktop.resolve1.Manager" +# member="Resolve{Address,Hostname,Record,Service}" +# peer=(name="org.freedesktop.resolve1"), + } +}