diff --git a/src/bw/lua/bunkerweb/helpers.lua b/src/bw/lua/bunkerweb/helpers.lua index 632e7315d..5d29da4a9 100644 --- a/src/bw/lua/bunkerweb/helpers.lua +++ b/src/bw/lua/bunkerweb/helpers.lua @@ -263,18 +263,21 @@ function helpers.load_variables(all_variables, plugins) end end for setting, data in pairs(all_settings) do + local escaped_setting = setting:gsub("([^%w])", "%%%1") if all_variables[setting] then variables["global"][setting] = all_variables[setting] end if data.multiple then for variable, value in pairs(all_variables) do - local multiple_setting = variable:match("^(" .. setting .. "_%d+)$") + local multiple_setting = variable:match("^(" .. escaped_setting .. "_%d+)$") if multiple_setting then variables["global"][multiple_setting] = value end if multisite then for _, server_name in ipairs(server_names) do - multiple_setting = variable:match("^" .. server_name .. "_(" .. setting .. "_%d+)$") + local escaped_server_name = server_name:gsub("([^%w])", "%%%1") + multiple_setting = + variable:match("^" .. escaped_server_name .. "_(" .. escaped_setting .. "_%d+)$") if multiple_setting then variables[server_name][multiple_setting] = value end diff --git a/src/common/confs/default-server-http.conf b/src/common/confs/default-server-http.conf index 4048c3f49..768c7951e 100644 --- a/src/common/confs/default-server-http.conf +++ b/src/common/confs/default-server-http.conf @@ -74,7 +74,7 @@ server { .. nonce_script .. "'; style-src 'nonce-" .. nonce_style - .. "'; frame-ancestors 'none'; base-uri 'none'; img-src 'self' data:; font-src 'self' data:; require-trusted-types-for 'script';" + .. "'; frame-ancestors 'none'; base-uri 'none'; img-src 'self' data:; font-src 'self' data:; require-trusted-types-for 'script'; block-all-mixed-content; upgrade-insecure-requests;" -- Remove server header ngx.header["Server"] = nil @@ -84,6 +84,12 @@ server { ngx.header["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload" end + -- Override X-Content-Type-Options header + ngx.header["X-Content-Type-Options"] = "nosniff" + + -- Override Referrer-Policy header + ngx.header["Referrer-Policy"] = "no-referrer" + -- Render template render("index.html", { nonce_style = nonce_style, diff --git a/src/common/core/antibot/antibot.lua b/src/common/core/antibot/antibot.lua index b8e941ae0..dcdddb7a3 100644 --- a/src/common/core/antibot/antibot.lua +++ b/src/common/core/antibot/antibot.lua @@ -90,6 +90,7 @@ function antibot:header() for directive, value in pairs(csp_directives) do csp_content = csp_content .. directive .. " " .. value .. "; " end + csp_content = csp_content .. "block-all-mixed-content; upgrade-insecure-requests;" ngx.header["Content-Security-Policy"] = csp_content return self:ret(true, "successfully overridden CSP header") end diff --git a/src/common/core/errors/errors.lua b/src/common/core/errors/errors.lua index d46cf0b48..a3bd80d94 100644 --- a/src/common/core/errors/errors.lua +++ b/src/common/core/errors/errors.lua @@ -96,7 +96,7 @@ function errors:render_template(code) .. "'; style-src 'nonce-" .. nonce_style --luacheck: ignore 631 - .. "'; frame-ancestors 'none'; base-uri 'none'; img-src 'self' data:; font-src 'self' data:; require-trusted-types-for 'script';" + .. "'; frame-ancestors 'none'; base-uri 'none'; img-src 'self' data:; font-src 'self' data:; require-trusted-types-for 'script'; block-all-mixed-content; upgrade-insecure-requests;" -- Remove server header ngx.header["Server"] = nil @@ -114,6 +114,12 @@ function errors:render_template(code) ngx.header["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload" end + -- Override X-Content-Type-Options header + ngx.header["X-Content-Type-Options"] = "nosniff" + + -- Override Referrer-Policy header + ngx.header["Referrer-Policy"] = "no-referrer" + -- Render template render("error.html", { title = code .. " - " .. self.default_errors[code].title, diff --git a/src/common/core/misc/confs/default-server-http/page.conf b/src/common/core/misc/confs/default-server-http/page.conf index 9c2f29d1b..e810fa767 100644 --- a/src/common/core/misc/confs/default-server-http/page.conf +++ b/src/common/core/misc/confs/default-server-http/page.conf @@ -22,7 +22,7 @@ location / { -- Override CSP header ngx.header["Content-Security-Policy"] = "default-src 'none'; frame-ancestors 'none'; form-action 'self'; img-src 'self' data:; style-src 'self' 'nonce-" .. nonce_style - .. "'; font-src 'self' data:; base-uri 'self'; require-trusted-types-for 'script';" + .. "'; font-src 'self' data:; base-uri 'self'; require-trusted-types-for 'script'; block-all-mixed-content; upgrade-insecure-requests;" -- Remove server header ngx.header["Server"] = nil @@ -32,6 +32,12 @@ location / { ngx.header["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload" end + -- Override X-Content-Type-Options header + ngx.header["X-Content-Type-Options"] = "nosniff" + + -- Override Referrer-Policy header + ngx.header["Referrer-Policy"] = "no-referrer" + -- Render template render("default.html", { nonce_style = nonce_style, diff --git a/src/common/core/ui/confs/default-server-http/ui.conf b/src/common/core/ui/confs/default-server-http/ui.conf index ae933eb80..f5547b9fa 100644 --- a/src/common/core/ui/confs/default-server-http/ui.conf +++ b/src/common/core/ui/confs/default-server-http/ui.conf @@ -36,19 +36,11 @@ location /setup/check { add_header 'Access-Control-Allow-Methods' 'GET' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; - default_type 'text/plain'; + default_type 'text/plain; charset=utf-8'; content_by_lua_block { - -- Override CSP header - ngx.header["Content-Security-Policy"] = "default-src 'none'; img-src 'self'; require-trusted-types-for 'script';" - -- Remove server header ngx.header["Server"] = nil - -- Override HSTS header - if ngx.var.scheme == "https" then - ngx.header["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload" - end - local logger = require "bunkerweb.logger":new("UI") local args, err = ngx.req.get_uri_args(1) if err == "truncated" or not args["server_name"] or args["server_name"] == "" then diff --git a/src/linux/bunkerweb.logrotate b/src/linux/bunkerweb.logrotate index 67adb0b4a..3fa38a47d 100644 --- a/src/linux/bunkerweb.logrotate +++ b/src/linux/bunkerweb.logrotate @@ -1,10 +1,12 @@ -/var/log/bunkerweb/*.log +/var/log/bunkerweb/*.log /var/log/bunkerweb/*.log.* { su nginx nginx daily + size 100M rotate 7 copytruncate compress + delaycompress missingok notifempty dateext diff --git a/src/linux/scripts/start.sh b/src/linux/scripts/start.sh index 4165f4c9f..11d2c3e4c 100644 --- a/src/linux/scripts/start.sh +++ b/src/linux/scripts/start.sh @@ -106,6 +106,18 @@ function start() { chown nginx:nginx /var/run/bunkerweb fi + # Create TMP folder + if [ ! -f /var/tmp/bunkerweb ] ; then + mkdir -p /var/tmp/bunkerweb + chown nginx:nginx /var/tmp/bunkerweb + fi + + # Create LOG folder + if [ ! -f /var/log/bunkerweb ] ; then + mkdir -p /var/log/bunkerweb + chown nginx:nginx /var/log/bunkerweb + fi + # Stop scheduler if it's running stop_scheduler diff --git a/src/ui/main.py b/src/ui/main.py index 8c6f01722..4a76de4a0 100644 --- a/src/ui/main.py +++ b/src/ui/main.py @@ -403,8 +403,9 @@ def inject_variables(): @app.after_request -def set_csp_header(response): - """Set the Content-Security-Policy header to prevent XSS attacks.""" +def set_security_headers(response): + """Set the security headers.""" + # * Content-Security-Policy header to prevent XSS attacks response.headers["Content-Security-Policy"] = ( "object-src 'none';" + " frame-ancestors 'self';" @@ -414,9 +415,26 @@ def set_csp_header(response): + " img-src 'self' data: https://assets.bunkerity.com;" + " font-src 'self' data:;" + " base-uri 'self';" + + " block-all-mixed-content;" + (" connect-src *;" if request.path.startswith(("/check", "/setup")) else "") ) + if request.headers.get("X-Forwarded-Proto") == "https": + if not request.path.startswith("/setup/loading"): + response.headers["Content-Security-Policy"] += " upgrade-insecure-requests;" + + # * Strict-Transport-Security header to force HTTPS if accessed via a reverse proxy + response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains; preload" + + # * X-Frames-Options header to prevent clickjacking + response.headers["X-Frame-Options"] = "DENY" + + # * X-Content-Type-Options header to prevent MIME sniffing + response.headers["X-Content-Type-Options"] = "nosniff" + + # * Referrer-Policy header to prevent leaking of sensitive data + response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" + return response @@ -615,6 +633,7 @@ def setup(): "REVERSE_PROXY_URL": request.form["ui_url"] or "/", "INTERCEPTED_ERROR_CODES": "400 404 405 413 429 500 501 502 503 504", "MAX_CLIENT_SIZE": "50m", + "KEEP_UPSTREAM_HEADERS": "Content-Security-Policy Strict-Transport-Security X-Frame-Options X-Content-Type-Options Referrer-Policy", } if request.form.get("auto_lets_encrypt", "no") == "yes":