diff --git a/.grype.yaml b/.grype.yaml index 23614b6..04b4903 100644 --- a/.grype.yaml +++ b/.grype.yaml @@ -22,3 +22,18 @@ ignore: package: name: curl type: binary + # Same binary-classifier false positive as above, newly published CVE IDs. + # Verified against simplerisk-minimal (php 8.3/8.4/8.5): the only "curl" + # match is /usr/local/lib/php/extensions/.../curl.so reporting the PHP + # version (e.g. 8.5.10) as its own. The real Debian curl package + # (8.14.1-2+deb13u5) is also affected but Debian's tracker marks both IDs + # "wont-fix", so --only-fixed already excludes that (legitimate) match on + # its own; only the misclassified binary match needs ignoring here. + - vulnerability: CVE-2026-19931 + package: + name: curl + type: binary + - vulnerability: CVE-2026-18924 + package: + name: curl + type: binary diff --git a/simplerisk-minimal/Dockerfile b/simplerisk-minimal/Dockerfile index a1082fa..30f1e36 100644 --- a/simplerisk-minimal/Dockerfile +++ b/simplerisk-minimal/Dockerfile @@ -99,18 +99,15 @@ RUN echo 'upload_max_filesize = 5M' >> /usr/local/etc/php/conf.d/docker-php-uplo echo 'log_errors = On' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \ echo 'error_log = /dev/stderr' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \ echo 'display_errors = Off' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \ -# Create SSL Certificates for Apache SSL - mkdir -p /etc/apache2/ssl/ca /etc/apache2/ssl/simplerisk && \ -# Generate CA - openssl genrsa -out /etc/apache2/ssl/ca/ca.key 4096 && \ - openssl req -x509 -new -nodes -key /etc/apache2/ssl/ca/ca.key -sha256 -days 3650 -out /etc/apache2/ssl/ca/ca.crt -subj "/CN=SimpleRisk CA" && \ -# Generate certs - openssl genrsa -out /etc/apache2/ssl/simplerisk/simplerisk.key 2048 && \ - openssl req -new -key /etc/apache2/ssl/simplerisk/simplerisk.key -out /etc/apache2/ssl/simplerisk/simplerisk.csr -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:simplerisk,IP:127.0.0.1,IP:0.0.0.0" && \ - openssl x509 -req -days 365 -in /etc/apache2/ssl/simplerisk/simplerisk.csr -CA /etc/apache2/ssl/ca/ca.crt -CAkey /etc/apache2/ssl/ca/ca.key -CAcreateserial -out /etc/apache2/ssl/simplerisk/simplerisk.crt -copy_extensions copyall && \ - cp /etc/apache2/ssl/ca/ca.crt /usr/local/share/ca-certificates/simplerisk.crt && \ - chmod 644 /usr/local/share/ca-certificates/simplerisk.crt && \ - update-ca-certificates && \ +# SSL certificate directory for Apache. The actual key pair is generated at +# container startup by entrypoint.sh (see set_ssl_certificate), not here at +# build time: a key baked into this RUN would be identical in every pulled +# copy of the image and, combined with registering it as a trusted CA, would +# let anyone who pulls the image forge certs the container trusts (HackerOne +# #3764027). No custom CA is created or installed into the system trust +# store -- Apache's cert is self-signed directly, matching the simplerisk +# (non-minimal) image. + mkdir -p /etc/apache2/ssl/simplerisk && \ # Activate Apache modules a2enmod headers rewrite ssl && \ a2enconf security && \ diff --git a/simplerisk-minimal/common/entrypoint.sh b/simplerisk-minimal/common/entrypoint.sh index ee6c210..cf43d40 100644 --- a/simplerisk-minimal/common/entrypoint.sh +++ b/simplerisk-minimal/common/entrypoint.sh @@ -202,6 +202,33 @@ set_csrf_secret(){ [ -n "${SIMPLERISK_CSRF_SECRET:-}" ] && echo "" > "$CSRF_SECRET_PATH"; } +set_ssl_certificate(){ + # Generate Apache's self-signed TLS key pair at container startup rather + # than at image build time. A key baked into the image (the previous + # behavior) is identical in every copy of the image anyone pulls, and was + # additionally being registered as a trusted CA in the container's system + # trust store -- anyone who extracted that key could forge certs the + # container's own outbound curl calls would accept (HackerOne #3764027). + # Generating here means each deployment gets its own key, and idempotence + # (skip if already present) means an existing cert persisted in the + # /etc/apache2/ssl volume survives container restarts/recreation. + local ssl_dir='/etc/apache2/ssl/simplerisk' + local key="$ssl_dir/simplerisk.key" + local crt="$ssl_dir/simplerisk.crt" + + if [ -f "$key" ] && [ -f "$crt" ]; then + return 0 + fi + + print_log "ssl_setup:info" "No Apache TLS certificate found; generating a self-signed one for this container." + exec_cmd "mkdir -p $ssl_dir" "Failed to create SSL certificate directory. Exiting." + # basicConstraints=CA:FALSE is explicit because openssl's own default + # x509 extensions otherwise mark a self-signed leaf cert as CA:TRUE, + # which Apache logs a warning about on every start. + exec_cmd "openssl req -x509 -newkey rsa:2048 -nodes -keyout $key -out $crt -days 365 -subj '/CN=localhost' -addext 'subjectAltName=DNS:localhost,DNS:simplerisk,IP:127.0.0.1,IP:0.0.0.0' -addext 'basicConstraints=critical,CA:FALSE' -addext 'keyUsage=critical,digitalSignature,keyEncipherment' -addext 'extendedKeyUsage=serverAuth'" "Failed to generate self-signed TLS certificate. Exiting." + exec_cmd "chmod 600 $key" "Failed to set permissions on TLS private key. Exiting." +} + set_cron(){ # If SIMPLERISK_CRON_SETUP was passed and it is set to disabled if [[ -n "${SIMPLERISK_CRON_SETUP:-}" && "${SIMPLERISK_CRON_SETUP:-}" = disabled* ]]; then @@ -443,6 +470,8 @@ unset_variables() { } _main() { + set_ssl_certificate + # Detect whether the operator has opted into Docker-managed config # provisioning. If no DB env vars are set, leave config.php absent so # SimpleRisk's web installer runs on first request. diff --git a/simplerisk-minimal/common/etc/apache2/sites-enabled/default-ssl.conf b/simplerisk-minimal/common/etc/apache2/sites-enabled/default-ssl.conf index 0b55c84..45dbc28 100644 --- a/simplerisk-minimal/common/etc/apache2/sites-enabled/default-ssl.conf +++ b/simplerisk-minimal/common/etc/apache2/sites-enabled/default-ssl.conf @@ -11,7 +11,6 @@ SSLStrictSNIVHostCheck Off Options -Indexes SSLEngine on - SSLCACertificateFile /etc/apache2/ssl/ca/ca.crt SSLCertificateFile /etc/apache2/ssl/simplerisk/simplerisk.crt SSLCertificateKeyFile /etc/apache2/ssl/simplerisk/simplerisk.key SSLProtocol -all +TLSv1.2 +TLSv1.3 diff --git a/simplerisk-minimal/generate_dockerfile.sh b/simplerisk-minimal/generate_dockerfile.sh index 53ba282..634b366 100755 --- a/simplerisk-minimal/generate_dockerfile.sh +++ b/simplerisk-minimal/generate_dockerfile.sh @@ -144,18 +144,15 @@ RUN echo 'upload_max_filesize = 5M' >> /usr/local/etc/php/conf.d/docker-php-uplo echo 'log_errors = On' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \\ echo 'error_log = /dev/stderr' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \\ echo 'display_errors = Off' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \\ -# Create SSL Certificates for Apache SSL - mkdir -p /etc/apache2/ssl/ca /etc/apache2/ssl/simplerisk && \\ -# Generate CA - openssl genrsa -out /etc/apache2/ssl/ca/ca.key 4096 && \\ - openssl req -x509 -new -nodes -key /etc/apache2/ssl/ca/ca.key -sha256 -days 3650 -out /etc/apache2/ssl/ca/ca.crt -subj "/CN=SimpleRisk CA" && \\ -# Generate certs - openssl genrsa -out /etc/apache2/ssl/simplerisk/simplerisk.key 2048 && \\ - openssl req -new -key /etc/apache2/ssl/simplerisk/simplerisk.key -out /etc/apache2/ssl/simplerisk/simplerisk.csr -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:simplerisk,IP:127.0.0.1,IP:0.0.0.0" && \\ - openssl x509 -req -days 365 -in /etc/apache2/ssl/simplerisk/simplerisk.csr -CA /etc/apache2/ssl/ca/ca.crt -CAkey /etc/apache2/ssl/ca/ca.key -CAcreateserial -out /etc/apache2/ssl/simplerisk/simplerisk.crt -copy_extensions copyall && \\ - cp /etc/apache2/ssl/ca/ca.crt /usr/local/share/ca-certificates/simplerisk.crt && \\ - chmod 644 /usr/local/share/ca-certificates/simplerisk.crt && \\ - update-ca-certificates && \\ +# SSL certificate directory for Apache. The actual key pair is generated at +# container startup by entrypoint.sh (see set_ssl_certificate), not here at +# build time: a key baked into this RUN would be identical in every pulled +# copy of the image and, combined with registering it as a trusted CA, would +# let anyone who pulls the image forge certs the container trusts (HackerOne +# #3764027). No custom CA is created or installed into the system trust +# store -- Apache's cert is self-signed directly, matching the simplerisk +# (non-minimal) image. + mkdir -p /etc/apache2/ssl/simplerisk && \\ # Activate Apache modules a2enmod headers rewrite ssl && \\ a2enconf security && \\ diff --git a/simplerisk/Dockerfile b/simplerisk/Dockerfile index ca867ea..55a7f51 100644 --- a/simplerisk/Dockerfile +++ b/simplerisk/Dockerfile @@ -110,7 +110,13 @@ RUN chown -R www-data: /var/www/simplerisk RUN chown -R www-data: /var/log/simplerisk # Setting up cronjob -RUN echo "* * * * * root /usr/bin/php -f /var/www/simplerisk/cron/cron.php > /dev/null 2>&1" >> /etc/cron.d/simplerisk-cron && \ +# Runs as www-data, not root: /var/www/simplerisk (including cron.php) is +# chowned to www-data above, so a root cron entry over a www-data-writable +# file would let any web-tier compromise escalate to root on the next tick +# (the same class of bug as CWE-732 / HackerOne #3761952). cron.php only runs +# application-level jobs that already execute as www-data under Apache, so it +# doesn't need root. +RUN echo "* * * * * www-data /usr/bin/php -f /var/www/simplerisk/cron/cron.php > /dev/null 2>&1" >> /etc/cron.d/simplerisk-cron && \ chmod 0644 /etc/cron.d/simplerisk-cron RUN echo "0 0 * * * root /usr/sbin/logrotate /etc/logrotate.d/simplerisk.conf > /dev/null 2>&1" >> /etc/cron.d/logrotate-cron && \ chmod 0644 /etc/cron.d/logrotate-cron diff --git a/simplerisk/generate_dockerfile.sh b/simplerisk/generate_dockerfile.sh index 10e347a..4cadb84 100755 --- a/simplerisk/generate_dockerfile.sh +++ b/simplerisk/generate_dockerfile.sh @@ -159,7 +159,13 @@ RUN chown -R www-data: /var/www/simplerisk RUN chown -R www-data: /var/log/simplerisk # Setting up cronjob -RUN echo "* * * * * root /usr/bin/php -f /var/www/simplerisk/cron/cron.php > /dev/null 2>&1" >> /etc/cron.d/simplerisk-cron && \\ +# Runs as www-data, not root: /var/www/simplerisk (including cron.php) is +# chowned to www-data above, so a root cron entry over a www-data-writable +# file would let any web-tier compromise escalate to root on the next tick +# (the same class of bug as CWE-732 / HackerOne #3761952). cron.php only runs +# application-level jobs that already execute as www-data under Apache, so it +# doesn't need root. +RUN echo "* * * * * www-data /usr/bin/php -f /var/www/simplerisk/cron/cron.php > /dev/null 2>&1" >> /etc/cron.d/simplerisk-cron && \\ chmod 0644 /etc/cron.d/simplerisk-cron RUN echo "0 0 * * * root /usr/sbin/logrotate /etc/logrotate.d/simplerisk.conf > /dev/null 2>&1" >> /etc/cron.d/logrotate-cron && \\ chmod 0644 /etc/cron.d/logrotate-cron