OpenSSH, PAM and user names

FreeBSD just published a security advisory for, amongst other issues, a piece of code in OpenSSH’s PAM integration which could allow an attacker to use one user’s credentials to impersonate another (CVE 2015-6563, original patch). I would like to clarify two things, one that is already mentioned in the advisory and one that isn’t.

The first is that in order to exploit this, the attacker must not only have valid credentials but also first compromise the unprivileged pre-authentication child process through a bug in OpenSSH itself or in a PAM service module.

The second is that this behavior, which is universally referred to in advisories and the trade press as a bug or flaw, is intentional and required by the PAM spec (such as it is). There are multiple legitimate use cases for this, such as:

  • Letting PAM, rather than the application, prompt for a user name; the spec allows passing NULL instead of a user name to pam_start(3), in which case it is the service module’s responsibility (in pam_sm_authenticate(3)) to prompt for a user name using pam_get_user(3). Note that OpenSSH does not support this.

  • Mapping multiple users with different identities and credentials in the authentication backend to a single “template” user when the application they need to access does not need to distinguish between them, or when this determination is made through other means (e.g. environment variable, which service modules are allowed to set).

  • Mapping Windows user names (which can contain spaces and non-ASCII characters that would trip up most Unix applications) to Unix user names.

That being said, I do not object to the patch, only to its characterization. Regarding the first issue, it is absolutely correct to consider the unprivileged child as possibly hostile; this is, after all, the entire point of privilege separation. Regarding the second issue, there are other (and probably better) ways to achieve the same result—performing the translation in the identity service, i.e. nsswitch, comes to mind—and the percentage of users affected by the change lies somewhere between zero and negligible.

One could argue that instead of silently ignoring the user name set by PAM, OpenSSH should compare it to the original user name and either emit a warning or drop the connection if it does not match, but that is a design choice which is entirely up to the OpenSSH developers.

Update 2015-08-27 NIST rates exploitability as “medium” rather than “low” because an attacker who is able to impersonate the UID used by the unprivileged child can use a debugger or other similar method to modify the username that the child passes back to the parent. In other words, an attacker can leverage elevated privileges into other elevated privileges. I disagree with the rating, but have never had any luck getting NIST to correct even blatantly false information in the past.

DNS improvements in FreeBSD 11

Erwin Lansing just posted a summary of the DNS session at the FreeBSD DevSummit that was held in conjunction with BSDCan 2014 in May. It gives a good overview of the current state of affairs, including known bugs and plans for the future.

I’ve been working on some of these issues recently (in between $dayjob and other projects). I fixed two issues in the last 48 hours, and am working on two more.

Continue reading “DNS improvements in FreeBSD 11” »

We can patch it for you wholesale

…but remembering costs extra.

Every once in a while, I come across a patch someone sent me, or which I developed in response to a bug report I received, but it’s been weeks or months and I can’t for the life of me remember where it came from, or what it’s for.

Case in point—I’m typing this on a laptop I haven’t used in over two months, and one of the first things I found when I powered it on and opened Chrome was a tab with the following patch:

diff --git a/lib/libpam/modules/pam_login_access/pam_login_access.c b/lib/libpam/modules/pam_login_access/pam_login_access.c
index 945d5eb..b365aee 100644
--- a/lib/libpam/modules/pam_login_access/pam_login_access.c
+++ b/lib/libpam/modules/pam_login_access/pam_login_access.c
@@ -79,20 +79,23 @@ pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,

        gethostname(hostname, sizeof hostname);

-       if (rhost == NULL || *(const char *)rhost == '\0') {
+       if (tty != NULL && *(const char *)tty != '\0') {
                PAM_LOG("Checking login.access for user %s on tty %s",
                    (const char *)user, (const char *)tty);
                if (login_access(user, tty) != 0)
                        return (PAM_SUCCESS);
                PAM_VERBOSE_ERROR("%s is not allowed to log in on %s",
                    user, tty);
-       } else {
+       } else if (rhost != NULL && *(const char *)rhost != '\0') {
                PAM_LOG("Checking login.access for user %s from host %s",
                    (const char *)user, (const char *)rhost);
                if (login_access(user, rhost) != 0)
                        return (PAM_SUCCESS);
                PAM_VERBOSE_ERROR("%s is not allowed to log in from %s",
                    user, rhost);
+       } else {
+               PAM_VERBOSE_ERROR("neither host nor tty is set");
+               return (PAM_SUCCESS);
        }

        return (PAM_AUTH_ERR);

The patch fixes a long-standing bug in pam_login_access(8) (the code assumes that either PAM_TTY or PAM_RHOST is defined, and crashes if they are both NULL), but I only have the vaguest recollection of the conversation that led up to it. If you’re the author, please contact me so I can give proper credit when I commit it.

On testing, part III

I just got word of an embarrassing bug in OpenPAM Nummularia. The is_upper() macro, which is supposed to evaluate to true if its argument is an upper-case letter in the ASCII character set, only evaluates to true for the letter A:

#define is_upper(ch)                            \
        (ch >= 'A' && ch <= 'A')

This macro is never used directly, but it is referenced by is_letter(), which is referenced by is_pfcs(), which is used to validate paths and path-like strings, i.e. service names and module names or paths. As a consequence, OpenPAM does not support services or modules which contain an upper-case letter other than A. I never noticed because a) none of the services or modules in use on the systems I use to develop and test OpenPAM have upper-case letters in their names and b) there are no unit or regression tests for the character classification macros, nor for any code path that uses them (except openpam_readword(), which uses is_lws() and is_ws()).

The obvious course of action is to add unit tests for the character classification macros (r760) and then fix the bug (r761). In this case, complete coverage is easy to achieve since there are only 256 possible inputs for each predicate.

I have merged the fix to FreeBSD head (r262529 and r262530). Impatient users can fix their system by running the following commands:

% cd /usr/src/contrib/openpam
% svn diff -r758:762 svn://svn.openpam.org/openpam/trunk | patch
% cd /usr/src/lib/libpam/libpam
% make && make install

Unsurprisingly, writing more unit tests for OpenPAM is moving up on my TODO list. Please contact me if you have the time and inclination to help out.

VerifyHostKeyDNS

The Internet Society likes my work. I aim to please…

One of the things I did in the process of importing LDNS and Unbound into FreeBSD 10 was to change the default value for VerifyHostKeyDNS from “no” to “yes” in our OpenSSH when compiled with LDNS support (which can be turned off by adding WITHOUT_LDNS=YES to /etc/src.conf before buildworld).

The announcement the ISOC blog post refers to briefly explains my reasons for doing so:

I consider this a lesser evil than “ask” (aka “train the user to type ‘yes’ and hit enter”) and “no” (aka “train the user to type ‘yes’ and hit enter without even the benefit of a second opinion”).

There were objections to this (which I’m too lazy to dig up and quote) along the lines of:

  • Shouldn’t OpenSSH tell you that it found and used an SSHFP record?
  • Shouldn’t known_hosts entries take precedence over SSHFP records?
  • Shouldn’t OpenSSH store the key in known_hosts after verifying it against an SSHFP record?

The answer to all of the above is “yes, but…”

Here is how host key verification should work, ideally:

  1. Obtain host key from server
  2. Gather cached host keys from various sources (known_hosts, SSHFP, LDAP…)
  3. If we found one or more cached keys:
    1. Check for and warn about inconsistencies between these sources
    2. Check for and warn about inconsistencies between the cached key and what the server sent us
    3. If we got a match from a trusted source, continue connecting
    4. Inform the user of any matches from untrusted sources
  4. Display the key’s fingerprint
  5. Ask the user whether to:
    1. Store the server’s key for future reference and continue connecting
    2. Continue connecting without storing the key
    3. Disconnect

The only configuration required here is a list of trusted and untrusted sources, the difference being that a match or mismatch from a trusted source is normative while a match or mismatch from an untrusted source is merely informative.

Unfortunately, in OpenSSH, SSHFP support seems to have been grafted onto the existing logic rather than integrated into it. Here’s how it actually works:

  1. Obtain host key from server
  2. If VerifyHostKeyDNS is “yes” or “ask”, look for SSHFP records in DNS
  3. If an SSHFP record was found:
    1. If it matches the server’s key:
      1. If it has a valid DNSSEC signature and VerifyHostKeyDNS is “yes”, continue connecting
      2. Otherwise, set a flag to indicate that a matching SSHFP record was found
    2. Otherwise, warn about the mismatch
  4. Look for cached keys in the user and system host key files
  5. If we got a match from the host key files, continue connecting
  6. If we did not find anything in the host key files:
    1. If we found a matching SSHFP record, tell the user
    2. Ask the user whether to:
      1. Store the server’s key for future reference and continue connecting
      2. Disconnect
  7. If we found a matching revoked key in the host key files, warn the user and terminate
  8. If we found a different key in the host key files, warn the user and terminate

Part of the problem is that at the point where we tell the user that we found a matching SSHFP record, we no longer know whether it was signed. By switching the default for VerifyHostKeyDNS to “yes”, I’m basically saying that I trust DNSSEC more than I trust the average user’s ability to understand the information they’re given and make an informed decision.