--- a/lib/cgi.c +++ b/lib/cgi.c @@ -252,6 +252,71 @@ cgidata_t *cgi_request(void) return head; } +char *csp_header(const char *str) +{ + char *csppol = NULL; + char *returnstr = NULL; + + if (getenv("XYMON_NOCSPHEADER")) return NULL; + + if (strncmp(str, "enadis", 6) == 0) csppol = strdup("script-src 'self' 'unsafe-inline'; connect-src 'self'; form-action 'self'; sandbox allow-forms allow-scripts;"); + else if (strncmp(str, "useradm", 7) == 0) csppol = strdup("script-src 'self'; connect-src 'self'; form-action 'self';"); + else if (strncmp(str, "chpasswd", 8) == 0) csppol = strdup("script-src 'self'; connect-src 'self'; form-action 'self';"); + else if (strncmp(str, "ackinfo", 7) == 0) csppol = strdup("script-src 'self'; connect-src 'self'; form-action 'self';"); + else if (strncmp(str, "acknowledge", 11) == 0) csppol = strdup("script-src 'self'; connect-src 'self'; form-action 'self';"); + else if (strncmp(str, "criticaleditor", 14) == 0) csppol = strdup("script-src 'self'; connect-src 'self'; form-action 'self';"); + else if (strncmp(str, "svcstatus", 9) == 0) csppol = strdup("script-src 'self'; connect-src 'self'; form-action 'self'; sandbox allow-forms;"); + else if (strncmp(str, "historylog", 10) == 0) csppol = strdup("script-src 'self'; connect-src 'self'; form-action 'self'; sandbox allow-forms;"); + else { + errprintf(" csp_header: page %s not listed, no CSP returned\n", str); + } + if ((!csppol) || (*csppol == '\0')) return NULL; + returnstr = (char *)malloc(3 * strlen(csppol) + 512); + snprintf(returnstr, (3 * strlen(csppol) + 512), "Content-Security-Policy: %s\nX-Content-Security-Policy: %s\nX-Webkit-CSP: %s\n", csppol, csppol, csppol); + dbgprintf("CSP return is %s", returnstr); + return returnstr; +} + +int cgi_refererok(char *expected) +{ + static char cgi_checkstr[1024]; + int isok = 0; + char *p, *httphost; + + p = getenv("HTTP_REFERER"); + dbgprintf(" - checking if referer is OK (http_referer: %s, http_host: %s, xymonwebhost: %s, checkstr: %s\n", textornull(p), textornull(getenv("HTTP_HOST")), textornull(xgetenv("XYMONWEBHOST")), textornull(expected)); + if (!p) return 0; + + /* If passed NULL, just check that there _is_ a REFERER */ + if (!expected) return 1; + + httphost = getenv("HTTP_HOST"); + if (!httphost) { + if (strcmp(xgetenv("XYMONWEBHOST"), "http://localhost") != 0) { + /* If XYMONWEBHOST is set by the admin, use that */ + snprintf(cgi_checkstr, sizeof(cgi_checkstr), "%s%s", getenv("XYMONWEBHOST"), expected); + if (strncmp(p, cgi_checkstr, strlen(cgi_checkstr)) == 0) isok = 1; + } + else { + errprintf("Disallowed request due to missing HTTP_HOST variable\n"); + return 0; + } + } + else { + /* skip the protocol specifier, which HTTP_REFERER has but HTTP_HOST doesn't */ + p += (strncasecmp(p, "https://", 8) == 0) ? 8 : (strncasecmp(p, "http://", 7) == 0) ? 7 : 0; + + if (*p == '\0') { errprintf("Disallowed request due to unexpected referer '%s'\n", getenv("HTTP_REFERER")); return 0; } + + snprintf(cgi_checkstr, sizeof(cgi_checkstr), "%s%s", httphost, expected); + if (strncmp(p, cgi_checkstr, strlen(cgi_checkstr)) == 0) isok = 1; + } + + if (!isok) errprintf("Disallowed request due to unexpected referer '%s', wanted '%s' (originally '%s')\n", p, cgi_checkstr, expected); + + return isok; +} + char *get_cookie(char *cookiename) { static char *ckdata = NULL; --- a/lib/cgi.h +++ b/lib/cgi.h @@ -23,6 +23,8 @@ extern enum cgi_method_t cgi_method; extern char *cgi_error(void); extern cgidata_t *cgi_request(void); +extern char *csp_header(const char *pagename); +extern int cgi_refererok(char *expected); extern char *get_cookie(char *cookiename); #endif --- a/web/criticaleditor.c +++ b/web/criticaleditor.c @@ -389,6 +389,18 @@ int main(int argc, char *argv[]) } redirect_cgilog("criticaleditor"); + + /* We only want to accept posts from certain pages */ + { + char cgisource[1024]; char *p; + p = csp_header("criticaleditor"); if (p) fprintf(stdout, "%s", p); + snprintf(cgisource, sizeof(cgisource), "%s/%s", xgetenv("SECURECGIBINURL"), "criticaleditor"); + if (!cgi_refererok(cgisource)) { + fprintf(stdout, "Location: %s.sh?\n\n", cgisource); + return 0; + } + } + parse_query(); load_critconfig(configfn); --- a/web/acknowledge.c +++ b/web/acknowledge.c @@ -359,6 +359,18 @@ int main(int argc, char *argv[]) strbuffer_t *response = newstrbuffer(0); int count = 0; + + /* We only want to accept posts from certain pages */ + { + char cgisource[1024]; char *p; + p = csp_header("acknowledge"); if (p) fprintf(stdout, "%s", p); + snprintf(cgisource, sizeof(cgisource), "%s/%s", xgetenv("SECURECGIBINURL"), "acknowledge"); + if (!cgi_refererok(cgisource)) { + fprintf(stdout, "Location: %s.sh?\n\n", cgisource); + return 0; + } + } + parse_query(); if (getenv("REMOTE_USER")) { char *remaddr = getenv("REMOTE_ADDR"); --- a/web/useradm.c +++ b/web/useradm.c @@ -75,6 +75,14 @@ int parse_query(void) cwalk = cwalk->next; } + /* We only want to accept posts from certain pages */ + if (returnval != ACT_NONE) { + char cgisource[1024]; char *p; + p = csp_header("useradm"); if (p) fprintf(stdout, "%s", p); + snprintf(cgisource, sizeof(cgisource), "%s/%s", xgetenv("SECURECGIBINURL"), "useradm"); + if (!cgi_refererok(cgisource)) { fprintf(stdout, "Location: %s.sh?\n\n", cgisource); return 0; } + } + return returnval; } --- a/web/enadis.c +++ b/web/enadis.c @@ -68,6 +68,23 @@ void parse_cgi(void) postdata = cgi_request(); if (cgi_method == CGI_GET) return; + + /* We only want to accept posts from certain pages: svcstatus (for info), and ourselves */ + /* At some point in the future, moving info lookups to their own page would be a good idea */ + { + char cgisource[1024]; char *p; + p = csp_header("enadis"); if (p) fprintf(stdout, "%s", p); + snprintf(cgisource, sizeof(cgisource), "%s/%s", xgetenv("SECURECGIBINURL"), "enadis"); + if (!cgi_refererok(cgisource)) { + snprintf(cgisource, sizeof(cgisource), "%s/%s", xgetenv("CGIBINURL"), "svcstatus"); + if (!cgi_refererok(cgisource)) { + dbgprintf("Not coming from self or svcstatus; abort\n"); + return; /* Just display, don't do anything */ + } + } + } + + if (!postdata) { errormsg(cgi_error()); } --- a/web/ackinfo.c +++ b/web/ackinfo.c @@ -92,6 +92,18 @@ int main(int argc, char *argv[]) } redirect_cgilog("ackinfo"); + + /* We only want to accept posts from certain pages */ + { + char cgisource[1024]; char *p; + p = csp_header("ackinfo"); if (p) fprintf(stdout, "%s", p); + snprintf(cgisource, sizeof(cgisource), "%s/%s", xgetenv("SECURECGIBINURL"), "criticalview"); + if (!cgi_refererok(cgisource)) { + fprintf(stdout, "Location: %s.sh?\n\n", cgisource); + return 0; + } + } + parse_query(); if (hostname && *hostname && testname && *testname && ((level == 0) || (validity>0)) && ackmsg && *ackmsg) { --- a/web/svcstatus.c +++ b/web/svcstatus.c @@ -748,6 +748,8 @@ int main(int argc, char *argv[]) } redirect_cgilog("svcstatus"); + fprintf(stdout, "%s", csp_header("svcstatus")); + fprintf(stdout, "Refresh: 30\n"); *errortxt = '\0'; hostname = service = tstamp = NULL;