初探php拓展层面(二)
p0desta WEB安全 5981浏览 · 2019-03-08 01:01

这篇我讲继续学习污点标记以及标记打在何处,学习过程我会通过阅读http://pecl.php.net/package/taint的源码来详述实现原理和一些细节。

下一篇讲会对污点跟踪进行分析。

污点标记

这里我们认为所有传入的数据都是不可信的,也就是说所有通过请求发送过来的数据都需要打上标记,被打上标记的数据是会传播的,比如说当进行字符串的拼接等操作在结束后要对新的数据从新标记,因为这个新的字符串仍然是不可信数据,但是经过一些处理函数,比如说addslashes这类函数,就可以将标记清除掉。

标记点

首先我们需要知道怎么打标记,将标记打在何处

首先php7和php5的变量结构体是不一样的,因为结构体的不同,标记打在何处也就产生了区别

  • php7

    typedef union _zend_value {
      zend_long         lval;             /* long value */
      double            dval;             /* double value */
      zend_refcounted  *counted;
      zend_string      *str;
      zend_array       *arr;
      zend_object      *obj;
      zend_resource    *res;
      zend_reference   *ref;
      zend_ast_ref     *ast;
      zval             *zv;
      void             *ptr;
      zend_class_entry *ce;
      zend_function    *func;
      struct {
          uint32_t w1;
          uint32_t w2;
      } ww;
    } zend_value;
    
    typedef struct _zend_refcounted_h {
      uint32_t         refcount;          /* reference counter 32-bit */
      union {
          struct {
              ZEND_ENDIAN_LOHI_3(
                  zend_uchar    type,
                  zend_uchar    flags,    /* used for strings & objects */
                  uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
          } v;
          uint32_t type_info;
      } u;
    } zend_refcounted_h;
    

    在taint中,对于php7来说污染标记的原理是利用zend_uchar flags变量回收结构中未被使用的标记为去做污染标记,如果随着版本的升级,这个位被使用后,那么就会产生冲突。

  • php5

    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;
      zend_ast *ast;
    } 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;
    };
    

    可以看到这个版本的字段并不多,没有方便我们做标记的位置。

    看下taint中是如何实现的吧。

Z_STRVAL_PP(ppzval) = erealloc(Z_STRVAL_PP(ppzval), Z_STRLEN_PP(ppzval) + 1 + PHP_TAINT_MAGIC_LENGTH);
PHP_TAINT_MARK(*ppzval, PHP_TAINT_MAGIC_POSSIBLE);

看的宏的定义

#define PHP_TAINT_MAGIC_NONE     0x00000000
#define PHP_TAINT_MAGIC_POSSIBLE 0x6A8FCE84
#define PHP_TAINT_MAGIC_UNTAINT  0x2C5E7F2D

#define PHP_TAINT_MARK(zv, mark) *((unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1)) = (mark)
#define PHP_TAINT_POSSIBLE(zv) (*(unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1) == PHP_TAINT_MAGIC_POSSIBLE)
#define PHP_TAINT_UNTAINT(zv)  (*(unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1) == PHP_TAINT_MAGIC_UNTAINT)

可能这样看不是很直观,直接看图

既然这样,那么当想要消除标记的时候直接再将

#define PHP_TAINT_MAGIC_NONE     0x00000000

打上即可。

http请求

上面我们认为所有的请求都是不可信的,再没有经过安全函数时都要打上标记,接下来看下获取http请求参数以及给参数打上标记。

获取http请求参数,看鸟哥的文章http://www.laruence.com/2008/04/04/17.html

#define TRACK_VARS_POST           0
#define TRACK_VARS_GET            1
#define TRACK_VARS_COOKIE         2
#define TRACK_VARS_SERVER         3
#define TRACK_VARS_ENV            4
#define TRACK_VARS_FILES          5
#define TRACK_VARS_REQUEST        6

鸟哥问中提到根据测试的结果,可以认定PG(http_globals)[TRACK_VARS_GET]是一个hash table;

我们先利用一下代码获取一下请求参数看一下,这里为了简单分析,直接修改上篇文章HOOK_INCLUDE_OR_EVAL来分析

HashTable *ht;
    zval *arr;
    arr = PG(http_globals)[TRACK_VARS_GET];
    ht = HASH_OF(arr);

可以看到是可以直接从这个hashtable里面获取到我们的参数的

可以利用相关的宏方便获取的,在zend_hash.h里面可以找到相关的宏

将hashtable中的数据全都遍历出来

static int HOOK_INCLUDE_OR_EVAL(ZEND_OPCODE_HANDLER_ARGS)
{
    ulong num_index;
    char *str_index;
    zval **data;
    HashTable *ht;
    zval *arr;
  char *data;
    char *key;
    arr = PG(http_globals)[TRACK_VARS_GET];
    ht = HASH_OF(arr);

    for (zend_hash_internal_pointer_reset(ht);
            zend_hash_has_more_elements(ht) == SUCCESS;
            zend_hash_move_forward(ht))
    {
        zend_hash_get_current_key(ht, &str_index, &num_index, 0);
        zend_hash_get_current_data(ht, (void**)&data);
        key = Z_STRVAL_PP(data);

    } 
    return ZEND_USER_OPCODE_DISPATCH; 
}

这几个函数的作用其实命名已经很明确了,但是还是想看一下,拿zend_hash_get_current_key来说

我们打个断点break zend_hash_get_current_key_ex

我们来看一下

正如上面所说,跟命名是一样的,str_index将返回我们想要得到的key

将其打印出来

打标记

我们重新创建一个扩展,完成基本定义

#define PHP_TAINT_MAGIC_LENGTH   sizeof(unsigned)
#define PHP_TAINT_MAGIC_NONE     0x00000000
#define PHP_TAINT_MAGIC_POSSIBLE 0x6A8FCE84
#define PHP_TAINT_MAGIC_UNTAINT  0x2C5E7F2D
PHP_FUNCTION(confirm_foobar_compiled);
#define ZEND_OPCODE_HANDLER_ARGS zend_execute_data *execute_data
#define PHP_TAINT_MARK(zv, mark) *((unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1)) = (mark)
#define PHP_TAINT_POSSIBLE(zv) (*(unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1) == PHP_TAINT_MAGIC_POSSIBLE)
#define PHP_TAINT_UNTAINT(zv)  (*(unsigned *)(Z_STRVAL_P(zv) + Z_STRLEN_P(zv) + 1) == PHP_TAINT_MAGIC_UNTAINT)

我们在请求初始化时,也就是PHP_RINIT_FUNCTION里面进行调用

PHP_RINIT_FUNCTION(ptaint)
{
    if(PG(http_globals)[TRACK_VARS_GET] && zend_hash_num_elements(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_GET]))) {
        php_taint_mark_arr(PG(http_globals)[TRACK_VARS_GET] TSRMLS_CC);
    }
    return SUCCESS;
}

然后递归对数组进行标记

static void php_taint_mark_arr(zval *symbol_table TSRMLS_DC)
{
    zval **data;
    HashTable *ht = Z_ARRVAL_P(symbol_table);

    for (zend_hash_internal_pointer_reset(ht);
            zend_hash_has_more_elements(ht) == SUCCESS;
            zend_hash_move_forward(ht))
    {


        if(zend_hash_get_current_data(ht, (void**)&data) == FAILURE)
            continue;

        if(Z_TYPE_PP(data) == IS_ARRAY)
        {
            php_taint_mark_arr(*data TSRMLS_CC);
        }else if(Z_TYPE_PP(data) == IS_STRING){
            Z_STRVAL_PP(data) = erealloc(Z_STRVAL_PP(data), Z_STRLEN_PP(data) + 1 + PHP_TAINT_MAGIC_LENGTH);
            PHP_TAINT_MARK(*data, PHP_TAINT_MAGIC_POSSIBLE);
        }
    } 
}

看下效果

参考:

http://www.laruence.com/2009/04/28/719.html
https://www.jianshu.com/p/c6dea66c54f3
https://www.cnblogs.com/iamstudy/articles/php_code_rasp_2.html
1 条评论
某人
表情
可输入 255