OpenSSH PAM Privilege Separation Vulnerabilities

Multiple vulnerabilities in OpenSSH were identified that could allow successful authentication as an arbitrary user and thus impersonation of other users.

 

Vendor  OpenBSD Foundation, http://www.openbsd.org
Affected Products OpenSSH
Affected Versions Portable versions <= 6.9p1
CVE-ID  CVE-2015-6564, CVE-2015-6563
Severity   Medium
 Author  Moritz Jodeit (@moritzj), Blue Frost Security GmbH

I. Impact

Two vulnerabilities were identified in the PAM privilege separation code. One of them (III) allows remote attackers who previously achieved remote code execution within the unprivileged pre-auth sandbox process to perform a successful authentication as an arbitrary user (e.g. root) and thus impersonate other users. The only additional prerequisite is any valid (possibly low-privileged) user account which can be used to login into the system via SSH.

II. Background

OpenSSH implements privilege separation which was introduced with version 5.9. Privilege separation is a generic approach which splits the code into two processes: An unprivileged child process and a privileged monitor process. The unprivileged child does most of the work and in particular processes all the network data. The monitor process communicates with the unprivileged child process and performs all the operations which require higher privileges. The idea of this design is to prevent programming errors in the unprivileged parts from compromising the whole application and thus prevent a full system compromise. A good technical overview can be found in the paper "Preventing Privilege Escalation" by Niels Provos et al. (http://www.peter.honeyman.org/u/provos/papers/privsep.pdf).

The unprivileged child process and privileged monitor process communicate via a socketpair. Several different monitor request and answer types are defined which can be used to exchange messages between the two processes. The complete list can be found in the mon_dispatch_proto{15,20} and mon_dispatch_postauth{15,20} structures defined in monitor.c.

Monitor requests have certain flags assigned which can restrict when and how requests are accepted by the monitor. E.g. the flag MON_ONCE determines that a request can only be sent once and is disabled after it was received for the first time in the monitor. The MON_AUTH flag determines that a request is related to the authentication process. The complete list of flags can be found in the monitor.c file as well.

Not all defined requests are permitted in every state of the SSH protocol. In order to control which requests are permitted, the functions monitor_permit() and monitor_permit_authentications() are used. The function monitor_permit() can be used to enable or disable a certain message while the function monitor_permit_authentications() enables or disables all authentication related messages which have the MON_AUTH flag set. When a request is received by the monitor which is currently not allowed the monitor process terminates by calling the fatal() function.

III. PAM Authentication Bypass in Privilege Separation

When PAM support is enabled in the portable version of OpenSSH, a few additonal monitor requests are enabled which can be found in the monitor.c file:

#ifdef USE_PAM
{MONITOR_REQ_PAM_START, MON_ONCE, mm_answer_pam_start},
{MONITOR_REQ_PAM_ACCOUNT, 0, mm_answer_pam_account},
{MONITOR_REQ_PAM_INIT_CTX, MON_ISAUTH, mm_answer_pam_init_ctx},
{MONITOR_REQ_PAM_QUERY, MON_ISAUTH, mm_answer_pam_query},
{MONITOR_REQ_PAM_RESPOND, MON_ISAUTH, mm_answer_pam_respond},
{MONITOR_REQ_PAM_FREE_CTX, MON_ONCE|MON_AUTHDECIDE, mm_answer_pam_free_ctx},
#endif 

Before any PAM-related monitor requests are sent, the unprivileged child process sends the MONITOR_REQ_PWNAM request to verify that the username received from the network represents a valid user. The monitor responds with the corresponding passwd struct entry if the user exists and additionally caches the username and passwd struct entry in the current monitor authentication context (struct Authctxt *authctxt).

PAM authentication then starts with the unprivileged child process sending the MONITOR_REQ_PAM_START request which tells the monitor to open a new authentication transaction for the current user by calling the PAM API function pam_start().

The next PAM-related monitor request sent by the child process is MONITOR_REQ_PAM_INIT_CTX which initializes [2] the current PAM authentication context in the monitor.

int
mm_answer_pam_init_ctx(int sock, Buffer *m)
{

        debug3("%s", __func__);
        authctxt->user = buffer_get_string(m, NULL);                    [1]
        sshpam_ctxt = (sshpam_device.init_ctx)(authctxt);               [2]
        sshpam_authok = NULL;
        buffer_clear(m);
[...] 

Interestingly the child process sends the current username as part of this request once again to the monitor and the monitor overwrites the previously stored username in the monitor authentication context with the received username [1].

During a normal PAM authentication the same username is sent, so this doesn't represent a problem. However an attacker who already compromised the unprivileged pre-auth child process could send a different username in this case. This could lead to a situation where the username stored in the monitor authentication context would not match the stored passwd struct entry.

The remaining PAM authentication only relies on the stored username. The user which is actually logged in after a successful authentication is on the contrary only identified by the stored passwd struct entry.

This allows an attacker who compromised the unprivileged child process to first send any desired username (e.g. root) with the MONITOR_REQ_PWNAM request which would store the corresponding passwd struct entry in the authentication context. Subsequently he would send a different username for whom he's in posession of the password and perform a valid PAM authentication with that user. Once the authentication was successful, the attacker is not logged in with the user he used for the authentication, but with the username he specified in the initial MONITOR_REQ_PWNAM request.

IV. Use-After-Free in PAM Privilege Separation

The monitor requests for PAM authentication which can be send by the unprivileged child process represent additional attack surface. But not only implementation flaws in the handlers have to be considered. Also the order in which requests are send could result in unexpected program states and thus lead to interesting vulnerabilities.

One such vulnerability can be found in the handler for the MONITOR_REQ_PAM_FREE_CTX request:

int
mm_answer_pam_free_ctx(int sock, Buffer *m)
{

        debug3("%s", __func__);
        (sshpam_device.free_ctx)(sshpam_ctxt);                          [3]
        buffer_clear(m);
        mm_request_send(sock, MONITOR_ANS_PAM_FREE_CTX, m);
        auth_method = "keyboard-interactive";
        auth_submethod = "pam";
        return (sshpam_authok == sshpam_ctxt);
} 

At [3] the free_ctx function pointer of the KbdintDevice structure is called which is set to sshpam_free_ctx(). That function can be found in auth pam.c:

static void
sshpam_free_ctx(void *ctxtp)
{
        struct pam_ctxt *ctxt = ctxtp;

        debug3("PAM: %s entering", __func__);
        sshpam_thread_cleanup();
        free(ctxt);                                                     [4]
        /*
         * We don't call sshpam_cleanup() here because we may need the PAM
         * handle at a later stage, e.g. when setting up a session.  It's
         * still on the cleanup list, so pam_end() *will* be called before
         * the server process terminates.
         */
} 

As can be seen this frees the current PAM context memory pointed to by the passed sshpam_ctxt pointer [4]. The flag MON_ONCE is set for the MONITOR_REQ_PAM_FREE_CTX monitor request which prevents a malicious child process from calling it multiple times which would otherwise lead to an obvious double free vulnerability.

However it is still possible for a compromised child process to first send the MONITOR_REQ_PAM_FREE_CTX request which will free the PAM context and then use any of the other PAM monitor requests which leads to a use-after-free condition.

The freed structure is defined in auth-pam.c as follows:

struct pam_ctxt {
        sp_pthread_t     pam_thread;
        int              pam_psock;
        int              pam_csock;
        int              pam_done;
}; 

As can be seen the structure does not contain any pointers or length fields which would be ideal candidates for the exploitation of this use-after-free bug. However it stores two other interesting values, which are the pam_psock and pam_csock file descriptors. These file descriptors identify the receiving and sending socket to the PAM authentication system respectively.

In order to exploit the use-after-free condition, you could try to replace the freed memory in the monitor process with a fake structure which would have pam_psocks and pam_csock being set to the file descriptors used for the communication with the unprivileged child process.

This way the monitor would send all PAM authentication requests directly to the unprivileged child process which could then impersonate the PAM subsystem and e.g. allow authentication for any user without a password.

The problem which most probably prevents the described way of exploitation is that only monitor requests which have the MON_AUTHDECIDE flag set are allowed to make an authentication decision:

if (!(ent->flags & MON_AUTHDECIDE))
     fatal("%s: unexpected authentication from %d",
         __func__, ent->type); 

For PAM only the MONITOR_REQ_PAM_FREE_CTX request has this flag set. Additionally the request has the MON_ONCE flag set which means we can send it to the monitor only a single time. Since we initially need to send this request to trigger the freeing of the PAM context, followed by other monitor requests for the actual authentication, we are running into the described MON_AUTHDECIDE check which prevents a successful authentication.

We didn't find a way to successfully exploit this use-after-free condition for anything interesting. But a creative mind might eventually find a useful scenario.

V. Mitigation

Both vulnerabilities were fixed in OpenSSH 7.0 which should be installed to resolve the issues.

VI. Disclosure Timeline

2015-08-10  Vulnerabilities reported to openssh@openssh com 
2015-08-11 OpenSSH 7.0 is released which fixes the issues

 

Unaltered electronic reproduction of this advisory is permitted. For all other reproduction or publication, in printing or otherwise, contact research@bluefrostsecurity.de for permission. Use of the advisory constitutes acceptance for use in an "as is" condition. All warranties are excluded. In no event shall Blue Frost Security be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages, even if Blue Frost Security has been advised of the possibility of such damages.

Copyright 2015 Blue Frost Security GmbH. All rights reserved. Terms of use apply.