Blue Frost Security GmbH
https://www.bluefrostsecurity.de/ research(at)bluefrostsecurity.de
BFS-SA-2015-002 13-August-2015
________________________________________________________________________________
Affected Product: OpenSSH (http://www.openssh.com)
Affected Version: Portable versions <= 6.9p1
Vulnerability: Vulnerabilities in PAM Privilege Separation Code
________________________________________________________________________________
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
________________________________________________________________________________
Credit:
Bugs found by Moritz Jodeit of Blue Frost Security GmbH.
________________________________________________________________________________
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.