CVE-2016-7067 (C, published 2018-09-10T14:29Z)
Description: value: Monit before version 5.20.0 is vulnerable to a cross site request forgery attack. Successful exploitation will enable an attacker to disable/enable all monitoring for a particular host or disable/enable monitoring for a specific service.
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: CWE-352 \u2014 Cross-Site Request Forgery (CSRF)
The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
Repository: https://bitbucket.org/tildeslash/monit
Files changed:
CHANGES
src/http/cervlet.c
src/http/client.c
src/http/processor.c
src/http/processor.h
src/util.c
src/util.h
Commit message: Fixed: CSRF vulnerability (CVE-2016-7067).

We use the double-submit cookie pattern to protect all POST requests (https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Double_Submit_Cookie)

The following http services are no longer implemented for GET method and require CSRF protected POST:

    _status
    _status2
    _summary
    _report
    _doaction
    _viewlog

The service for "_runtime" and "/<servicename>" is still available via GET, but only the non-state changing part (read-only) - the actions for "_runtime" (force validate, stop monit http) and "/<servicename>" require CSRF protected POST too.


Vulnerable code:
#define RUN         "/_runtime"
static void handle_action(HttpRequest, HttpResponse);
static void handle_do_action(HttpRequest, HttpResponse);
static void handle_run(HttpRequest, HttpResponse);
        if (ACTION(RUN))
                handle_run(req, res);
                handle_do_action(req, res);
                handle_action(req, res);
        } else if (ACTION(RUN)) {
                handle_run(req, res);
        } else if (ACTION(VIEWLOG)) {
                do_viewlog(req, res);
        } else if (ACTION(STATUS)) {
                print_status(req, res, 1);
        } else if (ACTION(STATUS2)) {
                print_status(req, res, 2);
        } else if (ACTION(SUMMARY)) {
                print_summary(req, res);
        } else if (ACTION(REPORT)) {
                _printReport(req, res);
        } else if (ACTION(DOACTION)) {
                handle_do_action(req, res);
                handle_action(req, res);
                                    "<td style='color:red;'><form method=POST action='_runtime'>Stop Monit http server? "
                                    "<input type=hidden name='action' value='stop'><input type=submit value='Go'></form></td>");
                                    "<td><form method=POST action='_runtime'>Force validate now? <input type=hidden name='action' value='validate'>"
                                    "<input type=submit value='Go'></form></td>");
                                            "<td><form method=GET action='_viewlog'>View Monit logfile? <input type=submit value='Go'></form></td>");
static void handle_action(HttpRequest req, HttpResponse res) {
static void handle_do_action(HttpRequest req, HttpResponse res) {

static void handle_run(HttpRequest req, HttpResponse res) {
        LOCK(Run.mutex)
        do_runtime(req, res);
        END_LOCK;
                                    "<td><form method=POST action=%s>"
                                    "<input type=submit value='Start service'></form></td>", s->name);
                                    "<td><form method=POST action=%s>"
                                    "<input type=submit value='Stop service'></form></td>", s->name);
                                    "<td><form method=POST action=%s>"
                                    "<input type=submit value='Restart service'></form></td>", s->name);
                            "<td><form method=POST action=%s>"
                            "<input type=hidden value='%s' name=action>"
                            "<input type=submit value='%s'></form></td></tr></table>",
                            s->name,
                            s->monitor ? "unmonitor" : "monitor",
                            s->monitor ? "Disable monitoring" : "Enable monitoring");
void set_header(HttpResponse res, const char *name, const char *value) {
        h->value = Str_dup(value);
        set_header(res, "Content-Type", mime);
void send_error(HttpRequest, HttpResponse, int status, const char *message, ...);
void set_header(HttpResponse res, const char *name, const char *value);
char *Util_monitId(char *idfile) {
        FILE *file = NULL;
        ASSERT(idfile);
                md5_context_t ctx;
                char buf[STRLEN];
                MD_T digest;
                /* Generate the unique id */
                snprintf(buf, STRLEN, "%lu%d%lu", (unsigned long)Time_now(), getpid(), random());
                md5_init(&ctx);
                md5_append(&ctx, (const md5_byte_t *)buf, STRLEN - 1);
                md5_finish(&ctx, (md5_byte_t *)digest);
                Util_digest2Bytes((unsigned char *)digest, 16, Run.id);
                fprintf(file, "%s", Run.id);

Fixed code:
Fixed: CSRF vulnerability (CVE-2016-7067).

#define RUNTIME     "/_runtime"
static void handle_service(HttpRequest, HttpResponse);
static void handle_service_action(HttpRequest, HttpResponse);
static void handle_doaction(HttpRequest, HttpResponse);
static void handle_runtime(HttpRequest, HttpResponse);
static void handle_runtime_action(HttpRequest, HttpResponse);
        if (ACTION(RUNTIME))
                handle_runtime_action(req, res);
        else if (ACTION(VIEWLOG))
                do_viewlog(req, res);
                handle_doaction(req, res);
                handle_service_action(req, res);
        } else if (ACTION(RUNTIME)) {
                handle_runtime(req, res);
                handle_service(req, res);
                                    "<td style='color:red;'>"
                                    "<form method=POST action='_runtime'>Stop Monit http server? "
                                    "<input type=hidden name='securitytoken' value='%s'>"
                                    "<input type=hidden name='action' value='stop'>"
                                    "<input type=submit value='Go'>"
                                    "</form>"
                                    "</td>",
                                    res->token);
                                    "<td>"
                                    "<form method=POST action='_runtime'>Force validate now? "
                                    "<input type=hidden name='securitytoken' value='%s'>"
                                    "<input type=hidden name='action' value='validate'>"
                                    "<input type=submit value='Go'>"
                                    "</form>"
                                    "</td>",
                                    res->token);
                                            "<td>"
                                            "<form method=POST action='_viewlog'>View Monit logfile? "
                                            "<input type=hidden name='securitytoken' value='%s'>"
                                            "<input type=submit value='Go'>"
                                            "</form>"
                                            "</td>",
                                            res->token);
static void handle_service(HttpRequest req, HttpResponse res) {
        char *name = req->url;
        Service_T s = Util_getService(++name);
        if (! s) {
                send_error(req, res, SC_NOT_FOUND, "There is no service named \"%s\"", name ? name : "");
                return;
        }
        do_service(req, res, s);
}


static void handle_service_action(HttpRequest req, HttpResponse res) {
static void handle_doaction(HttpRequest req, HttpResponse res) {
static void handle_runtime(HttpRequest req, HttpResponse res) {
        LOCK(Run.mutex)
        do_runtime(req, res);
        END_LOCK;
}


static void handle_runtime_action(HttpRequest req, HttpResponse res) {
        handle_runtime(req, res);
                                    "<td>"
                                    "<form method=POST action=%s>"
                                    "<input type=hidden name='securitytoken' value='%s'>"
                                    "<input type=submit value='Start service'>"
                                    "</form>"
                                    "</td>", s->name, res->token);
                                    "<td>"
                                    "<form method=POST action=%s>"
                                    "<input type=hidden name='securitytoken' value='%s'>"
                                    "<input type=submit value='Stop service'>"
                                    "</form>"
                                    "</td>", s->name, res->token);
                                    "<td>"
                                    "<form method=POST action=%s>"
                                    "<input type=hidden name='securitytoken' value='%s'>"
                                    "<input type=submit value='Restart service'>"
                                    "</form>"
                                    "</td>", s->name, res->token);
                                    "<td>"
                                    "<form method=POST action=%s>"
                                    "<input type=hidden name='securitytoken' value='%s'>"
                                    "<input type=hidden value='%s' name=action>"
                                    "<input type=submit value='%s'>"
                                    "</form>"
                                    "</td>",
                                    s->name,
                                    res->token,
                                    s->monitor ? "unmonitor" : "monitor",
                                    s->monitor ? "Disable monitoring" : "Enable monitoring");
        StringBuffer_append(res->outputbuffer, "</tr></table>");
        MD_T token;
        StringBuffer_append(data, "%ssecuritytoken=%s", StringBuffer_length(data) > 0 ? "&" : "", Util_getToken(token));
                "Cookie: securitytoken=%s\r\n"
                token,
void set_header(HttpResponse res, const char *name, const char *value, ...) {
        va_list ap;
        va_start(ap, value);
        h->value = Str_vcat(value, ap);
        va_end(ap);
        set_header(res, "Content-Type", "%s", mime);
                        set_header(res, "Set-Cookie", "securitytoken=%s; Max-Age=600; HttpOnly; SameSite=strict%s", res->token, Run.httpd.flags & Httpd_Ssl ? "; Secure" : "");
        Util_getToken(res->token);
        if (IS(req->method, METHOD_POST)) {
                // Check CSRF double-submit cookie (https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Double_Submit_Cookie)
                const char *cookie = get_header(req, "Cookie");
                const char *token = get_parameter(req, "securitytoken");
                if (! cookie) {
                        LogError("HttpRequest: access denied -- client [%s]: missing CSRF token cookie\n", NVLSTR(Socket_getRemoteHost(req->S)));
                        send_error(req, res, SC_FORBIDDEN, "Invalid CSRF Token");
                        return false;
                }
                if (! token) {
                        LogError("HttpRequest: access denied -- client [%s]: missing CSRF token in HTTP parameter\n", NVLSTR(Socket_getRemoteHost(req->S)));
                        send_error(req, res, SC_FORBIDDEN, "Invalid CSRF Token");
                        return false;
                }
                if (! Str_startsWith(cookie, "securitytoken=")) {
                        LogError("HttpRequest: access denied -- client [%s]: no CSRF token in cookie\n", NVLSTR(Socket_getRemoteHost(req->S)));
                        send_error(req, res, SC_FORBIDDEN, "Invalid CSRF Token");
                        return false;
                }
                if (Str_compareConstantTime(cookie + 14, token)) {
                        LogError("HttpRequest: access denied -- client [%s]: CSRF token mismatch\n", NVLSTR(Socket_getRemoteHost(req->S)));
                        send_error(req, res, SC_FORBIDDEN, "Invalid CSRF Token");
                        return false;
                }
        }
        MD_T token;
void send_error(HttpRequest, HttpResponse, int status, const char *message, ...) __attribute__((format (printf, 4, 5)));
void set_header(HttpResponse res, const char *name, const char *value, ...) __attribute__((format (printf, 3, 4)));
char *Util_getToken(MD_T token) {
        md5_context_t ctx;
        char buf[STRLEN];
        MD_T digest;
        snprintf(buf, STRLEN, "%lu%d%lu", (unsigned long)Time_now(), getpid(), random());
        md5_init(&ctx);
        md5_append(&ctx, (const md5_byte_t *)buf, STRLEN - 1);
        md5_finish(&ctx, (md5_byte_t *)digest);
        Util_digest2Bytes((unsigned char *)digest, 16, token);
        return token;
}
char *Util_monitId(char *idfile) {
        ASSERT(idfile);
        FILE *file = NULL;
                // Generate the unique id
                fprintf(file, "%s", Util_getToken(Run.id));
 * @return pointer to result buffer
/**
 * Get a random token
 * @param token buffer to store the MD digest
 * @return pointer to token buffer
 */
char *Util_getToken(MD_T token);