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