### Quickstart: Build and install Snuffleupagus from source Source: https://snuffleupagus.readthedocs.io/_sources/installation.rst.txt Clone the repository, compile the extension with phpize and configure, then install it. Requires php7.0-dev or later on Debian. ```bash git clone https://github.com/jvoisin/snuffleupagus cd snuffleupagus/src phpize ./configure --enable-snuffleupagus make make install ``` -------------------------------- ### Install dependencies on OpenBSD Source: https://snuffleupagus.readthedocs.io/installation.html Use the pkg_add command to install required dependencies, ensuring gmake is included for PHP build system compatibility. ```shell doas pkg_add git php autoconf gmake ``` -------------------------------- ### Example gdb session showing crash backtrace and registers Source: https://snuffleupagus.readthedocs.io/debug.html This is the expected output from a gdb session when Snuffleupagus crashes at startup. It shows the command to start gdb, the 'run' command, the segmentation fault, the backtrace from 'bt', and the register dump from 'info registers'. Use this as a reference for the information to include in a bug report. ```gdb $ gdb --args /usr/bin/php7.0 -d "sp.configuration_file=/config_disabled_functions_param_alias.ini" -d "extension=/home/jvoisin/snuffleupagus/src/modules/snuffleupagus.so" (gdb) r Starting program: /usr/bin/php7.0 -d sp.configuration_file=/config_disabled_functions_param_alias.ini -d extension=/home/jvoisin/snuffleupagus/src/modules/snuffleupagus.so Program received signal SIGSEGV, Segmentation fault. 0x00007fffe25558f5 in parse_str (line=0x7fffddd3602d "\"system\").alias(\"1\").drop();", keyword=0x7fffe25673ec ".function(", retval=0x7fffddd33028) at /home/jvoisin/snuffleupagus/src/sp_config.c:107 107 char a = *x; (gdb) bt #0 0x00007fffe25558f5 in parse_str (line=0x7fffddd3602d "\"system\").alias(\"1\").drop();", keyword=0x7fffe25673ec ".function(", retval=0x7fffddd33028) at /home/jvoisin/snuffleupagus/src/sp_config.c:107 #1 0x00007fffe2555e2c in parse_keywords (funcs=0x7fffffffc700, line=0x7fffddd3602d "\"system\").alias(\"1\").drop();") at /home/jvoisin/snuffleupagus/src/sp_config_utils.c:11 #2 0x00007fffe2562ff6 in parse_disabled_functions (line=0x7fffddd36023 ".function(\"system\").alias(\"1\").drop();") at /home/jvoisin/snuffleupagus/src/sp_config_keywords.c:219 #3 0x00007fffe2555609 in parse_line (line=0x7fffddd36010 "sp.disable_function.function(\"system\").alias(\"1\").drop();") at /home/jvoisin/snuffleupagus/src/sp_config.c:46 #4 0x00007fffe2555bf5 in sp_parse_config (conf_file=0x7ffff7e14028 "/config_disabled_functions_param_alias.ini") at /home/jvoisin/snuffleupagus/src/sp_config.c:182 #5 0x00007fffe2555331 in OnUpdateConfiguration (entry=0x7fffddd39010, new_value=0x7ffff7e14010, mh_arg1=0x0, mh_arg2=0x0, mh_arg3=0x0, stage=1) at /home/jvoisin/snuffleupagus/src/snuffleupagus.c:176 #6 0x00005555557d2861 in zend_register_ini_entries () #7 0x00007fffe2554fc9 in zm_startup_snuffleupagus (type=1, module_number=53) at /home/jvoisin/snuffleupagus/src/snuffleupagus.c:92 #8 0x00005555557bb9ae in zend_startup_module_ex () #9 0x00005555557bba5c in ?? () #10 0x00005555557c902a in zend_hash_apply () #11 0x00005555557bbd1a in zend_startup_modules () #12 0x00005555557555db in php_module_startup () #13 0x000055555584d02d in ?? () #14 0x0000555555638581 in main () (gdb) info registers rax 0x0 0 rbx 0x7fffe2562acc 140736990685900 rcx 0x7fffe25558cd 140736990632141 rdx 0x7fffddd33028 140736914993192 rsi 0x7fffe25673ec 140736990704620 rdi 0x7fffddd3602d 140736915005485 rbp 0x7fffffffc660 0x7fffffffc660 rsp 0x7fffffffc620 0x7fffffffc620 r8 0xffff 65535 r9 0x1 1 r10 0x7ffff653b2a0 140737326068384 r11 0x0 0 r12 0x7ffff3b7cff0 140737282297840 r13 0x35 53 r14 0x15 21 r15 0x7fffe2565525 140736990696741 rip 0x7fffe25558f5 0x7fffe25558f5 eflags 0x10202 [ IF RF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 (gdb) ``` -------------------------------- ### Configure sp.configuration_file in php.ini Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Basic setup to load the Snuffleupagus module and specify the path to the rules file. ```ini module=snuffleupagus.so sp.configuration_file=/path/to/your/snuffleupagus/rules/file.rules ``` -------------------------------- ### Analyze core dump with GDB: bt and info registers Source: https://snuffleupagus.readthedocs.io/_sources/debug.rst.txt Start GDB with the PHP binary and the core dump file, then run 'bt' to get a backtrace and 'info registers' to get the registers. This is used to gather debugging information after a crash, either to send to the Snuffleupagus developers or to analyze locally. ```bash gdb /usr/bin/php /tmp/core-php-fpm-1337 ``` ```gdb bt ``` ```gdb info registers ``` -------------------------------- ### Configure ini settings Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Examples of setting ini keys with constraints like min/max values, read-only/read-write policies, and regular expression validation. ```text sp.ini.key("display_errors").set("0").ro(); sp.ini.key("default_socket_timeout").min("1").max("300").rw(); sp.ini.key("highlight.comment").regexp("^#[0-9a-fA-F]{6}$"); ``` -------------------------------- ### Heroku deployment error log Source: https://snuffleupagus.readthedocs.io/installation.html Example of a crashed Heroku process due to composer deployment issues. ```text heroku[web.1]: Starting process with command `vendor/bin/heroku-php-apache2 -F fpm_custom.conf public/` heroku[web.1]: Stopping all processes with SIGTERM app[web.1]: Stopping httpd... app[web.1]: SIGTERM received, attempting graceful shutdown... app[web.1]: Stopping php-fpm... app[web.1]: Shutdown complete. heroku[web.1]: Process exited with status 143 app[web.1]: [heroku-exec] Starting app[web.1]: Unable to determine Composer vendor-dir setting; is 'composer' executable on path or 'composer.phar' in current working directory? heroku[web.1]: Process exited with status 1 heroku[web.1]: State changed from starting to crashed ``` -------------------------------- ### Configure system function evaluation order Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt This example demonstrates the evaluation order of rules for the system function: allowing 'id', tracing 'ping' commands, and dropping all other calls. ```python sp.disable_function.function("system").param("cmd").value("id").allow(); sp.disable_function.function("system").param("cmd").value_r("^ping").drop().simulation(); sp.disable_function.function("system").param("cmd").drop(); ``` -------------------------------- ### Gentoo Libtool Version Mismatch Error Source: https://snuffleupagus.readthedocs.io/installation.html Example of the build error encountered on Gentoo due to a libtool version mismatch. ```bash $ make $ /bin/sh /root/snuffleupagus-0.5.0/src/libtool --mode=compile cc -I. -I/root/snuffleupagus-0.5.0/src -DPHP_ATOM_INC -I/root/snuffleupagus-0.5.0/src/include -I/root/snuffleupagus-0.5.0/src/main -I/root/snuffleupagus-0.5.0/src -I/usr/lib64/php7.3/include/php -I/usr/lib64/php7.3/include/php/main -I/usr/lib64/php7.3/include/php/TSRM -I/usr/lib64/php7.3/include/php/Zend -I/usr/lib64/php7.3/include/php/ext -I/usr/lib64/php7.3/include/php/ext/date/lib -DHAVE_CONFIG_H -g -O2 -Wall -Wextra -Wno-unused-parameter -Wformat=2 -Wformat-security -D_FORTIFY_SOURCE=2 -fstack-protector -c /root/snuffleupagus-0.5.0/src/snuffleupagus.c -o snuffleupagus.lo libtool: Version mismatch error. This is libtool 2.4.6, but the libtool: definition of this LT_INIT comes from an older release. libtool: You should recreate aclocal.m4 with macros from libtool 2.4.6 libtool: and run autoconf again. make: *** [Makefile:193: snuffleupagus.lo] Error 63 $ ``` -------------------------------- ### Heroku: Example deployment error output Source: https://snuffleupagus.readthedocs.io/_sources/installation.rst.txt Shows the error output when the default rules block composer deployment. The process exits with status 143 or 1. ```bash heroku[web.1]: Starting process with command `vendor/bin/heroku-php-apache2 -F fpm_custom.conf public/` heroku[web.1]: Stopping all processes with SIGTERM app[web.1]: Stopping httpd... app[web.1]: SIGTERM received, attempting graceful shutdown... app[web.1]: Stopping php-fpm... app[web.1]: Shutdown complete. heroku[web.1]: Process exited with status 143 app[web.1]: [heroku-exec] Starting app[web.1]: Unable to determine Composer vendor-dir setting; is 'composer' executable on path or 'composer.phar' in current working directory? heroku[web.1]: Process exited with status 1 heroku[web.1]: State changed from starting to crashed ``` -------------------------------- ### Ghetto error-based SQLi detection Source: https://snuffleupagus.readthedocs.io/config.html Example configuration to drop database query functions if they return FALSE, which may indicate an error-based SQL injection attempt. ```text # 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(); ``` -------------------------------- ### Complex conditional logic Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Example using logical operators within a condition block. ```text @condition extension_loaded("session") && PHP_VERSION_ID <= 80200; set whitelist "my_fun,cos" sp.eval_whitelist.list(whitelist).simulation().dump("/tmp/dump_result/"); @end_condition; ``` -------------------------------- ### Snuffleupagus default configuration file Source: https://snuffleupagus.readthedocs.io/config.html A comprehensive example of a Snuffleupagus configuration file, including hardening for PRNG, XXE protection, cookie settings, and conditional rules based on PHP version. ```text # This is the default configuration file for Snuffleupagus (https://snuffleupagus.rtfd.io). # 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(); # Enable XXE protection @condition extension_loaded("xml"); sp.xxe_protection.enable(); @end_condition; # Global configuration variables # sp.global.secret_key("YOU _DO_ NEED TO CHANGE THIS WITH SOME RANDOM CHARACTERS."); # 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"); # 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"); # Note that an attacker with arbitrary PHP code execution # can bypass some virtual-patching, by (as)using PHP feature. # A clever example would be to declare a class with a __toString method. # Harden the `chmod` function (0777 (oct = 511, 0666 = 438) @condition PHP_VERSION_ID < 80000; sp.disable_function.function("chmod").param("mode").value("438").drop(); sp.disable_function.function("chmod").param("mode").value("511").drop(); @condition PHP_VERSION_ID >= 80000; sp.disable_function.function("chmod").param("permissions").value("438").drop(); sp.disable_function.function("chmod").param("permissions").value("511").drop(); @end_condition; # Prevent various `mail`-related vulnerabilities @condition PHP_VERSION_ID < 80000; sp.disable_function.function("mail").param("additional_parameters").value_r("\\-").drop(); sp.disable_function.function("mb_send_mail").param("additional_parameters").value_r("\\-").drop(); @condition PHP_VERSION_ID >= 80000; sp.disable_function.function("mail").param("additional_params").value_r("\\-").drop(); sp.disable_function.function("mb_send_mail").param("additional_params").value_r("\\-").drop(); @end_condition; # Since it's now burned, me might as well mitigate it publicly @condition PHP_VERSION_ID < 80000; sp.disable_function.function("putenv").param("setting").value_r("LD_").drop(); sp.disable_function.function("putenv").param("setting").value("PATH").drop(); @condition PHP_VERSION_ID >= 80000; sp.disable_function.function("putenv").param("assignment").value_r("LD_").drop(); sp.disable_function.function("putenv").param("assignment").value("PATH").drop(); @end_condition; # This one was burned in Nov 2019 - https://gist.github.com/LoadLow/90b60bd5535d6c3927bb24d5f9955b80 @condition PHP_VERSION_ID < 80000; sp.disable_function.function("putenv").param("setting").value_r("GCONV_").drop() @condition PHP_VERSION_ID >= 80000; sp.disable_function.function("putenv").param("assignment").value_r("GCONV_").drop() @end_condition; # https://github.com/php/php-src/issues/22035 # CURLOPT_SSLENGINE = 10089 @condition PHP_VERSION_ID < 80000; sp.disable_function.function("curl_setopt").param("option").value("10089").drop() @condition PHP_VERSION_ID >= 80000; sp.disable_function.function("curl_setopt").param("option").value("10089").drop() @end_condition; ``` -------------------------------- ### Configure composer.json for Heroku Source: https://snuffleupagus.readthedocs.io/installation.html Update composer.json to include a compile script that clones and installs the Snuffleupagus extension. ```json { "require": { "php": "~7.4.6" }, "config": { "platform": { "php": "7.4.6" } }, "scripts": { "compile": [ "git clone https://github.com/jvoisin/snuffleupagus /tmp/snuffleupagus", "cd /tmp/snuffleupagus/src && phpize && ./configure --enable-snuffleupagus && make && make install", "echo 'extension=snuffleupagus.so\nsp.allow_broken_configuration=on\nsp.configuration_file=/dev/null' > /app/.heroku/php/etc/php/conf.d/999-ext-snuffleupagus.ini" ] } } ``` -------------------------------- ### Set global secret_key Source: https://snuffleupagus.readthedocs.io/config.html Example of setting the global secret_key configuration variable, used for cryptographic features. A long random hex string is provided. ```ini sp.global.secret_key("44239bd400aa82e125337c9d4eb8315767411ccd"); ``` -------------------------------- ### PHP version conditions for configuration portability Source: https://snuffleupagus.readthedocs.io/config.html Uses conditions (e.g., PHP_VERSION_ID) to include or exclude rules depending on the PHP version. Conditions can be used to make configuration portable across different PHP setups. ```ini @condition PHP_VERSION_ID < 80000; # some rules @condition PHP_VERSION_ID >= 80000; # some other rules @end_condition; ``` -------------------------------- ### Heroku: composer.json configuration for Snuffleupagus Source: https://snuffleupagus.readthedocs.io/_sources/installation.rst.txt Edit composer.json to add a compile script that clones, builds, and installs Snuffleupagus, and writes a configuration file with allow_broken_configuration enabled. ```json { "require": { "php": "~7.4.6" }, "config": { "platform": { "php": "7.4.6" } }, "scripts": { "compile": [ "git clone https://github.com/jvoisin/snuffleupagus /tmp/snuffleupagus", "cd /tmp/snuffleupagus/src && phpize && ./configure --enable-snuffleupagus && make && make install", "echo 'extension=snuffleupagus.so\nsp.allow_broken_configuration=on\nsp.configuration_file=/dev/null' > /app/.heroku/php/etc/php/conf.d/999-ext-snuffleupagus.ini" ] } } ``` -------------------------------- ### Example Snuffleupagus error log on Heroku Source: https://snuffleupagus.readthedocs.io/_sources/installation.rst.txt Shows the expected output when a rule blocks the exec function. The log includes the fatal error message and the HTTP request details, confirming Snuffleupagus is running on Heroku. ```text app[web.1]: [05-Jul-2020 07:45:22 UTC] PHP Fatal error: [snuffleupagus][0.0.0.0][disabled_function] Aborted execution on call of the function 'exec', because its argument '$command' content (id;whoami) matched a rule in /app/public/test2.php on line 1 app[web.1]: 10.9.226.141 - - [05/Jul/2020:07:45:22 +0000] "GET /test2.php?cmd=id;whoami HTTP/1.1" 500 - "-" "curl/7.68.0 heroku[router]: at=info method=GET path="/test2.php?cmd=id;whoami" host=heroku-x-snuffleupagus.herokuapp.com request_id=012345678-9012-3456-7890-123456789012 fwd="1.2.3.4" dyno=web.1 connect=0ms service=7ms status=500 bytes=169 protocol=http ``` -------------------------------- ### Multi-line rule definition Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Example of a rule split across multiple lines for readability, available since Snuffleupagus 0.8.0. ```text sp.disable_function.function("mail") .param("to").value_r("\\n") .alias("newline in mail() To:") .drop(); ``` -------------------------------- ### Define and use configuration variables Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Use the set keyword to define variables for reuse within rules. ```text @set CMD "ls" sp.disable_function.function("system").pos("0").value(CMD).allow(); ``` -------------------------------- ### Set log_media to php, syslog, or file Source: https://snuffleupagus.readthedocs.io/config.html Configure logging system to use php, syslog, or file. The default is 'php', but using 'syslog' is recommended; the 'file:' option may be useful for fuzzing or auditing. ```ini sp.log_media("php"); sp.log_media("syslog"); sp.log_media("file:/var/log/snuffleupagus.log"); ``` -------------------------------- ### Run Snuffleupagus with debug symbols in gdb Source: https://snuffleupagus.readthedocs.io/debug.html Use this command to reproduce a startup crash under gdb. Replace the paths with your own configuration file and the debug build of snuffleupagus.so. After launching, type 'run' to trigger the crash, then 'bt' for a backtrace and 'info registers' for register contents. ```bash gdb --args /usr/bin/php -d "sp.configuration_file=/path/to/your/config/config.rules" -d "extension=/complete/path/to/debug/snuffleupagus.so" ``` -------------------------------- ### Configure sp.log_media Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Sets the logging destination for Snuffleupagus. Using syslog is recommended over the default php logging. ```php sp.log_media("php"); sp.log_media("syslog"); sp.log_media("file:/var/log/snuffleupagus.log"); ``` -------------------------------- ### Restrict file uploads Source: https://snuffleupagus.readthedocs.io/config.html Prevents uploading files with extensions starting with .ph or .ht by filtering the destination path in move_uploaded_file, with variations for different PHP versions. ```text @condition PHP_VERSION_ID >= 80000; 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(); @condition PHP_VERSION_ID >= 70400; sp.disable_function.function("move_uploaded_file").param("new_path").value_r("\\.ph").drop(); sp.disable_function.function("move_uploaded_file").param("new_path").value_r("\\.ht").drop(); @condition PHP_VERSION_ID >= 70000; sp.disable_function.function("move_uploaded_file").param("destination").value_r("\\.ph").drop(); sp.disable_function.function("move_uploaded_file").param("destination").value_r("\\.ht").drop(); @end_condition; ``` -------------------------------- ### Configure Snuffleupagus in php.ini Source: https://snuffleupagus.readthedocs.io/_sources/installation.rst.txt Add the extension loading directive and specify the configuration file location. This can be placed in a conf.d/20-snuffleupagus.ini file or directly in php.ini. Be careful to edit the correct configuration for your SAPI (cli/fpm/cgi). ```ini extension=snuffleupagus.so # This is only an example, # you can place your rules wherever you want. sp.configuration_file=/etc/php/conf.d/snuffleupagus.rules ``` -------------------------------- ### Enable session encryption and simulation mode Source: https://snuffleupagus.readthedocs.io/_sources/cookies.rst.txt Activates transparent session encryption and simulation mode. Disabled by default. Requires global.secret_key and optionally global.cookie_env_var to be set. ```php sp.session.encrypt(); sp.session.simulation(); ``` -------------------------------- ### Configure sp.log_max_len Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Specifies the maximum size of the log. ```php sp.log_max_len("16"); ``` -------------------------------- ### Conditional configuration blocks Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Use @condition blocks to apply rules based on PHP version or environment state. ```text @condition PHP_VERSION_ID < 80000; # some rules @condition PHP_VERSION_ID >= 80000; # some other rules @end_condition; ``` -------------------------------- ### Enable core dumps for debugging Snuffleupagus crashes Source: https://snuffleupagus.readthedocs.io/_sources/debug.rst.txt Enable core dumps on Linux by setting the core_uses_pid flag, configuring the core pattern to write to /tmp with the executable name and PID, and removing the core dump size limit. These commands must be run as root (or with appropriate privileges) before restarting the PHP stack. ```bash echo 0 > /proc/sys/kernel/core_uses_pid ``` ```bash echo '/tmp/core-%e.%p' > /proc/sys/kernel/core_pattern ``` ```bash ulimit -c unlimited ``` -------------------------------- ### Snuffleupagus rule with aliases and drop (mail function example) Source: https://snuffleupagus.readthedocs.io/config.html A multi-line rule disabling the mail() function when a newline is in the 'To:' parameter, aliased as 'newline in mail() To:' and dropped. This demonstrates the syntax for indentation and line breaks in rules (since 0.8.0). ```ini sp.disable_function.function("mail") .param("to").value_r("\\n") .alias("newline in mail() To:") .drop(); ``` -------------------------------- ### PHP-FPM configuration for Snuffleupagus on Heroku Source: https://snuffleupagus.readthedocs.io/_sources/installation.rst.txt Sets sp.allow_broken_configuration to off and sp.configuration_file to /app/default.rules in PHP-FPM. This ensures the composer deployment phase is not killed by rules and points to the rule set to use. ```ini ; ext-snuffleupagus php_admin_flag[sp.allow_broken_configuration] = off php_admin_value[sp.configuration_file] = /app/default.rules ``` -------------------------------- ### Configure virtual-patching Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Uses disable_function to restrict dangerous function calls like system() for specific files. ```text # Allow `id.php` to restrict system() calls to `id` sp.disable_function.function("system").filename("/var/www/html/id.php").param("cmd").value("id").allow(); sp.disable_function.function("system").filename("/var/www/html/id.php").drop() ``` -------------------------------- ### Configure eval white and blacklist Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Specifies functions allowed or forbidden within eval. Whitelisting takes precedence over blacklisting. ```text sp.eval_blacklist.list("system,exec,shell_exec"); sp.eval_whitelist.list("strlen,strcmp").simulation(); ``` -------------------------------- ### Configure upload_validation Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Sets a script to validate file uploads. The upload is allowed if the script returns 0. ```text sp.upload_validation.script("/var/www/is_valid_php.py").enable(); ``` -------------------------------- ### Set INI protection to simulation mode Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Configures INI protection to report violations without enforcing rules. ```php sp.ini_protection.simulation(); ``` -------------------------------- ### Gentoo: Fix libtool version mismatch error Source: https://snuffleupagus.readthedocs.io/_sources/installation.rst.txt This error is a documented PHP bug. Recreate aclocal.m4 and rebuild with libtoolize and autoreconf. ```bash $ make $ /bin/sh /root/snuffleupagus-0.5.0/src/libtool --mode=compile cc -I. -I/root/snuffleupagus-0.5.0/src -DPHP_ATOM_INC -I/root/snuffleupagus-0.5.0/src/include -I/root/snuffleupagus-0.5.0/src/main -I/root/snuffleupagus-0.5.0/src -I/usr/lib64/php7.3/include/php -I/usr/lib64/php7.3/include/php/main -I/usr/lib64/php7.3/include/php/TSRM -I/usr/lib64/php7.3/include/php/Zend -I/usr/lib64/php7.3/include/php/ext -I/usr/lib64/php7.3/include/php/ext/date/lib -DHAVE_CONFIG_H -g -O2 -Wall -Wextra -Wno-unused-parameter -Wformat=2 -Wformat-security -D_FORTIFY_SOURCE=2 -fstack-protector -c /root/snuffleupagus-0.5.0/src/snuffleupagus.c -o snuffleupagus.lo libtool: Version mismatch error. This is libtool 2.4.6, but the libtool: definition of this LT_INIT comes from an older release. libtool: You should recreate aclocal.m4 with macros from libtool 2.4.6 libtool: and run autoconf again. make: *** [Makefile:193: snuffleupagus.lo] Error 63 $ ``` ```bash rm -f aclocal.m4 phpize aclocal && libtoolize --force && autoreconf ./configure --enable-snuffleupagus make ``` -------------------------------- ### Extension-based conditional rule Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Apply rules only if a specific PHP extension is loaded. ```text @condition extension_loaded("sqlite3"); sp.ini.key("sqlite3.extension_dir").ro(); @end_condition; ``` -------------------------------- ### Set INI protection policy to drop Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Sets the global policy for rule violations to drop. ```php sp.ini_protection.policy_drop(); ``` -------------------------------- ### Using set keyword to define variable (system function disable) Source: https://snuffleupagus.readthedocs.io/config.html Defines a variable with 'set' and uses it as an argument to disable the system function. The variable is used in a value() argument. ```ini @set CMD "ls" sp.disable_function.function("system").pos("0").value(CMD).allow(); ``` -------------------------------- ### Validate file content with VLD Source: https://snuffleupagus.readthedocs.io/features.html Use the vld project to ensure an uploaded file does not contain valid PHP code. ```bash $ php -d vld.execute=0 -d vld.active=1 -d extension=vld.so $file ``` -------------------------------- ### Enable session cookie encryption Source: https://snuffleupagus.readthedocs.io/cookies.html Activates transparent session encryption. Disabled by default; can be used in simulation mode. ```ini sp.session.encrypt(); sp.session.simulation(); ``` -------------------------------- ### Clone Snuffleupagus source code Source: https://snuffleupagus.readthedocs.io/download.html Use git to clone the official Snuffleupagus repository from GitHub. ```bash git clone https://github.com/jvoisin/snuffleupagus ``` -------------------------------- ### Set INI protection policy to silent fail Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Configures rules to fail silently without logging. ```php sp.ini_protection.policy_silent_fail(); ## or write sp.ini_protection.policy_no_log(); as an alias ``` -------------------------------- ### Configure INI protection Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Enables or disables the INI protection feature. ```php sp.ini_protection.enable(); sp.ini_protection.disable(); ``` -------------------------------- ### Generate whitelist of dangerous functions Source: https://snuffleupagus.readthedocs.io/features.html This script generates a hash and line-based whitelist of dangerous functions, effectively dropping them everywhere else. It helps make post-exploitation processes like backdooring or RAT usage more difficult. ```php $object){ if (FALSE === in_array (pathinfo($name, PATHINFO_EXTENSION), $extensions, true)) { continue; } $hash = ''; $file_content = file_get_contents($name); if ($useHash) { $hash = '.hash("' . hash('sha256', $file_content) . '")'; } $tokens = token_get_all($file_content); foreach ($tokens as $pos => $token) { if (!is_array($token)) { continue; } if (isset($token[1][0]) && '\\' === $token[1][0]) { $token[1] = substr($token[1], 1); } if (!in_array($token[1], $functions_blacklist, true)) { continue; } $prev_token = find_previous_token($tokens, $pos); // Ignore function definitions and class calls // function shell_exec() -> ignored // $db->exec() -> ignored // MyClass::assert() -> ignored if ($prev_token === T_FUNCTION || $prev_token === T_DOUBLE_COLON || $prev_token === T_OBJECT_OPERATOR) { continue; } $output[] = 'sp.disable_function.function("' . $token[1] . '").filename("' . $name . '")' . $hash . '.allow();' . "\n"; } } foreach($functions_blacklist as $fun) { $output[] = 'sp.disable_function.function("' . $fun . '").drop();' . "\n"; } foreach (array_unique($output) as $line) { echo $line; } function find_previous_token(array $tokens, int $pos): ?int { for ($i = $pos - 1; $i >= 0; $i--) { $token = $tokens[$i]; if ($token[0] === T_WHITESPACE) { continue; } if (!is_array($token)) { return null; } return $token[0]; } return null; } ``` -------------------------------- ### Set log_max_len Source: https://snuffleupagus.readthedocs.io/config.html Sets the maximum length of the log (roughly) to 16 characters. Default is 255. ```ini sp.log_max_len("16"); ``` -------------------------------- ### Allowlist php stream-wrappers Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Restricts the permitted built-in PHP streams. ```text sp.wrappers_whitelist.php_list("stdout,stdin,stderr"); ``` -------------------------------- ### Condition using extension_loaded() Source: https://snuffleupagus.readthedocs.io/config.html Conditional rule using the extension_loaded() function to check if a PHP extension is loaded. This condition is not nested. ```ini @condition extension_loaded("sqlite3"); sp.ini.key("sqlite3.extension_dir").ro(); @end_condition; ``` -------------------------------- ### Toggle xxe_protection Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Enables or disables protection against XXE attacks by controlling libxml entity loading. ```text sp.xxe_protection.enable(); sp.xxe_protection.disable(); ``` -------------------------------- ### Whitelist stream-wrappers Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Explicitly defines allowed stream wrappers. ```text sp.wrappers_whitelist.list("file,php,phar"); ``` -------------------------------- ### Resolve Libtool Version Mismatch Source: https://snuffleupagus.readthedocs.io/installation.html Commands to fix the libtool version mismatch by regenerating the build configuration files. ```bash rm -f aclocal.m4 phpize aclocal && libtoolize --force && autoreconf ./configure --enable-snuffleupagus make ``` -------------------------------- ### Condition with logical and arithmetic operations Source: https://snuffleupagus.readthedocs.io/config.html Condition block using arithmetic and logical operations (&& and <=) to set a whitelist variable and apply a simulation rule, which dumps results to a file. ```ini @condition extension_loaded("session") && PHP_VERSION_ID <= 80200; set whitelist "my_fun,cos" sp.eval_whitelist.list(whitelist).simulation().dump("/tmp/dump_result/"); @end_condition; ``` -------------------------------- ### Configure harden_random Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Enables or disables the replacement of insecure rand functions with random_int. ```php sp.harden_random.enable(); sp.harden_random.disable(); ``` -------------------------------- ### Set INI protection global access policy Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Sets the global access policy to either read-only or read-write. ```php sp.ini_protection.policy_readonly(); sp.ini_protection.policy_readwrite(); ``` -------------------------------- ### Set global secret key Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Configure the secret key used for cryptographic features. ```text sp.global.secret_key("44239bd400aa82e125337c9d4eb8315767411ccd"); ``` -------------------------------- ### Detect backdoors via environment reconnaissance Source: https://snuffleupagus.readthedocs.io/config.html Uses conditional logic based on PHP_VERSION_ID to drop calls to dangerous functions like eval, exec, and system, or access to sensitive ini settings. ```text @condition PHP_VERSION_ID < 80000; sp.disable_function.function("ini_get").param("varname").value("allow_url_fopen").drop(); sp.disable_function.function("ini_get").param("varname").value("open_basedir").drop(); sp.disable_function.function("ini_get").param("varname").value_r("suhosin").drop(); sp.disable_function.function("function_exists").param("function_name").value("eval").drop(); sp.disable_function.function("function_exists").param("function_name").value("exec").drop(); sp.disable_function.function("function_exists").param("function_name").value("system").drop(); sp.disable_function.function("function_exists").param("function_name").value("shell_exec").drop(); sp.disable_function.function("function_exists").param("function_name").value("proc_open").drop(); sp.disable_function.function("function_exists").param("function_name").value("passthru").drop(); sp.disable_function.function("is_callable").param("var").value("eval").drop(); sp.disable_function.function("is_callable").param("var").value("exec").drop(); sp.disable_function.function("is_callable").param("var").value("system").drop(); sp.disable_function.function("is_callable").param("var").value("shell_exec").drop(); sp.disable_function.function("is_callable").param("var").value("proc_open").drop(); sp.disable_function.function("is_callable").param("var").value("passthru").drop(); @condition PHP_VERSION_ID >= 80000; 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").param("value").value("proc_open").drop(); sp.disable_function.function("is_callable").param("value").value("passthru").drop(); @end_condition; ``` -------------------------------- ### Blacklist classic webshell patterns Source: https://snuffleupagus.readthedocs.io/config.html Blocks common execution functions when used in conjunction with base64_decode and defines an eval blacklist. ```text sp.disable_function.function("system>base64_decode").drop(); sp.disable_function.function("shell_exec>base64_decode").drop(); sp.disable_function.function("exec>base64_decode").drop(); sp.disable_function.function("passthru>base64_decode").drop(); sp.disable_function.function("proc_open>base64_decode").drop(); sp.eval_blacklist.list("system,exec,shell_exec,passthru,proc_open"); ``` -------------------------------- ### Configure unserialize_noclass Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Enables or disables the restriction on object deserialization via unserialize. ```php sp.unserialize_noclass.enable(); sp.unserialize_noclass.disable(); ``` -------------------------------- ### Configure sloppy_comparison Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Enables or disables the prevention of PHP type juggling during comparisons. ```php sp.sloppy_comparison.enable(); sp.sloppy_comparison.disable(); ``` -------------------------------- ### Restrict include-related functions Source: https://snuffleupagus.readthedocs.io/config.html Allows specific file extensions for include/require functions while dropping all other calls to prevent arbitrary file inclusion. ```text 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(); ``` -------------------------------- ### Enable readonly_exec Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Enables the readonly_exec feature to prevent execution of writable PHP files. ```text sp.readonly_exec.enable(); ``` -------------------------------- ### Set INI protection policy for silent read-only violations Source: https://snuffleupagus.readthedocs.io/_sources/config.rst.txt Configures read-only violations to be silent. ```php sp.ini_protection.policy_silent_ro(); ``` -------------------------------- ### Ensure certificate verification in cURL Source: https://snuffleupagus.readthedocs.io/config.html Enforces SSL verification by dropping attempts to disable CURLOPT_SSL_VERIFYCLIENT or CURLOPT_SSL_VERIFYHOST. ```text 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."); ```