Add snuffleupagus

This seems to work in my limited testing.
main
Florent Daigniere 2 years ago
parent 56a106ad60
commit e5ab9821f9

@ -52,16 +52,32 @@ RUN set -euxo pipefail \
mkdir -p /root/.cargo/registry/index && \
git clone --bare https://github.com/rust-lang/crates.io-index.git /root/.cargo/registry/index/github.com-1285ae84e5963aae \
; pip install -r requirements-${MAILU_DEPS}.txt \
; apk del -r .build-deps \
; rm -rf /root/.cargo /tmp/*.pem \
; } \
; rm -rf /root/.cache
ARG SNUFFLEUPAGUS_VERSION=0.8.3
ENV SNUFFLEUPAGUS_URL https://github.com/jvoisin/snuffleupagus/archive/refs/tags/v$SNUFFLEUPAGUS_VERSION.tar.gz
RUN set -euxo pipefail \
; curl -sL ${SNUFFLEUPAGUS_URL} | tar xz \
; cd snuffleupagus-$SNUFFLEUPAGUS_VERSION \
; rm -rf src/tests/*php7*/ src/tests/*session*/ src/tests/broken_configuration/ src/tests/*cookie* src/tests/upload_validation/ \
; apk add --virtual .build-deps php81-dev php81-cgi php81-simplexml php81-xml pcre-dev build-base php81-pear php81-openssl re2c \
; ln -s /usr/bin/phpize81 /usr/bin/phpize \
; ln -s /usr/bin/pecl81 /usr/bin/pecl \
; ln -s /usr/bin/php-config81 /usr/bin/php-config \
; ln -s /usr/bin/php81 /usr/bin/php \
; pecl install vld-beta \
; make -j $(grep -c processor /proc/cpuinfo) release \
; cp src/.libs/snuffleupagus.so /app \
; apk del -r .build-deps
# base mailu image
FROM system
COPY --from=build /app/venv/ /app/venv/
COPY --chown=root:root --from=build /app/snuffleupagus.so /usr/lib/php81/modules/
ENV VIRTUAL_ENV=/app/venv
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}"

@ -21,6 +21,8 @@ RUN set -euxo pipefail \
; ln -s /usr/bin/php81 /usr/bin/php \
; gpg --import /tmp/snappymail.asc \
; gpg --import /tmp/roundcube.asc \
; echo extension=snuffleupagus > /etc/php81/conf.d/snuffleupagus.ini \
; rm -f /tmp/*asc \
; mkdir -p /run/nginx \
; mkdir -p /conf
@ -41,7 +43,8 @@ RUN set -euxo pipefail \
; cd roundcube \
; rm -rf CHANGELOG.md SECURITY.md INSTALL LICENSE README.md UPGRADING composer.json-dist installer composer.* \
; ln -sf index.php /var/www/roundcube/public_html/sso.php \
; rm -rf plugins/{autologon,example_addressbook,http_authentication,krb_authentication,new_user_identity,password,redundant_attachments,squirrelmail_usercopy,userinfo,virtuser_file,virtuser_query}
; rm -rf plugins/{autologon,example_addressbook,http_authentication,krb_authentication,new_user_identity,password,redundant_attachments,squirrelmail_usercopy,userinfo,virtuser_file,virtuser_query} \
; sed -i '/suhosin.session.encrypt/d;/mbstring\.func_overload/d' program/lib/Roundcube/bootstrap.php
COPY roundcube/config/config.inc.php /conf/
COPY roundcube/login/mailu.php /var/www/roundcube/plugins/mailu/
@ -81,6 +84,7 @@ COPY start.py /
COPY php.ini /defaults/
COPY php-webmail.conf /etc/php81/php-fpm.d/php-webmail.conf
COPY nginx-webmail.conf /conf/
COPY snuffleupagus.rules /etc/snuffleupagus.rules.tpl
EXPOSE 80/tcp
VOLUME /data

@ -11,3 +11,5 @@ log_errors=On
zlib.output_compression=Off
access.log = /dev/fd/2
error_log = /dev/fd/2
module=snuffleupagus.so
sp.configuration_file=/etc/snuffleupagus.rules

@ -5,7 +5,7 @@ $config = array();
// Generals
$config['db_dsnw'] = '{{ DB_DSNW }}';
$config['temp_dir'] = '/dev/shm/';
$config['des_key'] = '{{ SECRET_KEY }}';
$config['des_key'] = '{{ ROUNDCUBE_KEY }}';
$config['cipher_method'] = 'AES-256-CBC';
$config['identities_level'] = 0;
$config['reply_all_mode'] = 1;

@ -0,0 +1,126 @@
# This is based on default configuration file for Snuffleupagus (https://snuffleupagus.rtfd.io),
# for php8.
# It contains "reasonable" defaults that won't break your websites,
# and a lot of commented directives that you can enable if you want to
# have a better protection.
# Harden the PRNG
sp.harden_random.enable();
# Disabled XXE
sp.xxe_protection.enable();
# Global configuration variables
sp.global.secret_key("{{ SNUFFLEPAGUS_KEY }}");
# Globally activate strict mode
# https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict
sp.global_strict.enable();
# Prevent unserialize-related exploits
# sp.unserialize_hmac.enable();
# Only allow execution of read-only files. This is a low-hanging fruit that you should enable.
sp.readonly_exec.enable();
# PHP has a lot of wrappers, most of them aren't usually useful, you should
# only enable the ones you're using.
sp.wrappers_whitelist.list("file,php,phar,mailsosubstreams");
# Prevent sloppy comparisons.
sp.sloppy_comparison.enable();
# Use SameSite on session cookie
# https://snuffleupagus.readthedocs.io/features.html#protection-against-cross-site-request-forgery
sp.cookie.name("PHPSESSID").samesite("lax");
# Harden the `chmod` function (0777 (oct = 511, 0666 = 438)
sp.disable_function.function("chmod").param("permissions").value("438").drop();
sp.disable_function.function("chmod").param("permissions").value("511").drop();
# Prevent various `mail`-related vulnerabilities
sp.disable_function.function("mail").param("additional_parameters").value_r("\\-").drop();
# Since it's now burned, me might as well mitigate it publicly
sp.disable_function.function("putenv").param("assignment").value_r("LD_").drop()
# This one was burned in Nov 2019 - https://gist.github.com/LoadLow/90b60bd5535d6c3927bb24d5f9955b80
sp.disable_function.function("putenv").param("assignment").value_r("GCONV_").drop()
# Since people are stupid enough to use `extract` on things like $_GET or $_POST, we might as well mitigate this vector
sp.disable_function.function("extract").param("array").value_r("^_").drop()
sp.disable_function.function("extract").param("flags").value("0").drop()
# This is also burned:
# ini_set('open_basedir','..');chdir('..');…;chdir('..');ini_set('open_basedir','/');echo(file_get_contents('/etc/passwd'));
# Since we have no way of matching on two parameters at the same time, we're
# blocking calls to open_basedir altogether: nobody is using it via ini_set anyway.
# Moreover, there are non-public bypasses that are also using this vector ;)
sp.disable_function.function("ini_set").param("option").value_r("open_basedir").drop()
# Prevent various `include`-related vulnerabilities
sp.disable_function.function("require_once").value_r("\.(inc|phtml|php)$").allow();
sp.disable_function.function("include_once").value_r("\.(inc|phtml|php)$").allow();
sp.disable_function.function("require").value_r("\.(inc|phtml|php)$").allow();
sp.disable_function.function("include").value_r("\.(inc|phtml|php)$").allow();
sp.disable_function.function("require_once").drop()
sp.disable_function.function("include_once").drop()
sp.disable_function.function("require").drop()
sp.disable_function.function("include").drop()
# Prevent `system`-related injections
sp.disable_function.function("system").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
sp.disable_function.function("shell_exec").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
sp.disable_function.function("exec").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
sp.disable_function.function("proc_open").param("command").value_r("^gpg ").allow();
sp.disable_function.function("proc_open").param("command").value_r("[$|;&`\\n\\(\\)\\\\]").drop();
# Prevent runtime modification of interesting things
sp.disable_function.function("ini_set").param("option").value("assert.active").drop();
sp.disable_function.function("ini_set").param("option").value("zend.assertions").drop();
sp.disable_function.function("ini_set").param("option").value("memory_limit").drop();
sp.disable_function.function("ini_set").param("option").value("include_path").drop();
sp.disable_function.function("ini_set").param("option").value("open_basedir").drop();
# Detect some backdoors via environment recon
sp.disable_function.function("ini_get").param("option").value("allow_url_fopen").drop();
sp.disable_function.function("ini_get").param("option").value("open_basedir").drop();
sp.disable_function.function("ini_get").param("option").value_r("suhosin").drop();
sp.disable_function.function("function_exists").param("function").value("eval").drop();
sp.disable_function.function("function_exists").param("function").value("exec").drop();
sp.disable_function.function("function_exists").param("function").value("system").drop();
sp.disable_function.function("function_exists").param("function").value("shell_exec").drop();
sp.disable_function.function("function_exists").param("function").value("proc_open").drop();
sp.disable_function.function("function_exists").param("function").value("passthru").drop();
sp.disable_function.function("is_callable").param("value").value("eval").drop();
sp.disable_function.function("is_callable").param("value").value("exec").drop();
sp.disable_function.function("is_callable").param("value").value("system").drop();
sp.disable_function.function("is_callable").param("value").value("shell_exec").drop();
sp.disable_function.function("is_callable").filename_r("/app/libraries/snappymail/pgp/gpg\.php$").param("value").value("proc_open").allow();
sp.disable_function.function("is_callable").param("value").value("proc_open").drop();
sp.disable_function.function("is_callable").param("value").value("passthru").drop();
# Ghetto error-based sqli detection
#sp.disable_function.function("mysql_query").ret("FALSE").drop();
#sp.disable_function.function("mysqli_query").ret("FALSE").drop();
#sp.disable_function.function("PDO::query").ret("FALSE").drop();
# Ensure that certificates are properly verified
sp.disable_function.function("curl_setopt").param("value").value("1").allow();
sp.disable_function.function("curl_setopt").param("value").value("2").allow();
# `81` is SSL_VERIFYHOST and `64` SSL_VERIFYPEER
sp.disable_function.function("curl_setopt").param("option").value("64").drop().alias("Please don't turn CURLOPT_SSL_VERIFYCLIENT off.");
sp.disable_function.function("curl_setopt").param("option").value("81").drop().alias("Please don't turn CURLOPT_SSL_VERIFYHOST off.");
# File upload
sp.disable_function.function("move_uploaded_file").param("to").value_r("\\.ph").drop();
sp.disable_function.function("move_uploaded_file").param("to").value_r("\\.ht").drop();
# Logging lockdown
sp.disable_function.function("ini_set").param("option").value_r("error_log").drop()
sp.disable_function.function("ini_set").param("option").value_r("display_errors").drop()
sp.auto_cookie_secure.enable();
sp.cookie.name("roundcube_sessauth").samesite("strict");
sp.cookie.name("roundcube_sessid").samesite("strict");
sp.ini_protection.policy_silent_fail();

@ -51,7 +51,9 @@ if not secret_key:
print(f"Can't read SECRET_KEY from file: {exc}", file=sys.stderr)
exit(2)
context['SECRET_KEY'] = hmac.new(bytearray(secret_key, 'utf-8'), bytearray('ROUNDCUBE_KEY', 'utf-8'), 'sha256').hexdigest()
context['ROUNDCUBE_KEY'] = hmac.new(bytearray(secret_key, 'utf-8'), bytearray('ROUNDCUBE_KEY', 'utf-8'), 'sha256').hexdigest()
context['SNUFFLEPAGUS_KEY'] = hmac.new(bytearray(secret_key, 'utf-8'), bytearray('SNUFFLEPAGUS_KEY', 'utf-8'), 'sha256').hexdigest()
conf.jinja("/etc/snuffleupagus.rules.tpl", context, "/etc/snuffleupagus.rules")
# roundcube plugins
# (using "dict" because it is ordered and "set" is not)
@ -118,7 +120,7 @@ if os.path.exists("/var/run/nginx.pid"):
os.system("nginx -s reload")
# clean env
[env.pop(key, None) for key in env.keys() if key == "SECRET_KEY" or key.startswith("ROUNDCUBE_")]
[env.pop(key, None) for key in env.keys() if key == "SECRET_KEY" or key.endswith("_KEY")]
# run nginx
os.system("php-fpm81")

Loading…
Cancel
Save