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

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

污点标记

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

标记点

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

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

  • php7

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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中是如何实现的吧。

1
2
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);

看的宏的定义

1
2
3
4
5
6
7
#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)

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

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

1
#define PHP_TAINT_MAGIC_NONE 0x00000000

打上即可。

http请求

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

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

1
2
3
4
5
6
7
#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来分析

1
2
3
4
HashTable *ht;
zval *arr;
arr = PG(http_globals)[TRACK_VARS_GET];
ht = HASH_OF(arr);

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

将其打印出来

打标记

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

1
2
3
4
5
6
7
8
9
#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里面进行调用

1
2
3
4
5
6
7
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;
}

然后递归对数组进行标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
}
}
}

看下效果

参考:

1
2
3
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