From: Lloyd Subject: httpd(8): patch to disable/modify the server banner To: "tech@openbsd.org" Date: Sun, 26 Jan 2025 20:20:35 +0000 I have created and tested the following patch for httpd(8) which adds two new options to httpd.conf(5): banner which allows you to customize the server name string. [no] disablebanner which allows you to suppress the Server header and output in errdocs entirely e.g.: banner "Microsoft-IIS/5.0" server "foo" { listen on * port 80 disablebanner } server "bar" { listen on * port 80 banner "OpenBSD httpd" } These directives are available both in global and server contexts and you can mix and match (e.g. suppress globally but allow publication on certain virtual servers only). Allowing configuration of the Server header is suggested in RFC 2616. Suppression of the header entirely is preferred by some operators but often difficult to do in most server software without recompiling. This patch also fixes a bug introduced when gzip-static was merged, but the bug only affected debugging output so went unnoticed. Tested on amd64. Regards Lloyd Index: config.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/config.c,v retrieving revision 1.65 diff -u -p -u -p -r1.65 config.c --- config.c 17 Jan 2024 08:22:40 -0000 1.65 +++ config.c 26 Jan 2025 19:55:19 -0000 @@ -54,6 +54,9 @@ config_init(struct httpd *env) (void)strlcpy(env->sc_errdocroot, "", sizeof(env->sc_errdocroot)); + /* Load the default server banner into the config if unconfigured */ + strlcpy(env->sc_banner, HTTPD_SERVERNAME, sizeof(env->sc_banner)); + /* Other configuration */ what = ps->ps_what[privsep_process]; @@ -591,6 +594,10 @@ config_getserver_config(struct httpd *en srv_conf->flags |= parent->flags & SRVFLAG_ERRDOCS; (void)strlcpy(srv_conf->errdocroot, parent->errdocroot, sizeof(srv_conf->errdocroot)); + + srv_conf->flags |= parent->flags & SRVFLAG_NO_BANNER; + strlcpy(srv_conf->banner, parent->banner, + sizeof(srv_conf->banner)); DPRINTF("%s: %s %d location \"%s\", " "parent \"%s[%u]\", flags: %s", Index: httpd.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v retrieving revision 1.125 diff -u -p -u -p -r1.125 httpd.conf.5 --- httpd.conf.5 3 Nov 2023 13:03:02 -0000 1.125 +++ httpd.conf.5 26 Jan 2025 19:55:19 -0000 @@ -105,6 +105,16 @@ server "example.com" { .Sh GLOBAL CONFIGURATION Here are the settings that can be set globally: .Bl -tag -width Ds +.It Ic banner Ar string +Set a custom server software name for +.Xr httpd 8 . +This is the value sent to clients in the +.Ic Server +HTTP response header, the +.Ic SERVER_SOFTWARE +environment variable for CGI scripts, and the +.Ic $SERVER_SOFTWARE +macro used to create custom error documents. .It Ic chroot Ar directory Set the .Xr chroot 2 @@ -121,6 +131,14 @@ see the section below. If not specified, the default type is set to .Ar application/octet-stream . +.It Ic disablebanner +Do not send the +.Ic Server +HTTP response header, and hide the server software name in error documents. +The +.Ic SERVER_SOFTWARE +CGI environment variable is always set in accordance with +.Pa RFC 3875 . .It Ic errdocs Ar directory Let .Xr httpd 8 @@ -227,6 +245,12 @@ and must be readable by the www user. Use the .Ic no authenticate directive to disable authentication in a location. +.It Ic banner Ar string +Set a custom +.Xr httpd 8 +software name for the current +.Ic server , +overriding the global setting if set. .It Ic block drop Drop the connection without sending an error page. .It Ic block Op Ic return Ar code Op Ar uri @@ -323,6 +347,13 @@ Disable the directory index. .Xr httpd 8 will neither display nor generate a directory index. .El +.It Oo Ic no Oc Ic disablebanner +Suppresses the server software name in HTTP headers and error documents +for the current +.Ic server. +The +.Ic no +keyword can be used to override the global setting. .It Oo Ic no Oc Ic errdocs Ar directory Overrides or, if the .Ic no Index: httpd.h =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v retrieving revision 1.165 diff -u -p -u -p -r1.165 httpd.h --- httpd.h 8 Oct 2024 05:28:11 -0000 1.165 +++ httpd.h 26 Jan 2025 19:55:19 -0000 @@ -46,6 +46,7 @@ #define CONF_FILE "/etc/httpd.conf" #define HTTPD_USER "www" #define HTTPD_SERVERNAME "OpenBSD httpd" +#define HTTPD_SERVERNAME_MAX 64 #define HTTPD_DOCROOT "/htdocs" #define HTTPD_ERRDOCTEMPLATE "err" /* 3-char name */ #define HTTPD_ERRDOCROOT_MAX (PATH_MAX - sizeof("000.html")) @@ -389,6 +390,7 @@ SPLAY_HEAD(client_tree, client); #define SRVFLAG_PATH_REWRITE 0x01000000 #define SRVFLAG_NO_PATH_REWRITE 0x02000000 #define SRVFLAG_GZIP_STATIC 0x04000000 +#define SRVFLAG_NO_BANNER 0x08000000 #define SRVFLAG_LOCATION_FOUND 0x40000000 #define SRVFLAG_LOCATION_NOT_FOUND 0x80000000 @@ -398,7 +400,7 @@ SPLAY_HEAD(client_tree, client); "\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG" \ "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK\25LOCATION_MATCH" \ "\26SERVER_MATCH\27SERVER_HSTS\30DEFAULT_TYPE\31PATH\32NO_PATH" \ - "\37LOCATION_FOUND\40LOCATION_NOT_FOUND" + "\33GZIP_STATIC\34NO_BANNER\37LOCATION_FOUND\40LOCATION_NOT_FOUND" #define TCPFLAG_NODELAY 0x01 #define TCPFLAG_NNODELAY 0x02 @@ -539,6 +541,7 @@ struct server_config { struct server_fcgiparams fcgiparams; int fcgistrip; char errdocroot[HTTPD_ERRDOCROOT_MAX]; + char banner[HTTPD_SERVERNAME_MAX]; TAILQ_ENTRY(server_config) entry; }; @@ -600,6 +603,7 @@ struct httpd { int sc_custom_errdocs; char sc_errdocroot[HTTPD_ERRDOCROOT_MAX]; + char sc_banner[HTTPD_SERVERNAME_MAX]; }; #define HTTPD_OPT_VERBOSE 0x01 Index: parse.y =================================================================== RCS file: /cvs/src/usr.sbin/httpd/parse.y,v retrieving revision 1.128 diff -u -p -u -p -r1.128 parse.y --- parse.y 27 Feb 2022 20:30:30 -0000 1.128 +++ parse.y 26 Jan 2025 19:55:19 -0000 @@ -141,7 +141,7 @@ typedef struct { %token TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD REQUEST %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS REWRITE %token CA CLIENT CRL OPTIONAL PARAM FORWARDED FOUND NOT -%token ERRDOCS GZIPSTATIC +%token ERRDOCS GZIPSTATIC BANNER DISABLEBANNER %token STRING %token NUMBER %type port @@ -210,9 +210,16 @@ main : PREFORK NUMBER { } conf->sc_prefork_server = $2; } + | BANNER STRING { + strlcpy(conf->sc_banner, $2, sizeof(conf->sc_banner)); + free($2); + } | CHROOT STRING { conf->sc_chroot = $2; } + | DISABLEBANNER { + conf->sc_flags |= SRVFLAG_NO_BANNER; + } | ERRDOCS STRING { if ($2 != NULL && strlcpy(conf->sc_errdocroot, $2, sizeof(conf->sc_errdocroot)) >= @@ -300,6 +307,12 @@ server : SERVER optmatch STRING { s->srv_conf.hsts_max_age = SERVER_HSTS_DEFAULT_AGE; + strlcpy(s->srv_conf.banner, conf->sc_banner, + sizeof(s->srv_conf.banner)); + + if (conf->sc_flags & SRVFLAG_NO_BANNER) + s->srv_conf.flags |= SRVFLAG_NO_BANNER; + (void)strlcpy(s->srv_conf.errdocroot, conf->sc_errdocroot, sizeof(s->srv_conf.errdocroot)); @@ -495,6 +508,11 @@ serveroptsl : LISTEN ON STRING opttls po TAILQ_INSERT_TAIL(&srv->srv_hosts, alias, entry); } + | BANNER STRING { + strlcpy(srv->srv_conf.banner, $2, + sizeof(srv->srv_conf.banner)); + free($2); + } | ERRDOCS STRING { if (parentsrv != NULL) { yyerror("errdocs inside location"); @@ -550,6 +568,7 @@ serveroptsl : LISTEN ON STRING opttls po | request | root | directory + | disablebanner | logformat | fastcgi | authenticate @@ -687,6 +706,22 @@ serveroptsl : LISTEN ON STRING opttls po } ; +disablebanner : NO DISABLEBANNER { + if (parentsrv != NULL) { + yyerror("disablebanner inside location"); + YYERROR; + } + srv->srv_conf.flags &= ~SRVFLAG_NO_BANNER; + } + | DISABLEBANNER { + if (parentsrv != NULL) { + yyerror("disablebanner inside location"); + YYERROR; + } + srv->srv_conf.flags |= SRVFLAG_NO_BANNER; + } + ; + optfound : /* empty */ { $$ = 0; } | FOUND { $$ = 1; } | NOT FOUND { $$ = -1; } @@ -1428,6 +1463,7 @@ lookup(char *s) { "authenticate", AUTHENTICATE}, { "auto", AUTO }, { "backlog", BACKLOG }, + { "banner", BANNER }, { "block", BLOCK }, { "body", BODY }, { "buffer", BUFFER }, @@ -1443,6 +1479,7 @@ lookup(char *s) { "default", DEFAULT }, { "dhe", DHE }, { "directory", DIRECTORY }, + { "disablebanner", DISABLEBANNER }, { "drop", DROP }, { "ecdhe", ECDHE }, { "errdocs", ERRDOCS }, Index: server_fcgi.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/server_fcgi.c,v retrieving revision 1.97 diff -u -p -u -p -r1.97 server_fcgi.c --- server_fcgi.c 8 Nov 2023 19:19:10 -0000 1.97 +++ server_fcgi.c 26 Jan 2025 19:55:19 -0000 @@ -345,7 +345,8 @@ server_fcgi(struct httpd *env, struct cl goto fail; } - if (fcgi_add_param(¶m, "SERVER_SOFTWARE", HTTPD_SERVERNAME, + /* RFC 3875 requires this variable always be present */ + if (fcgi_add_param(¶m, "SERVER_SOFTWARE", srv_conf->banner, clt) == -1) { errstr = "failed to encode param"; goto fail; @@ -659,9 +660,12 @@ server_fcgi_header(struct client *clt, u kv_set(&resp->http_pathquery, "%s", error) == -1) return (-1); - /* Add headers */ - if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL) - return (-1); + /* Add server banner header to response unless suppressed */ + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0) { + if (kv_add(&resp->http_headers, "Server", + srv_conf->banner) == NULL) + return (-1); + } if (clt->clt_fcgi.type == FCGI_END_REQUEST || EVBUFFER_LENGTH(clt->clt_srvevb) == 0) { Index: server_http.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v retrieving revision 1.155 diff -u -p -u -p -r1.155 server_http.c --- server_http.c 22 Dec 2024 13:51:42 -0000 1.155 +++ server_http.c 26 Jan 2025 19:55:19 -0000 @@ -890,6 +890,7 @@ server_abort_http(struct client *clt, un char *httpmsg, *body = NULL, *extraheader = NULL; char tmbuf[32], hbuf[128], *hstsheader = NULL; char *clenheader = NULL; + char *bannerheader = NULL, *bannertoken = NULL; char buf[IBUF_READ_SIZE]; char *escapedmsg = NULL; char cstr[5]; @@ -981,7 +982,11 @@ server_abort_http(struct client *clt, un body = replace_var(body, "$HTTP_ERROR", httperr); body = replace_var(body, "$RESPONSE_CODE", cstr); - body = replace_var(body, "$SERVER_SOFTWARE", HTTPD_SERVERNAME); + /* Check if server banner is suppressed */ + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0) + body = replace_var(body, "$SERVER_SOFTWARE", srv_conf->banner); + else + body = replace_var(body, "$SERVER_SOFTWARE", ""); bodylen = strlen(body); goto send; @@ -994,6 +999,11 @@ server_abort_http(struct client *clt, un "body { background-color: #1E1F21; color: #EEEFF1; }\n" "a { color: #BAD7FF; }\n}"; + /* If banner is suppressed, don't write it to the error document */ + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0) + asprintf(&bannertoken, "
\n
%s
\n", + srv_conf->banner); + /* Generate simple HTML error document */ if ((bodylen = asprintf(&body, "\n" @@ -1005,10 +1015,11 @@ server_abort_http(struct client *clt, un "\n" "\n" "

%03d %s

\n" - "
\n
%s
\n" + "%s" "\n" "\n", - code, httperr, style, code, httperr, HTTPD_SERVERNAME)) == -1) { + code, httperr, style, code, httperr, + bannertoken == NULL ? "" : bannertoken)) == -1) { body = NULL; goto done; } @@ -1037,11 +1048,16 @@ server_abort_http(struct client *clt, un } } + /* If banner is suppressed, don't write a Server header */ + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0) + asprintf(&bannerheader, "Server: %s\r\n", + srv_conf->banner); + /* Add basic HTTP headers */ if (asprintf(&httpmsg, "HTTP/1.0 %03d %s\r\n" "Date: %s\r\n" - "Server: %s\r\n" + "%s" "Connection: close\r\n" "Content-Type: text/html\r\n" "%s" @@ -1049,7 +1065,8 @@ server_abort_http(struct client *clt, un "%s" "\r\n" "%s", - code, httperr, tmbuf, HTTPD_SERVERNAME, + code, httperr, tmbuf, + bannerheader == NULL ? "" : bannerheader, clenheader == NULL ? "" : clenheader, extraheader == NULL ? "" : extraheader, hstsheader == NULL ? "" : hstsheader, @@ -1558,10 +1575,12 @@ server_response_http(struct client *clt, kv_set(&resp->http_pathquery, "%s", error) == -1) return (-1); - /* Add headers */ - if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL) - return (-1); - + /* Add server banner header to response unless suppressed */ + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0) { + if (kv_add(&resp->http_headers, "Server", + srv_conf->banner) == NULL) + return (-1); + } /* Is it a persistent connection? */ if (clt->clt_persist) { if (kv_add(&resp->http_headers,