Source of trunk/safe_eval.php at revision HEAD (07/02/2010 4:07:29, 7646 bytes, 245 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
    $code preg_replace("#/\\*.*\\*/#Us"""$code);
122
    $code preg_replace("#//.*#"""$code);
123
    $state = array("not in str");
124
    for ($i 0$i strlen($code); $i++) {
125
        switch ($state[count($state) - 1]) {
126
        case "not in str":
127
        case "dq braced var":
128
            switch ($code[$i]) {
129
            case "\\":
130
                $i++;
131
                break;
132
            case "\"":
133
                array_push($state"dqstr");
134
                $stripped_code .= " ";
135
                break;
136
            case "'":
137
                array_push($state"sqstr");
138
                $stripped_code .= " ";
139
                break;
140
            case "+":
141
                if ($code[$i+1] == "+") {
142
                    $i++;
143
                    $stripped_code .= " ++ ";
144
                } else
145
                    $stripped_code .= " ";
146
                break;
147
            case "-":
148
                if ($code[$i+1] == "-") {
149
                    $i++;
150
                    $stripped_code .= " -- ";
151
                } else
152
                    $stripped_code .= " ";
153
                break;
154
            case "=":
155
                $operator "=";
156
                if ($code[$i+1] != "=") {
157
                    // continue to the end of the operator and then work back
158
                    // they all end with '='
159
                    for ($j $i 1$j >= 0$j--) {
160
                        if (preg_match("#[<>^!=+/*%&-]#"$code[$j])) {
161
                            $operator $code[$j].$operator;
162
                        } else {
163
                            $stripped_code .= $operator ";
164
                            break;
165
                        }
166
                    }
167
                }
168
                break;
169
            case "}":
170
                if ($state[count($state) - 1] == "dq braced var")
171
                    array_pop($state);
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 ".":
188
            case ";":
189
            case ",":
190
            case "\n":
191
            case "\t":
192
                $stripped_code .= " ";
193
                break;
194
            default:
195
                if (!preg_match("/[0-9]/"$code[$i]))
196
                    $stripped_code .= $code[$i];
197
                break;
198
            }
199
            break;
200
        case "dqstr":
201
            switch ($code[$i]) {
202
            case "\\":
203
                $i++;
204
                break;
205
            case "$":
206
                array_push($state"dq var");
207
                $stripped_code .= "$";
208
                break;
209
            case "{":
210
                if ($code[++$i] == "$") {
211
                    array_push($state"dq braced var");
212
                    $stripped_code .= "$";
213
                }
214
                break;
215
            case "\"":
216
                array_pop($state);
217
                break;
218
            }
219
            break;
220
        case "sqstr":
221
            switch ($code[$i]) {
222
            case "\\":
223
                $i++;
224
                break;
225
            case "'":
226
                array_pop($state);
227
                break;
228
            }
229
            break;
230
        case "dq var":
231
            if ($code[$i-1] == "$" ? !preg_match("/[a-zA-Z_\x7f-\xff]/"$code[$i]) : !preg_match("/[a-zA-Z0-9_\x7f-\xff]/"$code[$i])) {
232
                array_pop($state);
233
                $stripped_code .= " ";
234
            } else {
235
                $stripped_code .= $code[$i];
236
            }
237
            break;
238
        }
239
    }
240
241
    return $stripped_code ";
242
}
243
244
?>
245

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