Skip to content
BeF edited this page Jul 26, 2022 · 12 revisions

2022-07-26 About research, brainstorming and implementing ideas

The original idea of "research and brainstorming" was to work through PHP 7 code and have a look at existing Snuffleupagus code, then come up with a bunch of ideas on how to improve the overall security of both PHP and Snuffleupagus. But, as it turns out, research takes a lot of time and might have been interrupted other projects as well as the infamous Corona situation. In the meantime, PHP 8.0 was released, then PHP 8.1, and Snuffleupagus had to become compatible with both PHP versions at some point. So, instead of specifically doing research, then implementing, it was more of a back and forth between brainstorming and coding. Ideas have been collected and tracked on the Suhosin-NG Ideas/Features Project Page. The list of ideas can generally be categorized into PHP or web application hardening, internal Snuffleupagus improvements and general code style improvements. Most ideas were selected to be implemented, some were not. I will describe in detail, what has been done so far:

  • Load Snuffleupagus as late as possible: Snuffleupagus must be ready to intercept function calls. In order to do so, these functions must already be loaded into PHP. As some functions come from other PHP extensions, they must be loaded first. As a side effect, when Snuffleupagus is loaded last, no other module can accidentally override Snuffleupagus features, e.g. by replacing PHP functions during module startup.

  • PHP end-of-life check: PHP's end-of-life roadmap clearly states when PHP support ends for each version of PHP. These dates have been programmed as a check into the Snuffleupagus startup function, emitting warning messages if an outdated PHP version is detected.

  • Dangerous extensions check: Some PHP extensions are meant for development only, specifically extensios for debugging, tracing and code introspection. An example of how to detect unwanted extensions was previously done with commit b499678, detecting runkit7 and FFI. The entire check was moved to the default configuration file in order to be used by default and extended with commit 9046cb9 to include xdebug, vld, ast, molten and trace in addition to runkit7 and FFI.

  • CSRF protection via Sec-Fetch headers: Cross-Site-Request-Forgery is a serious vulnerability, where an attacker sends a request to another website using your authenticated browser session. One way to protect a client from letting a cross-site request use a browser session is to set the session cookie's SameSite flag. Enforcing this flag was already supported by Snuffleupagus. Most modern browsers support sending request metadata using Sec- headers. Headers prefixed by Sec- are protected and cannot be changed in the context of a website. Specifically the Sec-Fetch-Site header contains the information whether the request was made cross-site or not. As only POST requests are supposed to change data, Snuffleupagus can check this header for POST requests and return 403 (access denied) if a cross-site request was detected. This feature can be enabled by setting sp.sec_fetch_check.enable();

  • Enforce strict sessions: Most session systems will not accept user-selected session IDs into their system, but they will simply reset the session cookie with a securely generated value. Unfortunately PHP's default behaviour is not to use strict sessions. For example an arbitrary session ID of 123 would be accepted and all session data would be stored under this session ID. This promotes Session Fixation as well as using insecure session IDs. Snuffleupagus' protection against this attack is to enforce the INI setting session.use_strict_mode via default configuration.

  • Prevent unwanted parent scope modifications: Parent scope modification is a way for a function to set or change a variable in scope, for example:

    <?php
    extract(['foo'=>'bar']);
    echo $foo; // this will print "bar"

    The dangerous thing would be to allow arbitrary user input to create variables, e.g. extract($_POST), thereby effectively reintroducing magic_quotes - a relic from the 90's PHP experience. Fortunately the default configuration file for Snuffleupagus already contained a rule preventing extract() to override any existing variable, so this is a partial solution. It would be possible to block extract() altogether, but that would likely break a lot of software out there. Another dangerous function is parse_str(), which effectively does the same as extract(), but with a URL-style key/value string, if called with only one argument. Since PHP 8, the second argument - a reference to a result array - is mandatory. A rule to Snuffleupagus default configuration file makes the second argument mandatory for PHP 7 as well:

    @condition PHP_VERSION_ID < 80000;
    sp.disable_function.function("parse_str").pos("1").param_type("reference").allow();
    sp.disable_function.function("parse_str").drop().alias("parse_str() must be called with two arguments.");
    @end_condition;
    

    Another dangerous function that does parent scope modification is assert(), which is disabled by enforcing the INI setting assert.active to be off, which was added to the default configuration as well:

    sp.ini.key("assert.active").set("0").ro();
    
  • Extended config parser: This internal Snuffleupagus feature introduced support for constants and conditions within the configuration file, which is used for implementing other features such as the 'dangerous extension check' (see above). Also, by having a way to check for the PHP version and loaded extension, a single Snuffleupagus configuration file can be used for different setups.

  • PHP 8.1 compatibility: As time moves on and official PHP 7 support will end in November 2022 (this year), PHP 8.1 support was a must-have feature.

  • Config file permission check: It would be rather silly to have a PHP script override the Snuffleupagus configuration and disabling all features. This patch emits a warning if the configuration file is writable by the PHP process.

  • Prefer a stack-allocated configuration structure: Formerly, all Snuffleupagus configuration was held by a heap allocated set of structs, which had to be allocated one by one and freed at some point. Having a stack allocation at this point makes memory management significantly easier, thereby reducing the risk of memory leaks, double free or other memory problems.

  • Prefer C-level implementations instead of calling into PHP: A call to hash_hmac() was made in order to generate HMAC values for safeguarding unserialize(). In theory a user implementation of hash_hmac() would have been used if ext/hash was not loaded in PHP. This potential security risk was mitigated by embedding the well-known crypto library tweetnacl.

There are a few points that have not been chosen for implementation (yet) for various reasons:

  • PHP 8.2 compatibility: PHP 8.2 beta is just out now. Eventually, Snuffleupagus will have support for PHP 8.2, but for now, only preliminary support was added.

  • Up-to-date check: It would be nice to have a message popping up somewhere whenever a new version of Snufflupagus comes out. However, an automated process would trigger outgoing traffic where it is not expected. And actually installing a new version of a PHP extension is usually followed by a process of operating system maintainers porting, checking and packaging the new version. A message of some kind with thousands/millions of PHP installations would likely not expedite the update process.

  • Retire PHP 7.0/7.1/7.2: These old PHP versions have been outdated for some time now and there should not be a single installation out there still using PHP 7.0 or even PHP5. But as long as maintaining support for these old versions is not too much of a hassle, their security can only benefit from having Snuffleupagus still compatible.

  • Enable some features by default: This is a bit of a tricky subject. If all features were to be enabled by default, Snuffleupagus would likely break a lot of software. If no features were enabled by default, there would likely be no protection for installations that just go with the default (sadly too many). So, a partial implementation is the middleground solution for now: old PHP warnings and some extended read-only checks are on by default on C-level and some features are preconfigured using the default configuration file. Also, users may generate a configuration file by QnA using the SNGgen tool.

That's it for now.

2022-07-20 SNGCheck: Runtime Configuration Check Tool

Checking the correctness and consistency of a Snuffleupagus configuration file has been a rather tedious and time consuming process up until now. The SNG-Tools suite now comes with a brand new tool called SNGCheck to check the Snuffleupagus configuration for common mistakes, which include:

  • Feature checks: Find unused or misconfigured Snuffleupagus features.
  • Unit tests for INI protection feature: Assert that certain INI changes only work within the configured range.
  • Unit tests for disabled functions: Assert that certain functions can or cannot be called with the configured parameters.
  • Regex overlap check for disabled functions and cookies: Check that rules do not overlap by matching names against regular expressions.

Since version 0.8 Snuffleupagus secretly ships with the ability to export its configuration. This data is imported and then checked for enabled features and inconsistencies, generating a list of findings sorted by severity. Example output:

php -f sngcheck -- -m ../snuffleupagus/src/modules/snuffleupagus.so -c ./test.rules
--------------------------------- ------ -
SNGCheck Tool v0.1 - Analyze Snuffleupagus configuration
  (C) 2022 SektionEins GmbH
This tool is part of the Suhosin-NG Tools Suite
  -> see https://github.com/sektioneins/sng-tools
--------------------------------- ------ -
PHP binary: /opt/php/root/8.1.8-64bit/bin/php
PHP extension dir: /opt/php/root/8.1.8-64bit/lib/php/extensions/no-debug-non-zts-20210902
SP config file: ./test.rules
--------------------------------- ------ -
[WARNING] ini rule "highlight.comment": access violation (read-only)[WARNING] DF unittest failed: #%DF PASS system RET 2
[NOTICE ] disabled function regex /^sys/ does not match more specific function 'system'
[NOTICE ] cookie rule with name 'PHPSESSID' take precedence over regex rule /PHP.*/
[INFO   ] sp.global.cookie_env_var is not set. encryption features might use a weak key.
[INFO   ] sp.session.encrypt is not set. server-side session data will not be encrypted.
[INFO   ] sp.harden_random is disabled. Please make sure not to trust unsafe random numbers generated my rand()/mt_rand().
[INFO   ] sp.readonly_exec is disabled. Writable scripts will be executed.
[INFO   ] sp.global_strict is disabled. PHP may implicitly cast between types, which may introduce unexpected behaviour.
[INFO   ] sp.upload_validation is disabled. Please make sure to validate user uploads properly.
[INFO   ] sp.global.max_execution_depth is not set. Worst case: This may cause a stack overflow and crash the script.
[INFO   ] sp.global.server_encode is disabled. This feature does URL encoding on URL related PHP variables in order to prevent all kinds of injection vulnerabilities.
[INFO   ] sp.global.server_strip is disabled. This feature removes dangerous characters from user supplied server variables in order to prevent injection vulnerabilities.
[INFO   ] sp.auto_cookie_secure is disabled. This feature adds the secure flag to all cookies, thereby preventing unencrypted cookie transfers.
[INFO   ] sp.xxe_protection is disabled. This feature disables XML entity loading in order to prevent XML entity injections.
[INFO   ] session ID length protection is not enabled. please set both sp.session.sid_min_length and sp.session.sid_max_length.
[INFO   ] sp.sloppy_comparison is disabled. Comparing different data types is allowed, which may lead to unexpected program behaviour.
[INFO   ] sp.ini_protection is disabled. PHP INI settings are not protected against tampering.

You may have noticed a failed unit test in the output. Unit tests are configured using special comments within the configuration file. Example:

sp.ini_protection.enable();
sp.ini_protection.policy_readonly();
sp.ini.key("highlight.comment").regexp("^#[0-9a-fA-F]{6}$");
#%INI FAIL highlight.comment abc
#%INI PASS highlight.comment #aabbcc

sp.disable_function.function("system").ret("2").drop();
#%DF PASS system POS 0 PARAM cmd VALUE "echo 123"
#%DF PASS system RET 2
#%DF PASS shell_exec PARAM cmd VALUE id IP 0.0.0.1

The first example restricts a specific INI setting via regex, which is checked by trying abc and #aabbcc as values and expecting the test to fail or pass respectively. Since the general policy for INI settings is set to read-only, the test fails and reveals a possible configuration mistake.

The second example defines a rule to drop any system() call returning 2, when it actually meant to allow calls returning 2. This is checked by the second unit test ending in RET 2, thus revealing the logical configuration mistake.

2022-07-17 Snuffleupagus Security Check

As part of the NLnet programme that funds the Suhosin-NG project, a basic security review was performed by Radically Open Security last year in September/October 2021. They allocated 2.5 days and had a look at our fork of snuffleupagus as well as the sng-tool 'snggen'. As a result there was a list of findings that will be briefly outlined here, as well as numerous recommendations and ideas on how to strengthen the overall security of the software.

First of all, it was suggested that some internal development script should download the PHP source code via HTTPS and check for a valid PGP signature. This oversight was mitigated right away.

Snuffleupagus needs a global secret key for some features. It was suggested that the user should rather not leave this key unset or with a default value. A suitable implementation was done with commit 9111fdf5e6332923a5faf9f8a7e6b428eb91795a.

It was suggested that -fstack-protector as it was used is weaker than -fstack-protector-strong. This was fixed with commit 7a465ca2d44836fb3c0437dbd5ed9fdf39a33e82.

A regular release build still contained debugging symbols and only partial RELRO. This was fixed with commits 5cd62972be096e2157c791cf7dcf8975e3c20c45 and 8653bcf4ba6a885f8508a943fad12fe9f2f1081e.

The configuration parser sometimes triggers compiler warnings regarding uninitialized variables and it was suggested that this should be investigated further. After thoroughly investigating the issue, the variables in question cannot be uninitialized with our use case.

The test suite triggered out-of-bounds access in the configuration parser. This was fixed with commit 2ffe94c9366f96700ec5f747385ac07307a012a5.

Other non-findings were used as a basis for some of the planning points in the Suhosin-NG Ideas Board.

All in all the entire review experience had been nothing but enlightning in a very professional way.

2021-12-14 Testing Limits

Every system has its limits. The goal of the following exercise was to check for ways to execute code even though Snuffleupagus would have been configured to prevent code execution. This was done systematically by checking for the correct matching of usual suspects, e.g. eval(), system(), ... If a suitable unit test was missing, it was added to the test suite. After that a look into features added to PHP recently, as well as release notes for PHP 8.0 and PHP 8.1 lead to some interesting ideas.

Some test cases were already in place, specifically for system(), call_user_func() and a few other examples. The following test cases were added with commit c3fddfecfab29504097932184389a94251991bc7:

  • direct execution of exec(), passthru(), popen(), proc_open()
  • indirect execution of shell_exec() via backtick syntax in a few different ways:
echo `ls`;
echo ${`ls`};
echo "{${`ls`}}";
  • Closure syntax
$x = Closure::fromCallable('shell_exec');
echo $x('ls');
// --- and ---
$x = shell_exec(...);
echo $x('ls');
  • Calling via variable function name
$x = 'shell_exec'; 
echo $x('ls');
  • Callbacks
echo filter_input(INPUT_GET, 'cmd', FILTER_CALLBACK, array('options' => 'shell_exec'));
register_shutdown_function('shell_exec', 'ls');
header_register_callback('phpinfo');
  • Opcache preload feature and data:// URLs
allow_url_include=1
opcache.enable=1
opcache.enable_cli=1
opcache.preload=data://text/plain,%3C%3Fphp+shell_exec%28%22ls%22%29%3B
  • Include and special URLs
include('data://text/plain,'.urlencode('<?php shell_exec("ls");'));
include('php://filter//resource=data://text/plain,'.urlencode('<?php shell_exec("ls");'));
  • Signal hander via pnctl
declare(ticks=1);
ini_set("pcntl.async_signals", "1");
pcntl_signal(SIGALRM, function($signo) { shell_exec("ls"); });
// pcntl_alarm(1); // too slow
system("kill -14 " . getmypid());
sleep(5);

Another test case failed starting with PHP 8.1, which turned out to be a missing check for oversight by not checking for the opcode ZEND_TICKS, after calling register_tick_function(). This was fixed with commit 6095651e2caa729ff56ae5a53c908b09e5f7dc29 along with minor issues regarding PHP 8.1 compatibility.

Matching of class names in eval()'d code with whitelisting was added/fixed with 4a45ba42b609d48c8297456d67cc8d955073b567, so we can match common method calls, e.g.

class Foo {
    static function bar($a) { echo $a . PHP_EOL; return 11; }
};
eval('Foo::bar("test");');

using a config line such as

sp.disable_function.function("Foo::bar").ret("11").drop();

And finally, function checks are applied to internal functions (not user-defined functions) as well, so that class methods, which are not hooked automatically, can be matched and filtered, too. See a5dcbb8cb802ba18e618ca38ea8e6acbf8b133ff.

During the testing phase I came across the FFI extension, which dynamically generates an interface to call C functions, e.g. on MacOS we might load system() from libSystem.dylib:

$ffi = FFI::cdef("
    int  system(const char *);
", "libSystem.dylib");
$ffi->system("ls");

Incidentally the function name set by FFI to call $ffi->system() is system rather than FFI::system, which makes matching rather problematic. As a general rule, I would recommend to disable FFI entirely in production environments.

2021-09-16 Suhosin Feature Backports

Snuffleupagus has done a great job protecting PHP with many features, some of which were inspired by the original Suhosin extension. But there are still a few things missing that had been available with Suhosin before. Some of these missing features were selected and have been ported back to Snuffleupagus:

2021-08-30 Q&A Tool: Generate Snuffleupagus rules files

I don't know what it is about configuration files, but people tend to leave them as they are, once their application runs smoothly. But with a PHP security extension such as Snuffleupagus, leaving features disabled or not configured properly is like forgetting to fasten your seatbelt. So, what could possibly be done to make this task - well, as much fun as a CLI tool can be.

I decided to do a Q&A style tool for two reasons: (1) The entire questionnaire must be completed. That means all features will be known at least from reading and understanding the questions. Also, it is unlikely that someone forgets to complete the configuration process over a lunch break as it might be with editing configuration files. And (2), providing easy questions for complex configuration options enables more people to actually do an informed decision.

Example:

$ ./snggen --qa
Hi.

This is a short questionnaire, just to get to know your system better.



|==========[ display_errors ]==========>

Is this a configuration for a production system, or for a development setup?

For production systems it is usually best to disable error output and similar information
disclosure.


[p]: production system
[d]: development system

Answer [default=p]:
Ok. Storing answer `[p]: production system`

You can check out the first tool at the new SNG Tools Repository. Please, let me know what you think about it either via Twitter (@suhosin) or via the repo's issue tracker.

2021-08-18 Protecting PHP INI settings

A lot of security can come from a little secure configuration. In order to enforce certain restrictions on INI settings, Snuffleupagus was extended to provide an easy and straightforward way to set up the rules for such restrictions. This was done with commit id 2392c46.

Let's start by activating the feature:

sp.ini_protection.enable();

INI settings can be set to read-only globally or individually. Example:

## global read-only policy
sp.ini_protection.policy_readonly();

## then make one setting read-write:
sp.ini.key("display_errors").rw();

or

## global read-write policy
sp.ini_protection.policy_readwrite();

## then set one setting to read-only mode
sp.ini.key("display_errors").ro();

Numeric settings can be checked for minimum/maximum value. Of course shorthand notation (K/M/G) is allowed.

sp.ini.key("memory_limit").min("4M").max("256M").rw();

A regular expression check can be applied.

sp.ini.key("highlight.comment").regexp("^#[0-9a-fA-F]{6}$");

Rule violations can fail silently or with a log message, as well as stop the request entirely

## fail silently
#sp.ini_protection.policy_silent_fail();

## or always stop the request
#sp.ini_protection.policy_drop();

## or just stop for this one rule
sp.ini.key("display_errors").ro().drop();

And of course, initial values can be enforced, which overrides php.ini or .htaccess settings:

sp.ini.key("display_errors").set("0").ro();

A more elaborate example can be found here.

2021-08-09 Snuffleupagus Code Review

During the last couple of months a manual code review of Snuffleupagus, the basis for most Suhosin-NG efforts, was performed in order to find both security issues and ways to improve the code for further development. Uncovered issues were then fixed and pushed to our fork of Snuffleupagus with the idea to eventually merge most of the changes back upstream. As the PHP extension is written in C, a particular focus was set to memory management, e.g.

  • double free()
  • use after free
  • memory leaks
  • memory corruption
  • format string issues
  • stack/heap overflows

Other checks are listed below: (The list may be incomplete)

  • Off-by-One mistakes
  • data/string encoding
  • incorrect byte order
  • arithmetic boundaries
  • type conversion, e.g. signed/unsigned, truncation, comparison
  • pointer arithmetic
  • typos in code or output
  • algorithmic boundaries, e.g. non-deterministic behaviour, infinite loops
  • algorithmic flaws
  • string handling
  • file handling and permissions
  • race conditions
  • logical flaws

Findings

The following issues were found and fixed with the corresponding git commit:

That's it for now. Stay tuned for another update about the new INI protection feature.

2021-08-03 Long time, no see

Hello everyone. It's been some time since the last suhosing-ng annoucement and a lot has happened. First of all, there was and still is the COVID-19 pandemic, which affected most people on this planet in some way or another. But hopefully, everybody will be inoculated soon, so that's that. Then, on a more technical note, PHP 8 was released with marvelous new features and internal workings. And of course, the original Snuffleupagus, which is the basis for the Suhosin-NG efforts, was developed quite a bit in the right direction.

So, where do we stand now? During the past couple of months there was a lot of brainstorming and prototype development being done behind the curtains. The results of this development will be incorporated into our fork of Snuffleupagus as well as the Suhosin-NG repository for additional tooling over the next few weeks.

2019-07-17 Suhosin is back!

Overwhelmingly marvellous news: Suhosin-NG was accepted for the 2019-04 open call from NLnet.

About NLnet

NLnet logo

When it comes to important ideas that can help improve our society, there really are no boundaries. The challenge is to turn those opportunities into reality. Great ideas just come, but they are gone in a breeze as well. Lets make good use of them.

About Suhosin-NG

Suhosin (pronounced 'su-ho-shin') is an advanced protection system for PHP installations. It is designed to protect servers and users from known and unknown flaws in PHP applications and the PHP core. Suhosin comes in two independent parts, that can be used separately or in combination. The first part is a small patch against the PHP core, that implements a few low-level protections against buffer overflows or format string vulnerabilities and the second part is a powerful PHP extension that implements numerous other protections.

Since the release of PHP 7.0 in December 2015 the internet community has been desperately hoping to get the protection of the Suhosin PHP extension for their freshly baked PHP 7 installation. There was a first attempt to port Suhosin to PHP 7 conveniently named "Suhosin7". Unfortunately the Suhosin7 development stagnated increasingly and eventually came to an end during the alpha stage.

A few years later in 2017 another project called Snuffleupagus (or "SP" for short, because nobody really knows how to pronounce "Snuffleupagus" in Central Europe) took the opportunity and implemented a powerful PHP protection system. And they did excellent work. Why this project has not gotten the attention that Suhosin did back with PHP 5 is unclear to me, but this is about to change drastically.

The Suhosin-NG project will improve upon SP by integrating a number of old and new ideas over the next couple of months. If all goes according to plan, most ideas will be sent as Github pull-requests to the upstream SP project and hopefully get integrated.

Who is behind Suhosin-NG?

SektionEins Logo

The original Suhosin project was developed and maintained for years at SektionEins. For more than a decade we have been well known in the internet and web community for our expertise in web application security audits -- source code audits, penetration testing, infrastructure analysis, training and consulting. More than a few projects were released as open source software over the years including Suhosin (the PHP hardening extension), PCC (PHP secure configuration checker), scd-pkcs-11 (PKCS#11 provider with smart card support via GnuPG) and other mind-blowing projects.

Suhosin-NG: The grand plan

Here is a sneak preview of upcoming milestones:

  • Setup and get started: In order to be as transparent as possible and provide the most value for the internet, Suhosin-NG needs some infrastructure setup. Also the internet should be made aware of NLnet/SektionEins collaboration and some details about the project itself.
  • Research and Brainstorming (and collecting ideas): We have collected a few ideas about how to harden web applications during our work on web application audits. In order to get even more up to date, a selective code review of PHP 7 will spark the creativity and provide the necessary insight into new Suhosin-NG features.
  • SP code review: Do a code review with security flaws in mind. We are writing security software, so the more eyes the better. If we happen to uncover ways to improve SP, there will be patches.
  • SP limitation testing: According to the SP documentation it is currently not possible to "hook every language construct“. A few more unit tests will uncover just how effective SP can protect against weird or uncommon language constructs.
  • SP Configuration Defaults: With disable_function rules SP provides a very powerful and versatile tool to restrict PHP’s function calls. This milestone should provide a reasonable default configuration for SP by mimicking suhosin’s feature set.
  • Simplify SP configuration: Configuring protections against security threats is an expert task. It should be possible for the average sysadmin to configure SP in a secure way with as little effort as possible.
  • Automate SP configuration checks: There is no easy way to check SP rules for semantic errors. This milestone provides a way to perform „unit tests“ on SP configuration rules.
  • Integrate php.ini protection: A lot of security flaws can be prevented by restricting the php.ini configuration. The PHP secure configuration checker (PCC) already provides reasonable checks for php.ini. SP should be able to provide runtime restrictions to php.ini configuration based on PCC rules.
  • Suhosin feature backports to SP: SP provides matching features for most but not all of Suhosin’s features. The goal should be to provide SP with some of the missing features.
  • Implementation of Ideas and new Features: Implement hardening ideas from the milestone „Research and Brainstorming“, as well as community ideas provided via Github issue tracker.
  • Porting and packaging: All new tools should be packaged for numerous operating systems.
  • WCAG (Accessibility), Security Scan and Wrap Up: The software should be checked for accessibility and security. Also, there may be other open issues from the community, which have to be addressed.

Nothing is absolutely set in stone. If you happen to have a great idea on how to improve PHP security, please feel free to leave a comment in the Issue tracker.

This news page will be updated on a regular basis every few weeks. Also, check out our social media presence: @suhosin on Twitter.