Source of trunk/safe_eval.php at revision 424 (04/21/2009 8:04:49, 7522 bytes, 242 lines, language: php) [download]:

1
<?php
2
3
/*
4
 * Safe Eval Functions
5
 * by William R. Fraser <wrf@codewise.org> 3/16/2009
6
 * Copyright (c) 2009 Codewise.org
7
 */
8
9
/*
10
 * !!! WARNING !!!
11
 *   This file initializes its function blacklist at include-time, so be sure
12
 * to only include this file ONLY AFTER all other functions have been declared.
13
 */
14
15
$safe_eval_cache = array();
16
17
list($safe_eval_blacklist_funcs$safe_eval_whitelist_vars) = safe_eval_init();
18
19
/*
20
** Builds the list of allowed and disallowed things in eval expressions.
21
**
22
** Returns an array with the following contents:
23
**  0 => blacklisted functions
24
**           This is a list of all defined functions, minus the ones in the
25
**           whitelist.
26
**  1 => whitelisted vars
27
**           This is a list of superglobal variables to allow. All others
28
**           should be blocked.
29
*/
30
function safe_eval_init()
31
{
32
    // which superglobals do we want to allow access to?
33
    $allowed_vars = array("_GET""_POST""_SERVER""_REQUEST""_FILES"
34
        "_COOKIE");
35
36
    // language constructs to disallow
37
    // these need to be explicitly specified since they're not included in the
38
    // get_defined_functions() list
39
    $blacklist = array("++""--""=""+=""-=""*=""/="".=""%=",
40
        "&=""|=""^-""<<="">>=""include""require""if""else",
41
        "while""for""switch""exit""break""print""echo");
42
43
    // which functions do we want to allow?
44
    $whitelist = array("count""isset""substr""str_replace""htmlentities""html_entity_decode");
45
46
    $funcs get_defined_functions();
47
    $funcs array_merge($funcs['internal'], $funcs['user']);
48
49
    foreach ($whitelist as $entry) {
50
        unset($funcs[array_search($entry$funcs)]);
51
    }
52
53
    $blacklist array_merge($blacklist$funcs);
54
55
    return array($blacklist$allowed_vars);
56
}
57
58
/*
59
** Perform a safe eval() call
60
**
61
** $code is the code to eval
62
** $environment is an array ('name' => $value) of variables to allow $code
63
**     to access
64
*/
65
function safe_eval($code$environment = array())
66
{
67
    global $safe_eval_cache$safe_eval_blacklist_funcs$safe_eval_whitelist_vars;
68
69
    // munge the code for easy checking
70
    $stripped_code safe_eval_strip_code($code);
71
72
    foreach ($safe_eval_blacklist_funcs as $bad) {
73
        if (($where strpos($stripped_code$bad ")) !== false) {
74
            echo "code has blacklisted function/construct $bad at character $where\n";
75
            echo "<pre>".htmlspecialchars($code)."</pre>";
76
            exit;
77
        }
78
    }
79
80
    $allowed_vars array_merge($safe_eval_whitelist_varsarray_keys($environment));
81
82
    //foreach (array_keys($GLOBALS) as $global) {
83
    //    if (strpos($global, "_") !== 0) // only care about superglobals
84
    //        continue;
85
    foreach (array("_ENV""_POST""_GET""_COOKIE""_SERVER""_FILES",
86
            "_REQUEST""_SESSION") as $global) {
87
        if (in_array($globals$allowed_vars))
88
            continue;
89
        if (($where strpos($stripped_code" \$bad ")) !== false) {
90
            echo "code has blacklisted variable $bad at character $where";
91
            echo "<pre>".htmlspecialchars($code)."</pre>";
92
            exit;
93
        }
94
    }
95
96
    // if the code passed, save it so we don't have to re-munge it next time
97
    $safe_eval_cache[$code] = true;
98
99
    // import the environment
100
    foreach ($environment as $name => $value) {
101
        $$name $value;
102
    }
103
104
    return eval($code);
105
}
106
107
/*
108
 * This elaborate state machine's purpose is to strip out all non-symbolic
109
 * text in the given piece of code so the safe_eval function can search it for
110
 * badness.
111
 *
112
 * It returns a string with all constructs/functions/variables separated by
113
 * whitespace. Variables start with '$'.
114
 *
115
 * Example: "foreach (glob("plugins/*.php{$var['hax']} $foo" as $plugin) {"
116
 * becomes: " foreach  glob  $db    $var $plugin  "
117
 */
118
function safe_eval_strip_code($code)
119
{
120
    $stripped_code "";
121
    $state = array("not in str");
122
    for ($i 0$i strlen($code); $i++) {
123
        switch ($state[count($state) - 1]) {
124
        case "not in str":
125
        case "dq braced var":
126
            switch ($code[$i]) {
127
            case "\\":
128
                $i++;
129
                break;
130
            case "\"":
131
                array_push($state"dqstr");
132
                $stripped_code .= " ";
133
                break;
134
            case "'":
135
                array_push($state"sqstr");
136
                $stripped_code .= " ";
137
                break;
138
            case "+":
139
                if ($code[$i+1] == "+") {
140
                    $i++;
141
                    $stripped_code .= " ++ ";
142
                } else
143
                    $stripped_code .= " ";
144
                break;
145
            case "-":
146
                if ($code[$i+1] == "-") {
147
                    $i++;
148
                    $stripped_code .= " -- ";
149
                } else
150
                    $stripped_code .= " ";
151
                break;
152
            case "=":
153
                $operator "=";
154
                if ($code[$i+1] != "=") {
155
                    // continue to the end of the operator and then work back
156
                    // they all end with '='
157
                    for ($j $i 1$j >= 0$j--) {
158
                        if (preg_match("#[<>^!=+/*%&-]#"$code[$j])) {
159
                            $operator $code[$j].$operator;
160
                        } else {
161
                            $stripped_code .= $operator ";
162
                            break;
163
                        }
164
                    }
165
                }
166
                break;
167
            case "}":
168
                if ($state[count($state) - 1] == "dq braced var")
169
                    array_pop($state);
170
            case "{":
171
            case "?":
172
            case ":":
173
            case "(":
174
            case ")":
175
            case "[":
176
            case "]":
177
            case "|":
178
            case "&":
179
            case "!":
180
            case "~":
181
            case "^":
182
            case "<":
183
            case ">":
184
            case "%":
185
            case ".":
186
            case ";":
187
            case "\n":
188
            case "\t":
189
                $stripped_code .= " ";
190
                break;
191
            default:
192
                if (!preg_match("/[0-9]/"$code[$i]))
193
                    $stripped_code .= $code[$i];
194
                break;
195
            }
196
            break;
197
        case "dqstr":
198
            switch ($code[$i]) {
199
            case "\\":
200
                $i++;
201
                break;
202
            case "$":
203
                array_push($state"dq var");
204
                $stripped_code .= "$";
205
                break;
206
            case "{":
207
                if ($code[++$i] == "$") {
208
                    array_push($state"dq braced var");
209
                    $stripped_code .= "$";
210
                }
211
                break;
212
            case "\"":
213
                array_pop($state);
214
                break;
215
            }
216
            break;
217
        case "sqstr":
218
            switch ($code[$i]) {
219
            case "\\":
220
                $i++;
221
                break;
222
            case "'":
223
                array_pop($state);
224
                break;
225
            }
226
            break;
227
        case "dq var":
228
            if ($code[$i-1] == "$" ? !preg_match("/[a-zA-Z_\x7f-\xff]/"$code[$i]) : !preg_match("/[a-zA-Z0-9_\x7f-\xff]/"$code[$i])) {
229
                array_pop($state);
230
                $stripped_code .= " ";
231
            } else {
232
                $stripped_code .= $code[$i];
233
            }
234
            break;
235
        }
236
    }
237
238
    return $stripped_code ";
239
}
240
241
?>
242

powered by Codewise Manager v0.1-DEV :: 60.28ms, 6 ops, 3 queries