OpenSMTPD's developers were ready with the patches before MITRE was
ready with the CVE-IDs.)
Qualys Security Advisory
OpenSMTPD Audit Report
==============================
Contents
==============================
Summary
Approach
Local Vulnerabilities
Remote Vulnerabilities
Inter-Process Vulnerabilities
Miscellaneous Bugs
Acknowledgments
==============================
Summary
==============================
For the past few months, one of our background projects has been to
audit OpenSMTPD, a free implementation of the server-side Simple Mail
Transfer Protocol (SMTP). OpenSMTPD replaces Sendmail as OpenBSD's
default Mail Transfer Agent (MTA) since OpenBSD 5.6, released on
November 1, 2014.
OpenSMTPD was designed to be secure, reliable, performant, and easy to
configure. Indeed, its codebase lives up to OpenBSD's reputation: it is
clean, modular, privilege-separated, and made our audit easy and really
enjoyable. However, the project is pretty much in its infancy (the first
stable version, 5.3, was released on March 17, 2013), which explains why
we discovered various vulnerabilities during our security assessment:
- an oversight in the portable version of fgetln() that allows attackers
to read and write out-of-bounds memory;
- multiple denial-of-service vulnerabilities that allow local users to
kill or hang OpenSMTPD;
- a stack-based buffer overflow that allows local users to crash
OpenSMTPD, or execute arbitrary code as the non-chrooted _smtpd user;
- a hardlink attack (or race-conditioned symlink attack) that allows
local users to unset the chflags() of arbitrary files;
- a hardlink attack that allows local users to read the first line of
arbitrary files (for example, root's hash from /etc/master.passwd);
- a denial-of-service vulnerability that allows remote attackers to fill
OpenSMTPD's queue or mailbox hard-disk partition;
- an out-of-bounds memory read that allows remote attackers to crash
OpenSMTPD, or leak information and defeat the ASLR protection;
- a use-after-free vulnerability that allows remote attackers to crash
OpenSMTPD, or execute arbitrary code as the non-chrooted _smtpd user;
- multiple inter-process vulnerabilities that allow attackers to
escalate from one (already-compromised) OpenSMTPD process to another.
==============================
Approach
==============================
The OpenSMTPD version that we audited is available at:
https://www.opensmtpd.org/
and is installed by default on OpenBSD's latest release (OpenBSD 5.7,
released on May 1, 2015). Unless otherwise noted, the vulnerabilities
that we discovered in OpenSMTPD 5.4.4p1 affect OpenSMTPD's latest
release as well (OpenSMTPD 5.7.1p1, released on June 30, 2015).
The "hybrid approach" that we adopted to review OpenSMTPD is described
in the bible of code auditing, "The Art of Software Security Assessment"
(by Mark Dowd, John McDonald, and Justin Schuh):
- We started with a "top-down approach" and reviewed the high-level
information that we gathered on OpenSMTPD: READMEs, manual pages, web
pages (https://www.opensmtpd.org/
and https://www.poolp.org/).
This approach allowed us to quickly understand OpenSMTPD's design
(seven privilege-separated, long-running, and event-driven processes
that communicate through UNIX sockets and the imsg API) and identify
its attack surface (local, remote, and inter-process entry points).
- We continued with a "bottom-up approach" and reviewed OpenSMTPD's
implementation: the lowest-level code first (openbsd-compat/ and
smtpd/mproc.c), followed by the higher-level code.
This approach allowed us to quickly identify complex vulnerabilities:
the remote out-of-bounds memory read and use-after-free are actually a
combination of several low-level and high-level bugs.
------------------------------
Privilege Separation
------------------------------
--[ PROC_PARENT ]-----------------------------
User: root
Chroot: no
Peers: PROC_CONTROL, PROC_LKA, PROC_QUEUE, PROC_CA, PROC_PONY
PROC_PARENT, the "[priv]" process, spawns the six other long-running
processes at startup (by calling fork_peers() from main()), and the
transient Mail Delivery Agent (MDA) processes on demand (by calling
forkmda() from parent_imsg()).
If any of its long-running children dies, PROC_PARENT calls
parent_shutdown(), kill()s its remaining children, and exit()s, but does
not restart automatically: if we try to exploit a memory corruption, we
have to come up with a one-shot, not a brute-force.
--[ PROC_CONTROL ]-----------------------------
User: _smtpd
Chroot: /var/empty
Peers: PROC_SCHEDULER, PROC_QUEUE, PROC_PARENT, PROC_LKA, PROC_PONY,
PROC_CA
PROC_CONTROL, the "control" process, handles messages from the control
socket "/var/run/smtpd.sock" (by calling control_dispatch_ext()), and
gathers statistics from its peers (by calling control_imsg()).
--[ PROC_PONY ]-----------------------------
User: _smtpd
Chroot: /var/empty
Peers: PROC_PARENT, PROC_QUEUE, PROC_LKA, PROC_CONTROL, PROC_CA
PROC_PONY, the "pony express" process
(https://en.wikipedia.org/
SMTP sessions (by calling smtp_imsg()), the client-side MTA sessions (by
calling mta_imsg()), and the local MDA deliveries (by calling
mda_imsg()).
--[ PROC_LKA ]-----------------------------
User: _smtpd
Chroot: no (needs access to /etc/resolv.conf and /etc/ssl/cert.pem)
Peers: PROC_PARENT, PROC_QUEUE, PROC_CONTROL, PROC_PONY
PROC_LKA, the "lookup" process, performs all lookups on behalf of the
other processes: asynchronous DNS resolution (by calling dns_imsg() and
libasr), user information and credentials lookup, SSL certificate
verification, alias expansion (by calling lka_imsg()).
--[ PROC_QUEUE ]-----------------------------
User: _smtpq (or _smtpd if _smtpq does not exist)
Chroot: /var/spool/smtpd
Peers: PROC_PARENT, PROC_CONTROL, PROC_LKA, PROC_SCHEDULER, PROC_PONY
PROC_QUEUE, the "queue" process, manages the persistent storage of
messages and envelopes (by calling queue_imsg()). By default, the
smtpd/queue_fs.c backend is used.
--[ PROC_SCHEDULER ]-----------------------------
User: _smtpd
Chroot: /var/empty
Peers: PROC_CONTROL, PROC_QUEUE
PROC_SCHEDULER, the "scheduler" process, knows about all existing
messages and envelopes (by calling scheduler_imsg()), and decides when
to relay or deliver them. By default, the smtpd/scheduler_ramqueue.c
backend is used.
--[ PROC_CA ]-----------------------------
User: _smtpd
Chroot: /var/empty
Peers: PROC_CONTROL, PROC_PARENT, PROC_PONY
PROC_CA, the "klondike" process
(https://en.wikipedia.org/
privilege-separated RSA encryption and decryption on behalf of PROC_PONY
(by calling ca_imsg()).
------------------------------
Attack Surface
------------------------------
--[ Local Vectors ]-----------------------------
----[ .forward
Local users may put a .forward file in their home directory in order to
control how their incoming email is processed and delivered.
When PROC_PONY receives a CMD_RCPT_TO from one of its SMTP clients, it
sends an IMSG_SMTP_EXPAND_RCPT to PROC_LKA. If the recipient is a local
user, the (unprivileged) PROC_LKA sends an IMSG_LKA_OPEN_FORWARD to the
(privileged) PROC_PARENT. If PROC_PARENT manages to open() the user's
.forward file, it sends its file descriptor back to PROC_LKA, which
parses and expands its contents.
----[ Control Socket
PROC_CONTROL calls getpeereid(), or getsockopt(SO_PEERCRED), in order to
determine the credentials of the clients that connect to its UNIX socket
"/var/run/smtpd.sock". It processes all the messages received from
connections initiated by root, but otherwise processes only the
IMSG_CTL_SMTP_SESSION and forwards it to PROC_PONY.
Clients normally connect to the control socket with the command-line
program "smtpctl", but we may also connect() to it directly, should we
ever want to exploit a vulnerability in the imsg API, for example.
----[ Offline Directory
The command-line program "smtpctl" can be used to send email (when
invoked as "sendmail"): it connects to the control socket, sends an
IMSG_CTL_SMTP_SESSION to PROC_CONTROL (which forwards it to PROC_PONY),
and enqueues the email through this local SMTP session (enqueue() in
smtpd/enqueue.c).
However, if OpenSMTPD is not running, the connection to the control
socket will fail, and "smtpctl" will simply store the email into the
"/var/spool/smtpd/offline" directory, which is mode 01777
(enqueue_offline() in smtpd/enqueue.c).
Later, when OpenSMTPD restarts, it will execvp() "smtpctl" for each
email stored in the offline directory, exactly as if its owner had just
submitted it for the first time (offline_enqueue() in smtpd/smtpd.c).
Komentarų nėra:
Rašyti komentarą