diff --git a/Dockerfile b/Dockerfile index 676bddc..e999c5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,6 +40,7 @@ COPY ./configs/phpinfo.php /var/www/html/ COPY ./configs/mariadb.repo /etc/yum.repos.d/ COPY ./configs/index.php /var/www/html/ COPY ./configs/remote_ip.conf /etc/httpd/conf.d/ +COPY ./configs/whp-security.conf /etc/httpd/conf.d/ # Set up cron job in a single layer RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab diff --git a/configs/litespeed/site-template.tpl b/configs/litespeed/site-template.tpl index b508e54..5db442d 100644 --- a/configs/litespeed/site-template.tpl +++ b/configs/litespeed/site-template.tpl @@ -72,6 +72,18 @@ virtualHostConfig { addDefaultCharset off } + ## SECURITY: never serve config / VCS dotfiles over HTTP. Same named list as + ## WHP's shared-ols vhconf template (first `context exp:` there) and + ## shared-httpd's 000-whp-security.conf. Measured on .148: this tier served + ## /.env even at mode 0640, because OLS runs as the customer uid here. An + ## `exp:` regex context wins over the `context /` prefix context regardless + ## of order (measured on OLS 1.8.4 for shared-ols), so a customer .htaccess + ## cannot reopen it. Named, not blanket "^\.": /.well-known/ (ACME, + ## security.txt, app-site-association) must stay reachable. + context exp:^/(.*/)?\.(user\.ini|env|git|svn|hg|bzr|ht|whp|site-builder) { + allowBrowse 0 + } + rewrite { enable 1 autoLoadHtaccess 1 diff --git a/configs/whp-security.conf b/configs/whp-security.conf new file mode 100644 index 0000000..b492680 --- /dev/null +++ b/configs/whp-security.conf @@ -0,0 +1,29 @@ +## SECURITY: never serve config / VCS dotfiles over HTTP (standalone cac). +## +## Same named list as WHP's shared-httpd drop-in (web-files/configs/ +## shared-httpd-security.conf, 000-whp-security.conf on every shared-httpd host), +## re-rooted from /mnt/users to /home because here the docroot is +## /home//public_html. Standalone cac never got that drop-in, and measured +## on .148 it served /.env with contents. +## +## matches the FILENAME resolved from the filesystem path, not the +## request URL, so it cannot be side-stepped by URL-encoding. httpd applies +## sections (and .htaccess) first and / after +## them, so this deny wins over create-vhost.sh's `Require all granted`. +## +## A NAMED LIST, NOT A BLANKET "^\." DENY: /.well-known/ (ACME, security.txt, +## apple-app-site-association, assetlinks.json, payment-domain verification) +## must stay reachable. FilesMatch tests the final path component, and for +## /.well-known/acme-challenge/ that is , not a dotfile. +## +## HTTP only: PHP still reads .user.ini and .env off the filesystem as before. + + + Require all denied + + +## Directory form: reaches the files INSIDE .git/ etc. (e.g. /.git/config, +## whose own name is not a dotfile). + + Require all denied +