CVE-2016-7067 (published 2018-09-10T14:29Z)

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 v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 6.5, MEDIUM (CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
description: 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.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         

Fix information:
repo_url: https://bitbucket.org/tildeslash/monit
commit_details: repo_url: https://bitbucket.org/tildeslash/monit
author: tildeslash
author_date: 2016-10-14 10:25:48+02:00
msg: 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.
repository: repo_url: https://bitbucket.org/tildeslash/monit
repo_name: visit repo url
description: visit repo url
repo_language: visit repo url
owner: tildeslash
file_changes: filename: CHANGES
change_type: ModificationType.MODIFY
diff: @@ -22,6 +22,8 @@ New: The cpu, memory, swap, inode and space tests now allow also decimal values
 
 New: Issue #457: Update HOME environment variable when executing program as other user.
 
+Fixed: CSRF vulnerability (CVE-2016-7067).
+
 Fixed: Monit HTTP GUI: Listen on IPv6 interface if no address is specified (linux specific issue).
 
 Fixed: Monit HTTP GUI: Support CIDR mask 0 as explicit allow-all IP ACL (e.g. "allow 0.0.0.0/0").

programming_language: Markdown
filename: cervlet.c
change_type: ModificationType.MODIFY
diff: @@ -99,7 +99,7 @@
 #define STATUS2     "/_status2"
 #define SUMMARY     "/_summary"
 #define REPORT      "/_report"
-#define RUN         "/_runtime"
+#define RUNTIME     "/_runtime"
 #define VIEWLOG     "/_viewlog"
 #define DOACTION    "/_doaction"
 #define FAVICON     "/favicon.ico"
@@ -133,9 +133,11 @@ static void do_ping(HttpResponse);
 static void do_getid(HttpResponse);
 static void do_runtime(HttpRequest, HttpResponse);
 static void do_viewlog(HttpRequest, HttpResponse);
-static void handle_action(HttpRequest, HttpResponse);
-static void handle_do_action(HttpRequest, HttpResponse);
-static void handle_run(HttpRequest, HttpResponse);
+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);
 static void is_monit_running(HttpResponse);
 static void do_service(HttpRequest, HttpResponse, Service_T);
 static void print_alerts(HttpResponse, Mail_T);
@@ -420,8 +422,10 @@ static void _printStatus(Output_Type type, HttpResponse res, Service_T s) {
  */
 static void doPost(HttpRequest req, HttpResponse res) {
         set_content_type(res, "text/html");
-        if (ACTION(RUN))
-                handle_run(req, res);
+        if (ACTION(RUNTIME))
+                handle_runtime_action(req, res);
+        else if (ACTION(VIEWLOG))
+                do_viewlog(req, res);
         else if (ACTION(STATUS))
                 print_status(req, res, 1);
         else if (ACTION(STATUS2))
@@ -431,9 +435,9 @@ static void doPost(HttpRequest req, HttpResponse res) {
         else if (ACTION(REPORT))
                 _printReport(req, res);
         else if (ACTION(DOACTION))
-                handle_do_action(req, res);
+                handle_doaction(req, res);
         else
-                handle_action(req, res);
+                handle_service_action(req, res);
 }
 
 
@@ -447,12 +451,10 @@ static void doGet(HttpRequest req, HttpResponse res) {
                 LOCK(Run.mutex)
                 do_home(res);
                 END_LOCK;
-        } else if (ACTION(RUN)) {
-                handle_run(req, res);
+        } else if (ACTION(RUNTIME)) {
+                handle_runtime(req, res);
         } else if (ACTION(TEST)) {
                 is_monit_running(res);
-        } else if (ACTION(VIEWLOG)) {
-                do_viewlog(req, res);
         } else if (ACTION(ABOUT)) {
                 do_about(res);
         } else if (ACTION(FAVICON)) {
@@ -461,18 +463,8 @@ static void doGet(HttpRequest req, HttpResponse res) {
                 do_ping(res);
         } else if (ACTION(GETID)) {
                 do_getid(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);
         } else {
-                handle_action(req, res);
+                handle_service(req, res);
         }
 }
 
@@ -812,15 +804,33 @@ static void do_runtime(HttpRequest req, HttpResponse res) {
                 StringBuffer_append(res->outputbuffer,
                                     "<table id='buttons'><tr>");
                 StringBuffer_append(res->outputbuffer,
-                                    "<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 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);
                 StringBuffer_append(res->outputbuffer,
-                                    "<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=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);
 
                 if ((Run.flags & Run_Log) && ! (Run.flags & Run_UseSyslog)) {
                         StringBuffer_append(res->outputbuffer,
-                                            "<td><form method=GET action='_viewlog'>View Monit logfile? <input type=submit value='Go'></form></td>");
+                                            "<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);
                 }
                 StringBuffer_append(res->outputbuffer,
                                     "</tr></table>");
@@ -868,7 +878,18 @@ static void do_viewlog(HttpRequest req, HttpResponse res) {
 }
 
 
-static void handle_action(HttpRequest req, HttpResponse res) {
+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) {
         char *name = req->url;
         Service_T s = Util_getService(++name);
         if (! s) {
@@ -900,12 +921,11 @@ static void handle_action(HttpRequest req, HttpResponse res) {
 }
 
 
-static void handle_do_action(HttpRequest req, HttpResponse res) {
+static void handle_doaction(HttpRequest req, HttpResponse res) {
         Service_T s;
         Action_Type doaction = Action_Ignored;
         const char *action = get_parameter(req, "action");
         const char *token = get_parameter(req, "token");
-
         if (action) {
                 if (is_readonly(req)) {
                         send_error(req, res, SC_FORBIDDEN, "You do not have sufficient privileges to access this page");
@@ -943,7 +963,14 @@ static void handle_do_action(HttpRequest req, HttpResponse res) {
 }
 
 
-static void handle_run(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) {
         const char *action = get_parameter(req, "action");
         if (action) {
                 if (is_readonly(req)) {
@@ -960,9 +987,7 @@ static void handle_run(HttpRequest req, HttpResponse res) {
                         return;
                 }
         }
-        LOCK(Run.mutex)
-        do_runtime(req, res);
-        END_LOCK;
+        handle_runtime(req, res);
 }
 
 
@@ -1665,29 +1690,47 @@ static void print_buttons(HttpRequest req, HttpResponse res, Service_T s) {
         /* Start program */
         if (s->start)
                 StringBuffer_append(res->outputbuffer,
-                                    "<td><form method=POST action=%s>"
+                                    "<td>"
+                                    "<form method=POST action=%s>"
+                                    "<input type=hidden name='securitytoken' value='%s'>"
                                     "<input type=hidden value='start' name=action>"
-                                    "<input type=submit value='Start service'></form></td>", s->name);
+                                    "<input type=submit value='Start service'>"
+                                    "</form>"
+                                    "</td>", s->name, res->token);
         /* Stop program */
         if (s->stop)
                 StringBuffer_append(res->outputbuffer,
-                                    "<td><form method=POST action=%s>"
+                                    "<td>"
+                                    "<form method=POST action=%s>"
+                                    "<input type=hidden name='securitytoken' value='%s'>"
                                     "<input type=hidden value='stop' name=action>"
-                                    "<input type=submit value='Stop service'></form></td>", s->name);
+                                    "<input type=submit value='Stop service'>"
+                                    "</form>"
+                                    "</td>", s->name, res->token);
         /* Restart program */
         if ((s->start && s->stop) || s->restart)
                 StringBuffer_append(res->outputbuffer,
-                                    "<td><form method=POST action=%s>"
+                                    "<td>"
+                                    "<form method=POST action=%s>"
+                                    "<input type=hidden name='securitytoken' value='%s'>"
                                     "<input type=hidden value='restart' name=action>"
-                                    "<input type=submit value='Restart service'></form></td>", s->name);
+                                    "<input type=submit value='Restart service'>"
+                                    "</form>"
+                                    "</td>", s->name, res->token);
         /* (un)monitor */
         StringBuffer_append(res->outputbuffer,
-                            "<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");
+                                    "<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>");
 }
 
 

programming_language: C
filename: client.c
change_type: ModificationType.MODIFY
diff: @@ -138,14 +138,18 @@ static void _parseHttpResponse(Socket_T S) {
 static void _send(Socket_T S, const char *request, StringBuffer_T data) {
         _argument(data, "format", "text");
         char *_auth = _getBasicAuthHeader();
+        MD_T token;
+        StringBuffer_append(data, "%ssecuritytoken=%s", StringBuffer_length(data) > 0 ? "&" : "", Util_getToken(token));
         int rv = Socket_print(S,
                 "POST %s HTTP/1.0\r\n"
                 "Content-Type: application/x-www-form-urlencoded\r\n"
+                "Cookie: securitytoken=%s\r\n"
                 "Content-Length: %d\r\n"
                  "%s"
                  "\r\n"
                  "%s",
                 request,
+                token,
                 StringBuffer_length(data),
                 _auth ? _auth : "",
                 StringBuffer_toString(data));

programming_language: C++
filename: processor.c
change_type: ModificationType.MODIFY
diff: @@ -241,7 +241,7 @@ void send_error(HttpRequest req, HttpResponse res, int code, const char *msg, ..
  * @param name Header key name
  * @param value Header key value
  */
-void set_header(HttpResponse res, const char *name, const char *value) {
+void set_header(HttpResponse res, const char *name, const char *value, ...) {
         HttpHeader h = NULL;
 
         ASSERT(res);
@@ -249,7 +249,10 @@ void set_header(HttpResponse res, const char *name, const char *value) {
 
         NEW(h);
         h->name = Str_dup(name);
-        h->value = Str_dup(value);
+        va_list ap;
+        va_start(ap, value);
+        h->value = Str_vcat(value, ap);
+        va_end(ap);
         if (res->headers) {
                 HttpHeader n, p;
                 for (n = p = res->headers; p; n = p, p = p->next) {
@@ -285,7 +288,7 @@ void set_status(HttpResponse res, int code) {
  * @param mime Mime content type, e.g. text/html
  */
 void set_content_type(HttpResponse res, const char *mime) {
-        set_header(res, "Content-Type", mime);
+        set_header(res, "Content-Type", "%s", mime);
 }
 
 
@@ -442,6 +445,7 @@ static void do_service(Socket_T s) {
                 if (Run.httpd.flags & Httpd_Ssl)
                         set_header(res, "Strict-Transport-Security", "max-age=63072000; includeSubdomains; preload");
                 if (is_authenticated(req, res)) {
+                        set_header(res, "Set-Cookie", "securitytoken=%s; Max-Age=600; HttpOnly; SameSite=strict%s", res->token, Run.httpd.flags & Httpd_Ssl ? "; Secure" : "");
                         if (IS(req->method, METHOD_GET))
                                 Impl.doGet(req, res);
                         else if (IS(req->method, METHOD_POST))
@@ -574,6 +578,7 @@ static HttpResponse create_HttpResponse(Socket_T S) {
         res->is_committed = false;
         res->protocol = SERVER_PROTOCOL;
         res->status_msg = get_status_string(SC_OK);
+        Util_getToken(res->token);
         return res;
 }
 
@@ -727,6 +732,31 @@ static boolean_t is_authenticated(HttpRequest req, HttpResponse res) {
                         return false;
                 }
         }
+        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;
+                }
+        }
         return true;
 }
 

programming_language: C
filename: processor.h
change_type: ModificationType.MODIFY
diff: @@ -89,6 +89,7 @@ typedef struct response {
         HttpHeader headers;
         const char *status_msg;
         StringBuffer_T outputbuffer;
+        MD_T token;
         Ssl_T ssl;
 } *HttpResponse;
 
@@ -102,9 +103,9 @@ void add_Impl(void(*doGet)(HttpRequest, HttpResponse), void(*doPost)(HttpRequest
 void set_content_type(HttpResponse res, const char *mime);
 const char *get_header(HttpRequest req, const char *header_name);
 void escapeHTML(StringBuffer_T sb, const char *s);
-void send_error(HttpRequest, HttpResponse, int status, const char *message, ...);
+void send_error(HttpRequest, HttpResponse, int status, const char *message, ...) __attribute__((format (printf, 4, 5)));
 const char *get_parameter(HttpRequest req, const char *parameter_name);
-void set_header(HttpResponse res, const char *name, const char *value);
+void set_header(HttpResponse res, const char *name, const char *value, ...) __attribute__((format (printf, 3, 4)));
 void Processor_setHttpPostLimit();
 
 #endif

programming_language: C
filename: util.c
change_type: ModificationType.MODIFY
diff: @@ -1385,27 +1385,30 @@ void Util_printServiceList() {
 }
 
 
-char *Util_monitId(char *idfile) {
-        FILE *file = NULL;
+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;
+}
 
-        ASSERT(idfile);
 
+char *Util_monitId(char *idfile) {
+        ASSERT(idfile);
+        FILE *file = NULL;
         if (! File_exist(idfile)) {
-                md5_context_t ctx;
-                char buf[STRLEN];
-                MD_T digest;
+                // Generate the unique id
                 file = fopen(idfile, "w");
                 if (! file) {
                         LogError("Error opening the idfile '%s' -- %s\n", idfile, STRERROR);
                         return NULL;
                 }
-                /* 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);
+                fprintf(file, "%s", Util_getToken(Run.id));
                 LogInfo(" New Monit id: %s\n Stored in '%s'\n", Run.id, idfile);
         } else {
                 if (! File_isFile(idfile)) {

programming_language: C
filename: util.h
change_type: ModificationType.MODIFY
diff: @@ -77,6 +77,7 @@ int Util_handle0Escapes(char *buf);
  * @param digest buffer containing a MD digest
  * @param mdlen digest length
  * @param result buffer to write the result to. Must be at least 41 bytes long.
+ * @return pointer to result buffer
  */
 char *Util_digest2Bytes(unsigned char *digest, int mdlen, MD_T result);
 
@@ -165,6 +166,14 @@ void Util_printService(Service_T s);
 void Util_printServiceList();
 
 
+/**
+ * Get a random token
+ * @param token buffer to store the MD digest
+ * @return pointer to token buffer
+ */
+char *Util_getToken(MD_T token);
+
+
 /**
  * Open and read the id from the given idfile. If the idfile doesn't exist,
  * generate new id and store it in the id file.

programming_language: Java