Many interactive commands are security sensitive.
An obvious example is passwd
which is used to change a user's
password.
Such commands require users to authenticate themselves even though
they have successfully logged in to the system.
Also, many server daemons carry out tasks on behalf of remote
users and most of these require the daemon to authenticate
the remote user.
How was this done?
Early versions of Unix had all such programs (applications and daemons)
directly read and parse the /etc/passwd
file, so they
could authenticate users.
This became a problem when the format of /etc/passwd
changed to include aging information in the second field.
Every program that needed to authenticate users
had to be updated and re-compiled.
Sometime after that, single (remote) user databases became common
in large organizations, using technology such as
NIS.
To use that, one couldn't simply rewrite all programs again, as sometimes
/etc/passwd
authentication was still used.
The solution?
A second set of all such commands (all NIS
commands start with the letters “yp”, as in
yppasswd
).
User's would have to remember to update the credentials using the
correct version.
Soon shadow passwords became common, then Kerberos, then different password encryption (actually, hashing) algorithms (such as MD5), then LDAP, etc. And every such change either requires custom versions of applications and daemons, or a re-write of all existing versions. And for every program that needed authentication!
At some point, someone came up with the idea that programs that need
authentication should use a standard library for that, which in turn
could be configured to use different databases and/or new algorithms
just by adding new DLLs
(called shared object (.so
files in Linux or Unix).
This system became known as
PAM
(Pluggable Authentication Modules).
Using PAM, some new authentication DLL can be invoked
by the PAM library just by editing a text configuration file
(which says which authentication modules to use).
“PAMified” programs do not need to be changed in any
way to use different authentication modules.
Only a text configuration file (one for each program) needs to be
updated to change how some program authenticates users.
This was genius: all the different versions could be replaced with
just a single version, and that version was future-proof!
From the FreeBSD PAM documentation: PAM was defined and developed in 1995 by Vipin Samar and Charlie Lai of Sun Microsystems, and has not changed much since. In 1997, the Open Group published the X/Open Single Sign-on (XSSO) preliminary specification, which standardized the PAM API and added extensions for single (or rather integrated) sign-on. PAM is widely used by BSD (OpenPAM), by Linux (PAM-Linux) and by Solaris (Solaris PAM).
To see if some program is “PAMified” or not, check if it has been compiled with the PAM library:
$ ldd cmd | grep libpam.so
Modern (and most legacy) applications and daemons that need authentication have been re-written (hopefully for the last time!) to use PAM. There are many PAM modules (yes I know that's redundant but saying “PAMs” or “PA modules” is awkward) available for every system, each supporting a different authentication method. New ones can be easily found on the Internet, or created by programmers. A nice benefit of this design is that different programs can use different PAM modules for authentication, all on the same system. (Each program's text configuration file may specify a different set of PAM modules to use.)
In addition to authentication, PAM modules can be used for
session setup and tear-down, logging, and various other uses.
For example, there is a PAM module to display the
MOTD file.
Another module changes the owner, group, and permissions of various
files in /dev
, to allow users logged in at the
console permission to use sound or access removable media.
The PAM configuration file for some program can list more than one
PAM module to try, and each is tried in the order listed.
So if the user fails to authenticate using the PAM module for (say)
local files (/etc/passwd
and /etc/shadow
),
then PAM will try the next module listed, which can attempt
authentication using a different database such as NIS,
LDAP, or even Windows AD.
This design allows administrators great flexibility to implement
nearly any security policy they can think of.
PAM modules don't only allow or deny access by authenticating users. They can check other things to determine access. For instance, some PAM modules deny access if the resource is busy with too many users already. Others can allow or deny users based on criteria such as the time of day, or indeed any arbitrary criteria.
Because of PAM's utility and flexibility, system administrators should become familiar with the system and the set of PAM modules available to them. Then they can implement a wide range of security policies for the various applications and services on their hosts. Those unfamiliar with PAM will have a difficult time changing security policies.
PAM is not the only such framework available, but it is the most widely used. Others on Linux include GSAPI and SSSD. Note that most of these frameworks include PAM modules, so even if some application uses one of them, they can still be configured through PAM. Also, PAM usually includes modules to use these other frameworks, leading to the possibility of the application using PAM to use (say) GSAPI, which may in turn be configured to use PAM. Some care is needed to avoid this sort of loop. (See overlapping below for more details.)
Keep in mind that PAM, like all such frameworks, can only inform the programs that use it whether or not a user should be allowed access. PAM cannot enforce that; a badly written program, or one infected with malware, might still perform its function even if a user is not authenticated or authorized. (This has always been true.) Other security subsystems, such as SE Linux, can enforce policy.
In the PAM configuration file for some program (application or daemon), the administrator lists all the PAM modules that should be used to implement the access policy. This list is called a stack. When a program needs to authenticate a user, each PAM module is invoked in the order listed in the configuration file for that program. Each module can return success or failure. The results of all the modules are combined into a single result, which is returned to the program. This process is controlled by the “control-flag” listed for each module. Generally, if any one module “fails”, then PAM informs the application that access should be denied.
The exact behavior of PAM in the event that one module fails can be changed in the configuration file, allowing for complex policies to be implemented. For example you can configure some program to try to authenticate with LDAP first, and if that fails to then try local files. Then allow access if either module succeeds.
Take for example an application such as hwbrowser
.
This is written to use PAM to see who is allowed to run that
command.
The exact set of modules is controlled by a text configuration
(or policy) file in the /etc/pam.d
directory;
the configuration file is called hwbrowser
.
(It is common but not required to use the application's name as
the configuration file's name.)
By editing this configuration file, you can enforce any access
policy desired.
Here's the default hwbrowser
PAM configuration file
from an old Fedora Core Linux system:
/etc/pam.d# cat hwbrowser #%PAM-1.0 auth sufficient pam_rootok.so auth sufficient pam_timestamp.so auth required pam_stack.so \ service=system-auth account required pam_permit.so session required pam_permit.so session optional pam_xauth.so
As you can see, the file has a list of DLLs to use, and some of them are passed extra arguments. (The syntax of PAM configuration files is discussed below.)
If an application can't find a configuration file for itself, it
defaults to using a PAM configuration
file named other
.
It is important for security reasons to have an
“other
” configuration file!
Typically this file should deny all access.
This is a deny by default policy.
The administrator will need to create (if missing) a PAM
configuration file for any “PAMified” program to be used
on the system.
By editing (or creating if missing) these files, a system administer implements access policy for the applications and servers on the host. Don't forget that other security systems may also require configuration to implement a host's access policies, including firewalls, TCP Wrappers, file permissions and ACLs, group memberships, SE Linux policy files, service configuration files, SASL, and so on.
SASL is a framework for authentication mechanism negotiation. A server can advertise one or more authentication mechanisms to clients, and the two can agree on which one to use. (Most mail servers use this.) It is not uncommon to have SASL configured to allow the use of PAM. See Cyrus SASL for System Administrators and RFC-4422 for more information on SASL.
PAM modules are usually stored in the /lib/security
for 32-bit
systems, or the /lib64/security
directory for more
modern systems.
Do an “ls
” of this directory to see what PAM modules
have been installed on your system.
Additional modules may be put anywhere, but if so the configuration
file needs to list the complete pathname of the module, rather than just
the file name.
Each PAM module is really just a DLL that defines one
or more of the six standard functions that PAM can use.
These functions are grouped into four types, or
contexts (I believe the official term is management group):
account
, auth
, session
,
and password
.
Any given PAM module will implement the functions needed for
one or more of these types.
(So, a module that only implements session
functions
will do nothing if used in any other context.)
To see which functions a given PAM module implements, your first
thought should be to consult the documentation.
When that is lacking, you can run the command:
$ nm --dynamic --defined-only pam_module.so
to see which PAM functions are defined in that module.
An “auth” module will define the function
pam_sm_authenticate()
, an “account”
module will define pam_sm_acct_mgmt()
, a “session”
module will define both pam_sm_open_session()
and
pam_sm_close_session()
, and
a “password” module will define pam_sm_chauthtok()
.
Any PAM module must define at least one set of these functions, and
may define several.
Before we dive into using PAM, let's take a look at how software interacts with the PAM library. Hopefully this will provide some good insight for administrators.
A program written to use the PAM library calls a series of functions that in turn invoke the functioned defined in various PAM modules. A typical application makes the following function calls to the PAM library:
... pam_start("service name", "username", ...); Initializes the PAM library ... if ( ! pam_authenticate(...) ) Authenticates using "auth" modules error_exit(); ... if ( ! pam-acct_mgmt(...) ) Checks for a valid, unexpired account and verifies access restrictions with "account" modules error_exit(); ... pam_setcred(...) Sets extra credentials, e.g. a Kerberos ticket ... pam_open_session(...); Sets up the session with "session" modules do_stuff(); pam_close_session(...); Tear-down session using the "session" modules pam_end(...);
The only part of this a system administrator needs to understand is that a program that uses PAM will make function calls to the PAM library, and these in turn invoke functions in the related PAM modules.
The pam_start
function is passed a service name
as the first argument.
This name is nearly always the same as the program's name.
The PAM configuration files in /etc/pam.d
are named
by this service name.
So if the application or server daemon has a different filename, an
administrator won't know which PAM configuration file it uses!
If the service name isn't obvious and you can't find it in the
program's documentation (or don't trust that) you can determine
this name yourself, by checking the string passed as the first
argument to the pam_start
function.
If you don't have access to the source code you can use tracing
and debugging tools to determine the service name.
Here's an example on Linux to determine the service name of the
vlock
application:
$ ltrace /usr/bin/vlock 2>&1 1>/dev/null </dev/null |grep 'pam_start' pam_start(0x8049ac0, 0x804a4c0, 0x804a210, 0xbf82adcc, 0x4554005a) = 0 ^^^^^^^--> this is the RAM address of the service name $ gdb /usr/bin/vlock (gdb) printf "%s\n",0x8049ac0 vlock (gdb) quit
This method won't work if the binary has been stripped (or is
not readable by the current user).
A better method is to use a tracing tool, such as strace
on Linux,
to see which files the command opens:
# strace -uwpollock -e open -o strace.out chfn # grep /etc/pam.d strace.out # not "other" or "system-auth". # rm strace.out
So a PAM program starts.
The correct configuration file is found and read.
The program then calls pam_authenticate()
which in turn calls a specific function
(pam_sm_authenticate()
) in each of the
“auth” modules listed in the configuration file,
in order.
The results are combined according to the control-flags
for those modules.
Finally pam_authenticate()
returns a result
back to the program.
The other PAM functions used by the program work the same way.
If some “auth” module wants to ask the user to re-enter a password, the module invokes a function in the application to prompt the user and to get the input. This is called a “call-back” function.
That's how PAM works. All that's left is to understand the configuration files so you can edit them. Let's dig in!
The PAM configuration file syntax is documented in the pam.conf
or the pam.d
man page on your system.
Older versions of PAM used one large pam.conf
file,
with each line starting with the name of the service the line
applies to.
In modern PAM, each service has its own file in
/etc/pam.d
.
The syntax is the same except that first field is omitted, even
though the man page may still mention it.
If both the /etc/pam.conf
file and the /etc/pam.d/
directory exist, Linux PAM will ignore the file.
Here's another look at a simple configuration file (copied from above):
/etc/pam.d# cat hwbrowser #%PAM-1.0 auth sufficient pam_rootok.so auth sufficient pam_timestamp.so auth required pam_stack.so \ service=system-auth account required pam_permit.so session required pam_permit.so session optional pam_xauth.so
Each line contains a module context (or type), the
control flag, the module, and
module options (if any).
Blank lines and comment lines (starting with “#
”)
are also allowed.
(Some implementations of PAM allow for long lines to be continued, using
the convention of ending a line with a backslash.
Don't rely on this unless you are certain your implementation
supports it!)
Finally, if some module option contains spaces, enclose the whole option
in square braces (Works on Red Hat based distros, not tested on others).
The context says when the module is used:
for authentication (auth
), for authorization (account
),
for password updates (password
, not used for hwbrowser
),
or for session setup/cleanup (session
).
The control flag tells PAM how to react to the module's
result (e.g., pass or fail).
For determining permission to run a command, only the lines that
start with “auth
” and “account
”
matter.
The others (“session
” and
“password
”) can be ignored.
(They have other uses, such as initializing the environment, logging command's
use, or approving a new password.)
The auth
modules determine who the user is and if that user has
a valid account on this machine (authentication).
The poorly-named account
modules determine if the user is allowed
access (authorization).
Some possible “account” policies you can test for include: the
account and password are not expired, must be a system account, only a certain
number of users may run this command (say FTP) at once,
access only allowed during certain hours, access from the console only
(i.e. not a remote user), and others.
The relevant modules are run in order listed in the file. Most software checks authentication (“auth”) modules first. Once the user is authenticated, the authorization (“account”) modules are then run. If all is well, “session” modules may be run next, if any. If the software allows a user to update their credentials (passwords), any “password” modules are then run. When the task is finished, the software will run the “session” modules again, to close the session.
Some PAM modules that logically should be in the session setup are run as “auth” modules. The reason is that some environment settings have security implications, or affect the user interface (when asking for a password for example), and therefore need to run first.
Modules are passed information about the request and user, plus any module options listed on the end of the line. Each module returns a result such as pass or fail back to the PAM library. The modules are run until an overall decision is reached, which is either pass or fail. (In more sophisticated configurations, many different return values can occur, and the configuration file can say what to do for each.) The final result is passed back to the application.
TL;DR
When a PAMified program wants to authenticate a user, it invokes the PAM
library.
PAM attempts to authenticate the user by trying all the auth
modules listed in that program's configuration file.
If that succeeds, PAM next attempts to authorize the user by trying
all the account
modules.
If that succeeds, PAM informs the program of success, else it returns
failure.
Modules are tried in the order listed in the configuration file.
A system administrator can change security policies for individual programs
by changing the modules listed, their order, and their arguments, in
the configuration files.
A description of many standard Linux-PAM modules can be found in the
on-line PAM Administrator's Guide, but often the name alone suggests what
a module does.
(Note this guide is old. Linux-PAM no longer hosts a website,
but you can find the source code to the current guides and man pages at
the Linux-PAM Git repository.)
Not every module is listed in this one guide.
However many PAM modules have man
pages (and
many don't), so if not listed in the guide try
“man pam_module
”.
(I.e. to find info on pam_console.so
module try
“man pam_console
”.)
If this doesn't work you must look on the Internet for information.
For example, a search for “pam_timestamp
” easily
finds a
man page for pam_timestamp.
To help you understand PAM, let's walk through a user's attempt to use
hwbrowser
software.
Examine the first non-comment line from the hwbrowser
example
above:
auth sufficient pam_rootok.so
A description of this “pam_rootok
” module can be
found in the on-line
PAM Administrator's Guide, but the name alone suggests what it
does:
If the user is root
, then that is sufficient to allow
access to this command.
The “sufficient
” control-flag means that if the
module passes, that is if the user is root, no further
(auth) modules need to be tried.
But if the user is not root
, PAM
must try the other auth modules listed (if any) to decide whether
to allow access.
The next “auth
” line is:
auth sufficient pam_timestamp.so
What does this mean?
Looking up this module in the guide we find... nothing!
In this case the man page is locally installed and we find this
information there:
“... In a nutshell, pam_timestamp
caches successful
authentication attempts, and allows you to use a recent successful
attempt as the basis for authentication. ...”
This means that if you have successfully authenticated recently
say by supplying your password,
that will be remembered for several minutes and you can use the
command without authenticating yourself again.
(This module was invented for the use of the sudo
command.
The timestamp is the modification time on a file, by default
in /var/run/sudo/username/*
.
You can also determine for how long after that time your
credentials remain valid; default is 5 minutes.)
Now look at the last auth
line:
auth required pam_stack.so \ service=system-auth
The “required
” control-flag means that if the
module is used, it must pass or the overall result will be
fail, regardless of the status of other modules.
The pam_stack
module runs all the modules in
the file listed after service=
(another PAM config file),
and returns whatever result that file's modules return.
Modern Linux PAM no longer uses pam_stack
.
Instead two new keywords are available for use in the configuration
files, include
and the similar substack
(They differ in their handling of the sub-module's sufficient success
(“done”) and requisite failure (“die”);
see the pam.conf man page for details).
On a modern system, the above line would look like this:
auth include system-auth
To add to the confusion, Debian based distros of Linux use
“@include
” instead of
just “include
”.
The pam_stack.so
line says that the user can use the
hwbrowser
command if they pass all the auth
modules listed in the PAM configuration file
“system-auth
”.
(The newer syntax would just be include
or substack
.)
“system-auth
” is a Red Hat thing.
The folks at Red Hat decided to centralize a lot of security
policies into that one file.
(Other systems use the same idea, but a different file names such as
“common-auth
”.)
If you look at some other configuration files in
/etc/pam.d
on a Red Hat based system,
you will see the same line.
(Note the system-auth
file is over-written each
time the “authconfig
” command is run, so
it is important to keep a backup copy of this file handy
if you edit it manually.)
Centralizing your default access policy in one PAM configuration file is a good idea, so if and when it changes you need only update that one file.
Now look at the account
line:
account required pam_permit.so
A quick check of this module says it merely always returns pass. This is here because if there is no module for some type, PAM may return fail if an application tries to invoke it. So it is common to have these permit or deny modules just to have something for each context. You can ignore such lines as they have no effect on PAM's overall result.
Putting these lines together we get this policy: A user can run the
hwbrowser
command: if the user ID is zero
(root
), or if the user recently authenticated, or
(if neither of those) if the user passes the common policy
in the system-auth
file.
(The default system-auth
file is discussed below.)
If none of those pass, access is denied and the attempt is logged.
Suppose the policy on your site changes so root and only root should
be allowed to use the command hwbrowser
.
To implement this new policy, an administrator need only comment
out the second and third “auth” lines in the configuration file.
As mentioned above, the “sufficient
”
control-flag means that if the module passes, that is
enough and the remaining modules in the same context
(“auth
”) are ignored.
On the other hand, if the module fails, that doesn't mean an overall
result of fail.
If a subsequent sufficient
passes, or if all subsequent
required
modules pass, then the overall result is
pass.
(You can think of sufficient
as if no failures so far,
stop checking and return success.)
The “required
” control-flag means it isn't enough to
pass this one module to be allowed to run the command.
All required
modules tried must
say “pass” before access is granted by PAM.
If any required
module fails, the remaining
required
modules are still tried
so that hackers won't know exactly which one failed, but it won't
matter if any of them pass.
(Note that a sufficient
modules that passes will
prevent any following required
modules from running, as
PAM will have stopped at that point.)
The “requisite
” control-flag means the same as
the required
flag, except when the module fails.
As with required
, the overall result is fail.
The difference is that with requisite
,
no further modules are tried.
(It is a fail-fast version of required
.)
The “optional
” control-flag means that the success
or failure of that module has no effect.
Generally, this flag is used for session modules only.
A missing module acts like a “fail”, and the error is logged (via
the system logging daemon, usually syslog
).
If a line in the configuration file starts with a dash, the error isn't
logged (but still causes authentication to fail).
This can be useful for modules that may not be present, for example, a
module for fingerprint authentication may not be present, but if it is,
it should be used.
To prevent authentication failure when a module is missing,
such modules should be used with sufficient
rather than
required
(along with the dash), for example:
-auth sufficient pam_fingerprintd.so
The order of the modules is significant.
If a prior required
module fails, and
a later sufficient
module passes, access
will be denied.
For example:
auth required pam_moduleA auth sufficient pam_moduleB auth required pam_moduleC
What policy does this implement? It says access is allowed: if both modules A and B pass, or if both modules A and C pass. On the other hand, the policy implemented with:
auth sufficient pam_moduleB auth required pam_moduleA auth required pam_moduleC
says to allow access: if module B passes, or if modules A and C both pass.
But, what does pass mean exactly?
Each PAM module examines information provided by the program
requesting authentication (usually the user's ID
and a supplied password), plus other information found
elsewhere (often there are per-module configuration files found
in /etc/security
).
PAM then decides if the current user passes the authentication test
and meets the required account policies.
If so the module returns “pass”; otherwise it returns
“fail”.
(Do not forget to check these per-module configuration files!
Some system administrators prefer to use those files rather than supply
arguments in each of the PAM service configuration files in
/etc/pam.d/
.)
Actually PAM modules can return several different status values
and not simply pass or fail.
The control flag can say to do this if the
status is one thing, and to do that if the status is
something else.
In practice the older scheme of just pass and fail, with the
control-flag keywords of sufficient
and
required
, is flexible enough for most policies,
and is commonly used.
You might occasionally see other control-flags listed in some
configuration files.
The “pam
” man page and
PAM Administrator's Guide
describe everything that can go there in gory detail.
But for most policies, you only need to understand the
required
and sufficient
control-flags.
It is not specified what happens when the PAM config file for some
application is empty, or all of the (say) AUTH modules have been
commented out, or listed as optional
.
By reading the
source for Linux PAM
(especially
pam_dispatch.c) and running some experiments,
I have determined that Linux PAM does the safe action: it fails in
these cases.
But system administrators should not rely on this undocumented
behavior.
Always have some required
(or
sufficient
) module for each type, even if only
pam_deny.so
or pam_permit.so
.
In short, all modules of the correct type (context) are tried in the order
listed, except when a sufficient
module passes, or a
requisite
module fails.
In those two cases, the remaining modules are not run and PAM returns a
result immediately.
Back to our story!
Recall the PAM configuration file for hwbrowser
above
also requires the common policy in system-auth
to pass.
The auth
and account
lines
(remember you can ignore the rest)
from the /etc/pam.d/system-auth
file look like this
(the default file on an old Fedora Linux system):
auth required pam_env.so auth sufficient pam_unix.so nullok try_first_pass auth requisite pam_succeed_if.so uid >= 500 quiet auth required pam_deny.so account required pam_unix.so account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 500 quiet account required pam_permit.so
Each of these modules should be looked up in the PAM reference
documentation.
That would tell you what each is for and what the
module arguments such as “nullok
”
mean.
Basically this (system default) policy authorizes a user to run
some command if the user provides a valid password.
(You've probably have seen this behavior when you try
any command with a similar access policy (hwbrowser
,
chfn
, and passwd
are some examples);
as root
you don't get asked a password to run
hwbrowser
, but as a regular user you must supply
a password to run it.)
Note the requisite
control-flag.
As mentioned previously, this is the same as required
except
that if the module fails no further modules are tried.
In other words requisite
is a “fail fast”
version of required
.
So this auth
section says access is permitted if
the user supplies a valid password.
If they don't, fail quietly if this is a system account, otherwise,
pam_deny
fails with a log message.
The pam_unix.so
and pam_succeed_if.so
modules are interesting because they are used in two different
contexts, account
and auth
!
The explanation is that modules can have different set of functions that
get invoked depending from which context they are called.
Take the pam_unix.so
module for example:
In the auth
context this
module checks the user's name and password,
but in the account
context this same module checks
the account's aging and expiration information instead.
Many modules have multiple purposes, depending on the context.
When reading the documentation for some module, be sure to refer to the part that describes the purpose of the module, for the context in which that the module is used.
On Linux, pam_unix.so
does not currently check
if a user's account is locked!
It logically should do so in the account
context, but apparently
the assumption is that a locked account won't pass the auth
context, since locking accounts on Unix and Linux is done by altering the
password hash.
However, if a user uses biometrics or SSH keys to authenticate,
the mangled password hash is never examined!
And what if you don't use the shadow suite, but something like LDAP instead? Then you just use a different module. This is the power of PAM: an easy way to change which authentication methods are used without re-writing all your applications, or changing the configuration of each application separately.
The pam_unix.so
module (and similar modules on other
systems such as the Solaris 8 pam_pwdb.so
module)
use standard system calls to check users and passwords.
On modern systems these system calls can use different account
databases depending on settings in the /etc/nsswitch.conf
file.
To use (say) NIS instead of the shadow suite, you either use the
pam_nis.so
module (if one is available on your
system), or use pam_unix.so
and configure the
nsswitch.conf
entries for passwd
,
shadow
, and group
to use
NIS.
A recently developed module uses SSSD, a common
service for all remote authentication, which can cache credentials
for when the network is unavailable.
All these modules are nice, but the overlap in functionality means there are many, slightly different ways to use the same authentication service (single sign-on). To use LDAP for example, you could:
The bottom line is that there is more than one way to configure a given access policy. However, there can be subtle differences in the various modules! For example, PAM's LDAP module and the NSS LDAP module actually attempt authentication differently! (I have no idea how other framework's LDAP modules work.)
The account
section is interesting.
The first line says the user must have a valid (unexpired)
user account.
The second says it is sufficient if that user is listed in the
local /etc/passwd
and /etc/shadow
files,
while the third line says it is sufficient if you are a system
account (that is, an account with a UID of less than 500).
But the second through fourth account
lines don't
seem to do anything!
You can comment out those three lines and the result is apparently
the same policy.
(What policy do you think they were trying to implement
here?)
The explanation may be that (as mentioned above)
pam_unix
uses the standard
system calls to check for users and passwords, and those system
calls may not be checking any local files.
So the intended policy is probably to allow a user
if it is a standard system account, or
if it is a valid account in the local /etc/passwd
and /etc/shadow
files, or if it is a valid account
in the standard (usually networked) database.
(What changes would you make to correctly implement this
policy?)
(Solaris Note:
Solaris 10 and newer no longer support pam_unix
or pam_pwdb
.
These have been replaced with a bunch of better modules.
See the Solaris
pam.conf(4) man page for more information.)
While you could restrict the use of hwbrowser
to root
by changing the permissions on the program
(or change group membership or by adding ACLs),
this is not a good way to do control access.
Such changes get lost when updating commands, are hard to remember,
and do not log violations.
So you should use PAM to enforce your policies whenever possible.
Suppose you decide to change the hwbrowser
configuration
so that only root
is allowed to run the command.
Take another look at the hwbrowser
configuration file's
auth
lines:
auth sufficient pam_rootok.so auth sufficient pam_timestamp.so auth required pam_stack.so \ service=system-auth
What would happen if you changed “sufficient
”
to “required
” in the rootok
line?
Now root
access would be required.
However PAM would still try pam_timestamp
and run through the modules listed in
the “system-auth
” file.
If those lines were commented out as well as changing
“sufficient
” to “required
”
in the first line, then you get the behavior you want:
only root
can run the command.
Changing sufficient
to requisite
would work too.
(To comment out a module means adding a “#
”
in the front of the line listing that module.)
(Note that just commenting out the second and third auth
lines will work too; if there are no required
modules,
the failure of all the sufficient
modules will cause PAM to
refuse authorization.
But this is undocumented PAM behavior, so I suggest you change
sufficient
to required
as well as
commenting out the second and third lines.)
Other PAM changes are simpler.
To modify “su
” to use the
“wheel
”
group membership for sufficient or required permission to run
a command, examine the /etc/pam.d/su
file.
To change the minimum required password length, look at the
/etc/pam.d/passwd
file, only in that case you will
only see references to the system-auth
file.
So go look at that file again.
If you want a different policy for some command, don't use
include
in that command's PAM configuration file.
Many system administrators mistakenly think they can set minimum
password length in other configuration files such as
/etc/default/login
, /etc/login.defs
,
or /etc/libuser.conf
.
But changes to those files may or may not have any effect!
It depends on your version of Unix or Linux used, as well as the
version of the passwd
command on your system
and which database you are using (e.g., shadow suite,
NIS, etc.).
You should always set the password policy using PAM. If you do set a minimum length in some other file(s) and those settings are used on your system, then a candidate password must meet the criteria from both the file(s) and from PAM. So you might as well only set your policy with PAM.
When changing passwords (or other user credentials),
the “password
”
modules are used to determine password policies
such as which database of passwords to use (e.g.,
/etc/shadow
, RADIUS, LDAP, etc.) and
password constraints (e.g., minimal length, types of characters
required such as a mix of letters and digits, etc.).
The “auth
” modules are still used to make sure the
current user has permission to change the password, but it is
the password
modules that determine acceptable passwords.
If you look up the password
modules found in
the system-auth
file
in the on-line PAM reference, you will quickly discover
that only the “pam_cracklib
” module controls password
constraints such as minimum password length, at least on Linux.
(Newer distros of Linux use the newer but compatible
“pam_pwquality
” module instead.)
This is done in a complex way so you really need to read
that module's description and the provided examples carefully.
(See some notes regarding pam_cracklib and pam_pwquality
below.)
Not all systems provide the exact same PAM modules, but usually
there are similar ones to the PAM-Linux distribution.
If your system doesn't seem to have pam_cracklib
or
pam_pwquality
,
see if there is another module with similar functionality.
On Solaris for example, you can use pam_authtok_check
instead.
You need to be careful! Modules with the same name may work differently on different systems. Always check the documentation and verify your PAM configuration files implement the policies you think they do. A good way to do that is to run experiments to test your policies.
The default policies (the ones controlled by PAM anyway) aren't always the most secure, and you should carefully review them on any upgraded system as part of the post-install process. A larger organization can have standardized PAM configurations that can be applied as a patch or RPM (or similar) package.
The Linux pam_unix
module in a PAM configuration
file with the context (module type) of “account” checks
that an account exists and isn't expired.
It does not check if an account has
been locked or has an invalid shell!
(Apparently pam_unix
assumes the auth
component will fail if the account is locked.)
This means that users using SSH keys to log in
will be allowed to do, so even if you lock their accounts with
the “passwd -l
” command or the
“usermod -L
” command (since
sshd
doesn't use the PAM auth modules in this
case)!
Setting an invalid shell is checked with the Linux
pam_shells
module, but that is usually included
only in the configuration files for FTP
servers by default.
(I've been locking users out this way for years and I've
never realized the danger.
Now I always add pam_shells
in the account
part of my PAM configuration (as required
),
and lock accounts by specifying an invalid shell such as
/bin/false
, for login
,
sshd
, and other remote access services.)
Solaris 10 and newer no longer use pam_unix
.
It has been replaced it with other modules that each do part of what
pam_unix
used to do.
The new pam_unix_account
module does check
for locked accounts.
Some versions of sshd
do check for locked accounts,
but only when configured to not use PAM.
When configured to use PAM, and sshd
uses keys
to authenticate the user, sshd
apparently only uses
the PAM account
and session
modules and
not the auth
modules.
While Linux PAM doesn't contain any(!) account
modules
that can check for locked accounts, you may be able to find one you
can use from non-standard PAM modules found on the Internet, or
create one yourself.
(Note that not all *nix systems have this security bug.)
The best way to prevent account access is to expire the account. That is checked by default in all PAM stacks. Access can be restored by setting a new expiration date (in the future).
As mentioned previously PAM modules are not limited to returning only pass or fail. Linux PAM defines over 30 different return values that a module might return (listed below). Instead of one of the original four control flags (required, sufficient, requisite, and optional) you can use the new syntax with square braces, like this:
type [value=action value=action ...] module options
You can list any one of seven different actions for each possible
return type.
Note, each module only returns one of these possible values
per invocation.
That's a lot of flexibility!
The value
is one of the following
PAM module return values:
The action
says how to combine this return
value for the module in the overall pass or fail
value that is returned to the application for the whole
stack.
It is one of:
ok
”
value will not be used to override that value.
(This is essentially the same as the old “required” flag
when the module returns SUCCESS, and is indeed often used as
“[success=ok...]
”.)
ok
with the side effect of terminating the
module stack and PAM immediately returning to the application.
(Similar to “sufficient”.) [... default=bad]
”.)
Each of the four control-flag keywords (required, requisite,
sufficient, and optional) have an equivalent expression in terms
of the [...]
syntax:
[success=ok new_authtok_reqd=ok ignore=ignore default=bad]
[success=ok new_authtok_reqd=ok ignore=ignore default=die]
[success=done new_authtok_reqd=done default=ignore]
[success=ok new_authtok_reqd=ok default=ignore]
So for example, “success=ok
” means that if
this module returns success, and all previous required module returned
success as well, than this module doesn't change that and the next module
is tried.
But if the previous modules have determined an overall failure so far,
“ok
” says not to change that regardless of the
success of this module.
(So this is exactly the same as the old required
control flag.)
The control field of:
[success=ok new_authtok_reqd=ok ignore=ignore default=bad]
Says that if the module returns success
, it contributes
to the overall result. If the module return is not success or
new_authtok_reqd or ignore (with the obvious meaning), treat it as
failure.
That's why this is equivalent to required
.
The return values are not well explained anywhere!
One would have to read the source code to figure them out.
You can find the list of return values in the file
_pam_types.h
in the GitHub repository found at
github.com/linux-pam/linux-pam/tree/master/libpam
.
Have fun with that!
As an example from an old Fedora system, consider the auth
lines from the smartcard-auth
PAM file:
auth required pam_env.so auth [success=done ignore=ignore default=die] \ pam_pkcs11.so nodebug wait_for_card auth required pam_deny.so
The cracklib
and pwquality
PAM modules
are very similar; pwquality
is a modern replacement
for cracklib
.
For both, the documentation uses the term minimum length or
“minlen
”, but it's really a “score” of
complexity, and not just the length of a password.
A password is acceptable if it passes certain checks (for example, not based
on a dictionary word or your account name), and has a complexity
score equal to or greater than minlen
.
This score for a candidate password is computed as follows when
using the default settings (cracklib and older pwquality):
lcredit
. ucredit
. dcredit
. ocredit
. minlen
is 9
.
With pam_cracklib
and older pam_pwquality
,
the default values for lcredit
, ucredit
,
dcredit
, and ocredit
are 1
(one).
The defaults for modern pam_pwquality
are different:
0 (zero) for all credits and minlen
defaults to 8
.
(These defaults are almost certainly no longer sufficient to prevent password
cracking by brute-force, so consider increasing minlen
on
your systems.)
The DoD/DISA recommendation (2015) is stricter:
minlen=14 dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-1
which requires at least one each of a digit, an uppercase letter, a lowercase
letter, and a symbol.
(This recommendation changes over time.
You can find some recommendations in
DoD Changes to UCR 2008,
section 5.4.6.2.1.2, part 1b subsection 8, (PDF), I think from 2015;
and public.cyber.mil/stigs/,
where you can download zips of STIGs for various systems (in the zip you'll find
a PDF readable copy of the STIG as well as the SCAP file you need
to check your system with some tool such as SCAP Workbench).
If the score is less than the value for minlen
,
the password is not acceptable (it “fails” the module's
“simplicity” test.)
Here's an example to illustrate how the complexity is calculated:
Suppose minlen=8
is used with
pam_cracklib
and pam_pwquality
.
If the user tries to set a password of “foobar
”
then cracklib/pwquality will not allow it.
The score for this password is 6 + 1 = 7 (assuming
lcredit=1
) but the minimum allowed score is 8.
The score for “Foobar
” would be
6 + 1 + 1 = 8, and that would
“pass” this test.
The score for “aB1$
” would be
4 + 1 + 1 + 1 + 1 = 8.
You don't have to use such a complex test if you don't want to.
If you set the credit for each type of character to zero then only
the length of the password will matter.
(This is in fact the default with pam_pwquality.so
.)
Besides this strength/complexity test for a minimum “length”,
pam_cracklib
/pwquality
has a hardcoded minimum number of bytes
(characters) in the password of 4.
(Perhaps because the U.S. government specifies that as a minimum
length, in
FIPS-112.
Note FIPS-112 was withdrawn in 2005.
In 2016, more general advice is given in
NIST SP 800-63B and
NIST SP 800-53 (see page 139)).
In addition the system password changing code often defines a
minimum length (number of characters, not minimum strength), whether
or not you use
pam_cracklib
/pam_pwquality
.
For Solaris, a length is defined in
/etc/default/passwd
.
For Linux (when using the “shadow” suite) it is defined
in /etc/login.defs
and possibly other files
(e.g. /etc/libuser.conf
).
Setting the credit for (say) digits to a number greater than
one or to a negative value allows more complex passwords to
be required.
Say you set dcredit=3
.
Then you will add zero to the score if no digits
are in the candidate password, add one if there is one digit, two
if there is two digits, and three if there is three or more
digits.
Basically the value is a maximum amount of extra credit you can
have by using that type of character in your passwords.
Setting the four credit values to 0 (zero) means only the length
of the password matters.
A negative credit number “-N” means the same as a positive number except that you must have at least that many or the candidate password will “fail” regardless of its overall score. For example, setting:
password required pam_cracklib.so dcredit=-1 ucredit=-1 minlen=8
or
password required pam_pwquality.so dcredit=-1 ucredit=-1 minlen=8
(and leaving lcredit
and ocredit
at the
default value of 1) will require all passwords to have at least one
digit and at least one uppercase letter.
So “Foobar” will fail even though its score is 8
(6 + 1 + 1).
But “Fo0bar” (note the zero) will pass this test (its
score is 6+1+1+1=9, and it has the required 1 digit).
Another cracklib/pwquality setting is
“difok
” (note the single “f”).
It determines how many characters must be different (inserted, removed, or
replaced) from your old password.
The default is 1 for pam_pwquality on Fedora (although the man page says it is
“5”), which means your new password must
have at least one character not present in the old password or be one character
shorter.
(It knows you previous password since you enter it when running the
passwd
command. For older passwords, it only knows
their hashes, so this setting only looks at your current password.)
The difok
default can be set in the
/etc/security/pwquality.conf
file.
Note a bug(?) in these modules
won't allow you to set a new password the
same as your current one, even if the value of difok
is zero.
To make the system remember old passwords, you need to add an
argument of “remember=N
” to the
pam_unix.so
module (the one for the
“password
” context, not the one for
“auth
”.)
(Modern systems often have a pam_pwhistory
module, but either
seems to work.)
Try adding “remember=3
” and then change your
password a few times.
Then examine the /etc/security/opasswd
file.
(Note, if using a single sign-on system such as LDAP
or Kerberos, password history doesn't work since the old hashes are only
saved on the local system.
Making this work properly would require a lot of changes; this is unlikely
to happen anytime soon.)
Cracklib/pwquality has a number of options that can be used
to support different policies, such as reject_username
,
maxrepeat
, and minclass
.
See the Linux PAM documentation for cracklib/pwquality
for a list of all options you can use.
(Also, test that your policy works as expected!)
PAM is powerful but difficult, which is why it is so important to understand how to use it correctly to enforce security policies. By controlling which modules are used, their options, the order of modules, and which control-flags are used, you have the flexibility to enforce any security policy you can imagine. You can use the more complex configuration file syntax for even more precise control. (You can read about that if you want to go beyond the simple syntax discussed in this tutorial.) You can even write your own modules (or you can hire someone to do so).
Defining, implementing, and auditing security policies is a common and vital task of the Unix/Linux system administrator. You should become familiar with the standard PAM modules on your system so you will have an idea of the sort of policy you can use PAM to enforce. From there you should read the other documentation found at linux-pam.org. (This was formerly kept at www.kernel.org/pub/linux/libs/pam/.)