1Input Filter Support in PHP 5 2----------------------------- 3 4XSS (Cross Site Scripting) hacks are becoming more and more prevalent, 5and can be quite difficult to prevent. Whenever you accept user data 6and somehow display this data back to users, you are likely vulnerable 7to XSS hacks. 8 9The Input Filter support in PHP 5 is aimed at providing the framework 10through which a company-wide or site-wide security policy can be 11enforced. It is implemented as a SAPI hook and is called from the 12treat_data and post handler functions. To implement your own security 13policy you will need to write a standard PHP extension. There is also 14a powerful standard implementation in ext/filter that should suit most 15peoples' needs. However, if you want to implement your own security 16policy, read on. 17 18A simple implementation might look like the following. This stores the 19original raw user data and adds a my_get_raw() function while the normal 20$_POST, $_GET and $_COOKIE arrays are only populated with stripped 21data. In this simple example all I am doing is calling strip_tags() on 22the data. If register_globals is turned on, the default globals that 23are created will be stripped ($foo) while a $RAW_foo is created with the 24original user input. 25 26ZEND_BEGIN_MODULE_GLOBALS(my_input_filter) 27 zval *post_array; 28 zval *get_array; 29 zval *cookie_array; 30ZEND_END_MODULE_GLOBALS(my_input_filter) 31 32#ifdef ZTS 33#define IF_G(v) TSRMG(my_input_filter_globals_id, zend_my_input_filter_globals *, v) 34#else 35#define IF_G(v) (my_input_filter_globals.v) 36#endif 37 38ZEND_DECLARE_MODULE_GLOBALS(my_input_filter) 39 40zend_function_entry my_input_filter_functions[] = { 41 PHP_FE(my_get_raw, NULL) 42 {NULL, NULL, NULL} 43}; 44 45zend_module_entry my_input_filter_module_entry = { 46 STANDARD_MODULE_HEADER, 47 "my_input_filter", 48 my_input_filter_functions, 49 PHP_MINIT(my_input_filter), 50 PHP_MSHUTDOWN(my_input_filter), 51 NULL, 52 PHP_RSHUTDOWN(my_input_filter), 53 PHP_MINFO(my_input_filter), 54 "0.1", 55 STANDARD_MODULE_PROPERTIES 56}; 57 58PHP_MINIT_FUNCTION(my_input_filter) 59{ 60 ZEND_INIT_MODULE_GLOBALS(my_input_filter, php_my_input_filter_init_globals, NULL); 61 62 REGISTER_LONG_CONSTANT("POST", PARSE_POST, CONST_CS | CONST_PERSISTENT); 63 REGISTER_LONG_CONSTANT("GET", PARSE_GET, CONST_CS | CONST_PERSISTENT); 64 REGISTER_LONG_CONSTANT("COOKIE", PARSE_COOKIE, CONST_CS | CONST_PERSISTENT); 65 66 sapi_register_input_filter(my_sapi_input_filter); 67 return SUCCESS; 68} 69 70PHP_RSHUTDOWN_FUNCTION(my_input_filter) 71{ 72 if(IF_G(get_array)) { 73 zval_ptr_dtor(&IF_G(get_array)); 74 IF_G(get_array) = NULL; 75 } 76 if(IF_G(post_array)) { 77 zval_ptr_dtor(&IF_G(post_array)); 78 IF_G(post_array) = NULL; 79 } 80 if(IF_G(cookie_array)) { 81 zval_ptr_dtor(&IF_G(cookie_array)); 82 IF_G(cookie_array) = NULL; 83 } 84 return SUCCESS; 85} 86 87PHP_MINFO_FUNCTION(my_input_filter) 88{ 89 php_info_print_table_start(); 90 php_info_print_table_row( 2, "My Input Filter Support", "enabled" ); 91 php_info_print_table_row( 2, "Revision", "$Id: 488ca82a7b15672b2186e25e65f9efa13ff650a0 $"); 92 php_info_print_table_end(); 93} 94 95/* The filter handler. If you return 1 from it, then PHP also registers the 96 * (modified) variable. Returning 0 prevents PHP from registering the variable; 97 * you can use this if your filter already registers the variable under a 98 * different name, or if you just don't want the variable registered at all. */ 99SAPI_INPUT_FILTER_FUNC(my_sapi_input_filter) 100{ 101 zval new_var; 102 zval *array_ptr = NULL; 103 char *raw_var; 104 int var_len; 105 106 assert(*val != NULL); 107 108 switch(arg) { 109 case PARSE_GET: 110 if(!IF_G(get_array)) { 111 ALLOC_ZVAL(array_ptr); 112 array_init(array_ptr); 113 INIT_PZVAL(array_ptr); 114 } 115 IF_G(get_array) = array_ptr; 116 break; 117 case PARSE_POST: 118 if(!IF_G(post_array)) { 119 ALLOC_ZVAL(array_ptr); 120 array_init(array_ptr); 121 INIT_PZVAL(array_ptr); 122 } 123 IF_G(post_array) = array_ptr; 124 break; 125 case PARSE_COOKIE: 126 if(!IF_G(cookie_array)) { 127 ALLOC_ZVAL(array_ptr); 128 array_init(array_ptr); 129 INIT_PZVAL(array_ptr); 130 } 131 IF_G(cookie_array) = array_ptr; 132 break; 133 } 134 Z_STRLEN(new_var) = val_len; 135 Z_STRVAL(new_var) = estrndup(*val, val_len); 136 Z_TYPE(new_var) = IS_STRING; 137 138 var_len = strlen(var); 139 raw_var = emalloc(var_len+5); /* RAW_ and a \0 */ 140 strcpy(raw_var, "RAW_"); 141 strlcat(raw_var,var,var_len+5); 142 143 php_register_variable_ex(raw_var, &new_var, array_ptr TSRMLS_DC); 144 145 php_strip_tags(*val, val_len, NULL, NULL, 0); 146 147 *new_val_len = strlen(*val); 148 return 1; 149} 150 151PHP_FUNCTION(my_get_raw) 152{ 153 long arg; 154 char *var; 155 int var_len; 156 zval **tmp; 157 zval *array_ptr = NULL; 158 HashTable *hash_ptr; 159 char *raw_var; 160 161 if(zend_parse_parameters(2 TSRMLS_CC, "ls", &arg, &var, &var_len) == FAILURE) { 162 return; 163 } 164 165 switch(arg) { 166 case PARSE_GET: 167 array_ptr = IF_G(get_array); 168 break; 169 case PARSE_POST: 170 array_ptr = IF_G(post_array); 171 break; 172 case PARSE_COOKIE: 173 array_ptr = IF_G(post_array); 174 break; 175 } 176 177 if(!array_ptr) RETURN_FALSE; 178 179 /* 180 * I'm changing the variable name here because when running with register_globals on, 181 * the variable will end up in the global symbol table 182 */ 183 raw_var = emalloc(var_len+5); /* RAW_ and a \0 */ 184 strcpy(raw_var, "RAW_"); 185 strlcat(raw_var,var,var_len+5); 186 hash_ptr = HASH_OF(array_ptr); 187 188 if(zend_hash_find(hash_ptr, raw_var, var_len+5, (void **)&tmp) == SUCCESS) { 189 *return_value = **tmp; 190 zval_copy_ctor(return_value); 191 } else { 192 RETVAL_FALSE; 193 } 194 efree(raw_var); 195} 196 197