诚心咨询楼主一个问题:我相对taint进行一些修改,代码逻辑问题不大,但是在使用gdb调试taint上遇到一些问题,可大体描述为nginx+php-fpm环境下,不知道怎么调试。希望楼主能给些建议。
这篇我讲继续学习污点标记以及标记打在何处,学习过程我会通过阅读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