MOPS Submission 08: Configuration Encryption Patch for Suhosin

May 22nd, 2010

Today it is time to present you the eighth external MOPS submission. It is an article by Juergen Pabel describing a new feature for the Suhosin Extension that allows encrypting configuration strings.

Configuration Encryption Patch for Suhosin

Juergen Pabel, 2010-04-18

Motivation

Passwords stored in configuration files pose a significant risk for application owners. Before delving into technical solutions it should be noted why it is important to employ an additional layer of protection to restrict access to such authentication credentials. The first aspect to consider is the threat impact in case of unauthorized access: for most scenarios these credentials facilitate “unrestricted” (with respect to application data and/or functionality) access to respective backend systems. The second factor in determining the respective risk is the likelihood of the threat: naturally, access to these configuration files should be limited to personal assigned to operating the application and computer system. However, the involved headcount for such operational personal varies largely. Smaller companies usually rely on one or two administrators while large corporations distribute these responsibilities amoung various teams: operating system administrators, application administrators, application operators and application support teams. It is also not uncommon for those teams to be comprised of company employees, service providers and even independant subcontractors. Limiting access to configuration files by setting restrictive file access permissions is surely a good start but will likely be an incomplete solution as some level of access to configuration files is often technically required. Thus, another approach is required to maintain the confidentiality for those authentication credentials. Encrypting the authentication credentials is a reasonable approach, although it should be noted that this does not implement wholistic protection: the application must access the decrypted authentication credentials in order to use them for authentication with backend systems. Hence, the decryption key must be accessible to the application in order to decrypt such encrypted configuration values. Any dedicated attacker will likely be able to obtain access to both the encrypted configuration values and the decryption key (which must be stored somewhere itself). However, it is still important to employ such a protection scheme: it prevents opportunistic and spontanious attacks like shoulder surfing. Some secondary consequences are that it might be easier to fend off potential accusations of negligence and in criminal prosecutions it might also be used to demonstrate deliberate and malicious intent. It is under these objectives that the presented feature addition to the Suhosin extension should be assessed.

Concept

Unlike most other programming languages it is common for PHP software to integrate the configuration files into applications; configuration files are usually implemented as PHP files that are executed as part of the application and contain configurative variable assignments. For simplicity, these files are referred to as “configuration scripts”.

Two new PHP functions are added to the Suhosin PHP extension: secureconfig_encrypt() and secureconfig_decrypt(). These functions implement the cryptographic operations and require a single string parameter each while returning the encrypted or decrypted result as a string. The secureconfig_decrypt() function is intended to be used within configuration scripts like so:

    $db_password = secureconfig_decrypt("Bdm9YyeumSFwGRBnn7eDRq3i896SSfzLO6bcD2yAFXY=");

Accordingly, the function secureconfig_encrypt() might be used for encrypting the plaintext configuration value (like authentication credentials). A simple encryptor tool could be implemented as follows:

    <?php
            echo "Please enter password: ";
            $stdin = fopen ("php://stdin","r");
            $pass = fgets($stdin);
            fclose($stdin);
            echo "Encrypted value: " . secureconfig_encrypt($pass);
    ?>

The as of yet undiscussed aspect is the cryptographic key; it must be defined in php.ini (or its included sub-configuration files). The cryptographic key must be specified in the Suhosin configuration file:

    suhosin.secureconfig.cryptkey = "secret"

The specified value’s SHA-256 digest is generated and used as the key for AES-256 processing. AES-256 is currently the only implemented cipher but any other cryptographic algorithm could be added later on – although such an enhancement would require an additional configuration value for specifying the to be used cryptographic algorithm (and of how many bits the cryptographic key of that cipher must consist).

Implementation

The Suhosin extension version 0.9.31 (the most recent release as of this writing) was used as the base for implementing the described feature. A new file (secureconfig.c) was added and the neccessary build and runtime-initialization references were included in the relevant files (config.m4, config.w32 and suhosin.c). In addition to the new PHP functions the source file secureconfig.c contains the runtime-initialization function suhosin_hook_secureconfig() for registering both newly implemented functions with the PHP runtime engine. The implementation of both suhosin_secureconfig_encrypt() and suhosin_secureconfig_decrypt() are unexpectably trivial; parameters are obtained from the PHP engine by calling zend_parse_parameters() and passed to suhosin_encrypt_string() and suhosin_decrypt_string() respectively for cryptographic processing along with the cryptographic key obtained from the PHP.ini configuration.

The cryptographic proccessing implemented in suhosin_encrypt_string() and suhosin_decrypt_string() calls for a more in-depth analysis. The first aspect to note is the adapted block cipher mode: it employs AES-256 in cipher-block chaining mode (CBC) without the use of an initialization vector (the first block is processed as in ECB mode). Although this practise is cryptographically worrisome because it renders the first encrypted block (16 bytes for AES) susceptible to some cryptographic attacks it’s extremely unlikely that this modification impacts the effective security for the presented feature*. Because the resulting ciphertext is originally intended to be stored in HTTP cookies the existing Suhosin functions employ a modified base64 encoding alphabet; all HTTP relevant characters are cautiously replaced with safer alternatives (“/”, “=” and “+” are mapped to “-”, “.” and “_” respectively). The newly implemented functions undo these transformations in order to employ a standard base64 encoding.

Albeit functionally irrelevant for this enhancement, it should be mentioned that any plaintext passed to suhosin_encrypt_string() is concatonated with the IP address of the client (it is obviously assumed that these functions are only calledin the context of HTTP cookies). The decryption function suhosin_decrypt_string() accepts besides the obvious ciphertext parameter an additional boolean parameter that controls whether the contained IP address must match the client IP address in the current context (again, assuming a HTTP request) in order to return the decrypted plaintext. Obviously, this parameter is set to false in suhosin_secureconfig_decrypt().

* A different conclusion might possibly be drawn for the original intent of suhosin_encrypt_string() and suhosin_decrypt_string(): to encrypt HTTP cookie data.

Download the patch here. The patch will be integrated into the official Suhosin Extension in the next week.

diff -Naur suhosin-0.9.31.org/config.m4 suhosin-0.9.31/config.m4
--- suhosin-0.9.31.org/config.m4    2010-03-28 22:43:13.000000000 +0200
+++ suhosin-0.9.31/config.m4    2010-04-18 15:56:25.000000000 +0200
@@ -5,5 +5,5 @@
 [  --enable-suhosin        Enable suhosin support])
 
 if test "$PHP_SUHOSIN" != "no"; then
-  PHP_NEW_EXTENSION(suhosin, suhosin.c crypt.c crypt_blowfish.c sha256.c memory_limit.c treat_data.c ifilter.c post_handler.c ufilter.c rfc1867.c log.c header.c execute.c ex_imp.c session.c aes.c compat_snprintf.c, $ext_shared)
+  PHP_NEW_EXTENSION(suhosin, suhosin.c crypt.c crypt_blowfish.c sha256.c memory_limit.c treat_data.c ifilter.c post_handler.c ufilter.c rfc1867.c log.c header.c execute.c ex_imp.c session.c aes.c compat_snprintf.c secureconfig.c, $ext_shared)
 fi
diff -Naur suhosin-0.9.31.org/config.w32 suhosin-0.9.31/config.w32
--- suhosin-0.9.31.org/config.w32   2010-03-28 22:43:13.000000000 +0200
+++ suhosin-0.9.31/config.w32   2010-04-18 15:56:25.000000000 +0200
@@ -4,7 +4,7 @@
 ARG_ENABLE("suhosin", "whether to enable suhosin support", "yes");
 
 if (PHP_SUHOSIN == "yes") {
-   EXTENSION("suhosin", "suhosin.c crypt.c crypt_blowfish.c sha256.c memory_limit.c treat_data.c ifilter.c post_handler.c ufilter.c rfc1867.c log.c header.c execute.c ex_imp.c session.c aes.c");
+   EXTENSION("suhosin", "suhosin.c crypt.c crypt_blowfish.c sha256.c memory_limit.c treat_data.c ifilter.c post_handler.c ufilter.c rfc1867.c log.c header.c execute.c ex_imp.c session.c aes.c secureconfig.c");
    if (PHP_SUHOSIN_SHARED) {
        ADD_SOURCES(configure_module_dirname, "crypt_win32.c crypt_md5.c", "suhosin");
    }
diff -Naur suhosin-0.9.31.org/php_suhosin.h suhosin-0.9.31/php_suhosin.h
--- suhosin-0.9.31.org/php_suhosin.h    2010-03-28 22:43:13.000000000 +0200
+++ suhosin-0.9.31/php_suhosin.h    2010-04-18 15:56:25.000000000 +0200
@@ -214,6 +214,8 @@
    long        cookie_checkraddr;
    HashTable *cookie_plainlist;
    HashTable *cookie_cryptlist;
+
+   char*   secureconfig_cryptkey;
   
    zend_bool   coredump;
    zend_bool   apc_bug_workaround;
@@ -329,6 +331,7 @@
 void normalize_varname(char *varname);
 int suhosin_rfc1867_filter(unsigned int event, void *event_data, void **extra TSRMLS_DC);
 void suhosin_bailout(TSRMLS_D);
+void suhosin_hook_secureconfig();
 
 /* Add pseudo refcount macros for PHP version < 5.3 */
 #ifndef Z_REFCOUNT_PP
diff -Naur suhosin-0.9.31.org/secureconfig.c suhosin-0.9.31/secureconfig.c
--- suhosin-0.9.31.org/secureconfig.c   1970-01-01 01:00:00.000000000 +0100
+++ suhosin-0.9.31/secureconfig.c   2010-04-18 16:20:33.000000000 +0200
@@ -0,0 +1,133 @@
+/*
+  +----------------------------------------------------------------------+
+  | Suhosin Version 1                                                    |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 2006-2007 The Hardened-PHP Project                     |
+  | Copyright (c) 2007-2010 SektionEins GmbH                             |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | license@php.net so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author: Juergen Pabel <jpabel@akkaya.de>                             |
+  +----------------------------------------------------------------------+
+*/
+
+#include <stdio.h>
+#include "php.h"
+#include "php_suhosin.h"
+#include "sha256.h"
+
+static char cryptkey[32];
+
+/* {{{ proto string secureconfig_encrypt(string plaintext)
+   Encrypt a configuration value using the configured cryptographic key */
+static PHP_FUNCTION(suhosin_secureconfig_encrypt)
+{
+   char *plaintext, *ciphertext;
+   int plaintext_len, ciphertext_len;
+   int i;
+   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &plaintext, &plaintext_len) == FAILURE) {
+       return;
+   }
+   ciphertext = suhosin_encrypt_string(plaintext, plaintext_len, "", 0, cryptkey TSRMLS_CC);
+   if(ciphertext == NULL) {
+       return;
+   }
+   ciphertext_len = strlen(ciphertext);
+   /* undo suhosin_encrypt_string()'s base64 alphabet transformation */
+   for (i=0; i<ciphertext_len; i++) {
+       switch (ciphertext[i]) {
+           case '-': ciphertext[i]='/'; break;
+           case '.': ciphertext[i]='='; break;
+           case '_': ciphertext[i]='+'; break;
+       }
+   }
+   RETURN_STRINGL((char *)ciphertext, ciphertext_len, 1);
+}
+
+/* }}} */
+
+
+/* {{{ proto string secureconfig_decrypt(string ciphertext)
+   Decrypt a configuration value using the configured cryptographic key */
+static PHP_FUNCTION(suhosin_secureconfig_decrypt)
+{
+   char *plaintext, *ciphertext;
+   int plaintext_len, ciphertext_len;
+   int i;
+  
+   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &ciphertext, &ciphertext_len) == FAILURE) {
+       return;
+   }
+
+   /* redo suhosin_encrypt_string()'s base64 alphabet transformation */
+   for (i=0; i<ciphertext_len; i++) {
+       switch (ciphertext[i]) {
+           case '/': ciphertext[i]='-'; break;
+           case '=': ciphertext[i]='.'; break;
+           case '+': ciphertext[i]='_'; break;
+       }
+   }
+   plaintext = suhosin_decrypt_string(ciphertext, ciphertext_len, "", 0, cryptkey, &plaintext_len, 0 TSRMLS_CC);
+   if(plaintext == NULL || plaintext_len <= 0) {
+       return;
+   }
+   RETURN_STRINGL((char *)plaintext, plaintext_len, 1);
+}
+
+/* }}} */
+
+
+/* {{{ suhosin_secureconfig_functions[]
+ */
+static function_entry suhosin_secureconfig_functions[] = {
+   PHP_NAMED_FE(secureconfig_encrypt, PHP_FN(suhosin_secureconfig_encrypt), NULL)
+   PHP_NAMED_FE(secureconfig_decrypt, PHP_FN(suhosin_secureconfig_decrypt), NULL)
+   {NULL, NULL, NULL}
+};
+/* }}} */
+
+
+void suhosin_hook_secureconfig()
+{
+   char* key;
+   suhosin_SHA256_CTX ctx;
+
+   TSRMLS_FETCH();
+  
+   /* check if we already have secureconfig support */
+   if (zend_hash_exists(CG(function_table), "secureconfig_encrypt", sizeof("secureconfig_encrypt"))) {
+       return;    
+   }
+
+   key = SUHOSIN_G(secureconfig_cryptkey);
+   if (key != NULL) {
+       suhosin_SHA256Init(&ctx);
+       suhosin_SHA256Update(&ctx, (unsigned char*)key, strlen(key));
+       suhosin_SHA256Final((unsigned char *)cryptkey, &ctx);
+   } else {
+       memset(cryptkey, 0x55 /*fallback key with alternating bits*/, 32);
+   }
+
+   /* add the secureconfig functions */
+#ifndef ZEND_ENGINE_2
+   zend_register_functions(suhosin_secureconfig_functions, NULL, MODULE_PERSISTENT TSRMLS_CC);
+#else
+   zend_register_functions(NULL, suhosin_secureconfig_functions, NULL, MODULE_PERSISTENT TSRMLS_CC);
+#endif
+}
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff -Naur suhosin-0.9.31.org/suhosin.c suhosin-0.9.31/suhosin.c
--- suhosin-0.9.31.org/suhosin.c    2010-03-28 22:43:13.000000000 +0200
+++ suhosin-0.9.31/suhosin.c    2010-04-18 15:56:25.000000000 +0200
@@ -956,6 +956,8 @@
    STD_ZEND_INI_BOOLEAN("suhosin.srand.ignore", "1", ZEND_INI_SYSTEM|ZEND_INI_PERDIR, OnUpdateMiscBool, srand_ignore,zend_suhosin_globals, suhosin_globals)
    STD_ZEND_INI_BOOLEAN("suhosin.mt_srand.ignore", "1", ZEND_INI_SYSTEM|ZEND_INI_PERDIR, OnUpdateMiscBool, mt_srand_ignore,zend_suhosin_globals,   suhosin_globals)
 
+   STD_PHP_INI_ENTRY("suhosin.secureconfig.cryptkey", "", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateString, secureconfig_cryptkey, zend_suhosin_globals, suhosin_globals)
+
 PHP_INI_END()
 /* }}} */
 
@@ -1071,6 +1073,7 @@
    suhosin_hook_crypt();
    suhosin_hook_sha256();
    suhosin_hook_ex_imp();
+   suhosin_hook_secureconfig();
 
    /* register the logo for phpinfo */
    php_register_info_logo(SUHOSIN_LOGO_GUID, "image/jpeg", suhosin_logo, sizeof(suhosin_logo));
diff -Naur suhosin-0.9.31.org/suhosin.ini suhosin-0.9.31/suhosin.ini
--- suhosin-0.9.31.org/suhosin.ini  2010-03-28 22:43:13.000000000 +0200
+++ suhosin-0.9.31/suhosin.ini  2010-04-18 15:56:25.000000000 +0200
@@ -443,3 +443,10 @@
 ; .htaccess. The string "legcprsum" will allow logging, execution, get,
 ; post, cookie, request, sql, upload, misc features in .htaccess
 ;suhosin.perdir = "0"
+
+; Controls the cryptographic key with which configuration values can be
+; encrypted (and decrypted using (secureconfig_decrypt).
+; It is recommended that this default value should be replaced with a
+; randomly generated key (256 bit key encoded with base64).
+;suhosin.secureconfig.cryptkey = "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="
+



blog comments powered by Disqus