CVE-2016-7067: Cross-Site Request Forgery (CSRF) (CWE-352)
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.
CWE 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.
CVSS v3 base score: 6.5
Published: 2018-09-10T14:29Z
Repository: https://bitbucket.org/tildeslash/monit
Language: C
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 date: 2016-10-14 10:25:48 +0200
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.

Diff: diff --git a/CHANGES b/CHANGES
index 52074d5..60d5a2d 100644
--- a/CHANGES
+++ b/CHANGES
@@ -20,10 +20,12 @@ New: Added new options to "set limits" statement, for setting default timeouts:
 
 New: The cpu, memory, swap, inode and space tests now allow also decimal values for percent limit.
 
 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").
 
 Fixed: Host header if IPv6 address is given in HTTP, APACHE-STATUS or WEBSOCKET protocol tests.
diff --git a/src/http/cervlet.c b/src/http/cervlet.c
index 7086104..bbde815 100644
--- a/src/http/cervlet.c
+++ b/src/http/cervlet.c
@@ -97,11 +97,11 @@
 #define GETID       "/_getid"
 #define STATUS      "/_status"
 #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"
 
 
@@ -131,13 +131,15 @@ static void do_home_host(HttpResponse);
 static void do_about(HttpResponse);
 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);
 static void print_buttons(HttpRequest, HttpResponse, Service_T);
 static void print_service_rules_timeout(HttpResponse, Service_T);
@@ -418,24 +420,26 @@ static void _printStatus(Output_Type type, HttpResponse res, Service_T s) {
  * Called by the Processor (via the service method)
  * to handle a POST request.
  */
 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))
                 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_doaction(req, res);
         else
-                handle_action(req, res);
+                handle_service_action(req, res);
 }
 
 
 /**
  * Called by the Processor (via the service method)
@@ -445,36 +449,24 @@ static void doGet(HttpRequest req, HttpResponse res) {
         set_content_type(res, "text/html");
         if (ACTION(HOME)) {
                 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)) {
                 printFavicon(res);
         } else if (ACTION(PING)) {
                 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);
         }
 }
 
 
 /* ----------------------------------------------------------------- Helpers */
@@ -810,19 +802,37 @@ static void do_runtime(HttpRequest req, HttpResponse res) {
         StringBuffer_append(res->outputbuffer, "</table>");
         if (! is_readonly(req)) {
                 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>");
         }
         do_foot(res);
@@ -866,11 +876,22 @@ static void do_viewlog(HttpRequest req, HttpResponse res) {
         }
         do_foot(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) {
                 send_error(req, res, SC_NOT_FOUND, "There is no service named \"%s\"", name ? name : "");
                 return;
@@ -898,16 +919,15 @@ static void handle_action(HttpRequest req, HttpResponse res) {
         }
         do_service(req, res, s);
 }
 
 
-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");
                         return;
                 }
@@ -941,11 +961,18 @@ static void handle_do_action(HttpRequest req, HttpResponse res) {
                 do_wakeupcall();
         }
 }
 
 
-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)) {
                         send_error(req, res, SC_FORBIDDEN, "You do not have sufficient privileges to access this page");
                         return;
@@ -958,13 +985,11 @@ static void handle_run(HttpRequest req, HttpResponse res) {
                         send_error(req, res, SC_SERVICE_UNAVAILABLE, "The Monit http server is stopped");
                         Engine_stop();
                         return;
                 }
         }
-        LOCK(Run.mutex)
-        do_runtime(req, res);
-        END_LOCK;
+        handle_runtime(req, res);
 }
 
 
 static void do_service(HttpRequest req, HttpResponse res, Service_T s) {
         char buf[STRLEN];
@@ -1663,33 +1688,51 @@ static void print_buttons(HttpRequest req, HttpResponse res, Service_T s) {
         }
         StringBuffer_append(res->outputbuffer, "<table id='buttons'><tr>");
         /* 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>");
 }
 
 
 static void print_service_rules_timeout(HttpResponse res, Service_T s) {
         for (ActionRate_T ar = s->actionratelist; ar; ar = ar->next) {
diff --git a/src/http/client.c b/src/http/client.c
index d0f7a02..b4bb929 100644
--- a/src/http/client.c
+++ b/src/http/client.c
@@ -136,18 +136,22 @@ 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));
         FREE(_auth);
         if (rv < 0)
diff --git a/src/http/processor.c b/src/http/processor.c
index 208fce9..16ff809 100644
--- a/src/http/processor.c
+++ b/src/http/processor.c
@@ -239,19 +239,22 @@ void send_error(HttpRequest req, HttpResponse res, int code, const char *msg, ..
  * had already been set the new value overwrites the previous one.
  * @param res HttpResponse object
  * @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);
         ASSERT(name);
 
         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) {
                         if (IS(p->name, name)) {
                                 FREE(p->value);
@@ -283,11 +286,11 @@ void set_status(HttpResponse res, int code) {
  * Set the response content-type
  * @param res HttpResponse object
  * @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);
 }
 
 
 /**
  * Returns the value of the specified header
@@ -440,10 +443,11 @@ static void do_service(Socket_T s) {
         volatile HttpRequest req = create_HttpRequest(s);
         if (res && req) {
                 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))
                                 Impl.doPost(req, res);
                         else
@@ -572,10 +576,11 @@ static HttpResponse create_HttpResponse(Socket_T S) {
         res->status = SC_OK;
         res->outputbuffer = StringBuffer_create(256);
         res->is_committed = false;
         res->protocol = SERVER_PROTOCOL;
         res->status_msg = get_status_string(SC_OK);
+        Util_getToken(res->token);
         return res;
 }
 
 
 /**
@@ -725,10 +730,35 @@ static boolean_t is_authenticated(HttpRequest req, HttpResponse res) {
                         send_error(req, res, SC_UNAUTHORIZED, "You are not authorized to access monit. Either you supplied the wrong credentials (e.g. bad password), or your browser doesn't understand how to supply the credentials required");
                         set_header(res, "WWW-Authenticate", "Basic realm=\"monit\"");
                         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;
 }
 
 
 /**
diff --git a/src/http/processor.h b/src/http/processor.h
index 029c28b..355fc7e 100644
--- a/src/http/processor.h
+++ b/src/http/processor.h
@@ -87,10 +87,11 @@ typedef struct response {
         const char *protocol;
         boolean_t is_committed;
         HttpHeader headers;
         const char *status_msg;
         StringBuffer_T outputbuffer;
+        MD_T token;
         Ssl_T ssl;
 } *HttpResponse;
 
 
 /* Public prototypes */
@@ -100,11 +101,11 @@ void set_status(HttpResponse res, int status);
 const char *get_status_string(int status_code);
 void add_Impl(void(*doGet)(HttpRequest, HttpResponse), void(*doPost)(HttpRequest, HttpResponse));
 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
diff --git a/src/util.c b/src/util.c
index 8749702..6e1fa42 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1383,31 +1383,34 @@ void Util_printServiceList() {
         memset(ruler, '-', STRLEN);
         printf("%-.79s\n", ruler);
 }
 
 
-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)) {
                         LogError("idfile '%s' is not a regular file\n", idfile);
                         return NULL;
diff --git a/src/util.h b/src/util.h
index 07857e5..b75bf0a 100644
--- a/src/util.h
+++ b/src/util.h
@@ -75,10 +75,11 @@ int Util_handle0Escapes(char *buf);
 /**
  * Convert a digest buffer to a char string
  * @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);
 
 
 /**
@@ -163,10 +164,18 @@ void Util_printService(Service_T s);
  * Print all the services in the servicelist
  */
 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.
  * @param idfile An idfile with full path
  * @return the id or NULL

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);