Recipe 9.6 Checking for Suspicious Account Use, Multiple Systems
9.6.1 Problem
You want to scan multiple computers for
unusual or dangerous usage of accounts.
9.6.2 Solution
Merge the lastlog databases from several systems, using
Perl:
use DB_File;
use Sys::Lastlog;
use Sys::Hostname;
my %omnilastlog;
tie(%omnilastlog, "DB_File", "/share/omnilastlog");
my $ll = Sys::Lastlog->new( );
while (my ($user, $uid) = (getpwent( ))[0, 2]) {
if (my $llent = $ll->getlluid($uid)) {
$omnilastlog{$user} = pack("Na*", $llent->ll_time( ),
join("\0", $llent->ll_line( ),
$llent->ll_host( ),
hostname))
if $llent->ll_time( ) >
(exists($omnilastlog{$user}) ?
unpack("N", $omnilastlog{$user}) : -1);
}
}
untie(%omnilastlog);
exit(0);
To read the merged lastlog database,
omnilastlog, use another Perl script:
use DB_File;
my %omnilastlog;
tie(%omnilastlog, "DB_File", "/share/omnilastlog");
while (my ($user, $record) = each(%omnilastlog)) {
my ($time, $rest) = unpack("Na*", $record);
my ($line, $host_from, $host_to) = split("\0", $rest, -1);
printf("%-8.8s %-16.16s -> %-16.16s %-8.8s %s\n",
$user, $host_from, $host_to, $line,
$time ? scalar(localtime($time)) : "**Never logged in**");
}
untie(%omnilastlog);
exit(0);
9.6.3 Discussion
Perusing the output from the
lastlog , last, and
lastb commands [Recipe 9.5] might
be sufficient to monitor activity on a single system with a small
number of users, but the technique doesn't scale
well in the following cases:
- If accounts are shared among many systems, you probably want to know
a user's most recent login on
any of your systems. - Some system accounts intended for special
purposes, such as bin or daemon, should never be
used for routine logins. - Disabled accounts should be monitored to make sure they have no login
activity.
Legitimate usage patterns vary, and your goal should be to notice
deviations from the norm. We need more flexibility than the preceding
tools provide.We can solve this dilemma through automation. The Perl
modules
Sys::Lastlog and
Sys::Utmp, which are available from CPAN, can
parse and display a system's last-login data.
Despite its name, Sys::Utmp can process the
wtmp and
btmp files; they have the same format as
/var/log/utmp,
the database containing a snapshot of currently logged-in users.Our recipe merges lastlog databases from several
systems into a single database, which we call
omnilastlog, using Perl. The script steps
through each entry in the password database on each system, looks up
the corresponding entry in the lastlog database
using the Sys::Lastlog module, and updates the
entry in the merged omnilastlog database if the
last login time is more recent than any other we have previously
seen.The merged omnilastlog database is tied to a
hash for easy access. We use the Berkeley DB format because it is
byte-order-independent and therefore portable: this would be
important if your Linux systems run on different architectures. If
all of your Linux systems are of the same type (e.g., Intel x86
systems), then any other Perl database module could be used in place
of DB_File.Our hash is indexed by usernames rather than numeric user IDs, in
case the user IDs are not standardized among the systems (a bad
practice that, alas, does happen). The record for each user contains
the time, terminal (ll_line), and remote and
local hostnames. The time is packed as an integer in network byte
order (another nod to portability: for homogeneous systems, using the
native "L" packing template instead
of "N" would work as well). The
last three values are glued together with null characters, which is
safe because the strings never contain nulls.Run the merge script on all of your systems, as often as desired, to
update the merged omnilastlog database. Our
recipe assumes a shared filesystem location,
/share/omnilastlog; if this is not convenient,
copy the file to each system, update it, and then copy it back to a
central repository. The merged database is compact, often smaller
than the individual lastlog databases.An even simpler Perl script reads and analyzes the merged
omnilastlog database. Our recipe steps through
and unpacks each record in the database, and then prints all of the
information, like the lastlog command.This script can serve as a template for checking account usage
patterns, according to your own conventions. For example, you might
notice dormant accounts by insisting that users with valid shells (as
listed in the file
/etc/shells, with the exception of
/sbin/nologin) must have logged in somewhere
during the last month. Conversely, you might require that
system accounts (recognized by their
low numeric user IDs) with invalid shells must never login, anywhere.
Finally, you could maintain a database of the dates when accounts are
disabled (e.g., as part of a standard procedure when people leave
your organization), and demand that no logins occur for such accounts
after the termination date for each.Run a script frequently to verify your assumptions about legitimate
account usage patterns. This way, you will be reminded promptly after
Joe's retirement party that his account should be
disabled, hopefully before crackers start guessing his password.
9.6.4 See Also
The Sys::Lastlog and
Sys::Utmp Perl modules are found at http://www.cpan.org.Perl for System Administration (section 9.2)
from O'Reilly shows how to unpack the
utmp records used for wtmp
and btmp files.
O'Reilly's Perl
Cookbook also has sample programs for reading records
from lastlog and wtmp
files: see the laston and
tailwtmp scripts in Chapter 8 of that
book.