source: trunk/client/inc/csrf-magic/csrf-magic.php@ 153

Last change on this file since 153 was 153, checked in by luciano, 12 years ago

Protezione cfrs.
Per aggiungere un input token ai form
Aggiunge il campo secret alla tabella _config per la creazione del token

File size: 13.4 KB
Line 
1<?php
2
3/**
4 * @file
5 *
6 * csrf-magic is a PHP library that makes adding CSRF-protection to your
7 * web applications a snap. No need to modify every form or create a database
8 * of valid nonces; just include this file at the top of every
9 * web-accessible page (or even better, your common include file included
10 * in every page), and forget about it! (There are, of course, configuration
11 * options for advanced users).
12 *
13 * This library is PHP4 and PHP5 compatible.
14 */
15
16// CONFIGURATION:
17
18/**
19 * By default, when you include this file csrf-magic will automatically check
20 * and exit if the CSRF token is invalid. This will defer executing
21 * csrf_check() until you're ready. You can also pass false as a parameter to
22 * that function, in which case the function will not exit but instead return
23 * a boolean false if the CSRF check failed. This allows for tighter integration
24 * with your system.
25 */
26$GLOBALS['csrf']['defer'] = false;
27
28/**
29 * This is the amount of seconds you wish to allow before any token becomes
30 * invalid; the default is two hours, which should be more than enough for
31 * most websites.
32 */
33$GLOBALS['csrf']['expires'] = 7200;
34
35/**
36 * Callback function to execute when there's the CSRF check fails and
37 * $fatal == true (see csrf_check). This will usually output an error message
38 * about the failure.
39 */
40$GLOBALS['csrf']['callback'] = 'csrf_callback';
41
42/**
43 * Whether or not to include our JavaScript library which also rewrites
44 * AJAX requests on this domain. Set this to the web path. This setting only works
45 * with supported JavaScript libraries in Internet Explorer; see README.txt for
46 * a list of supported libraries.
47 */
48$GLOBALS['csrf']['rewrite-js'] = false;
49
50/**
51 * A secret key used when hashing items. Please generate a random string and
52 * place it here. If you change this value, all previously generated tokens
53 * will become invalid.
54 */
55$GLOBALS['csrf']['secret'] = '';
56
57/**
58 * Set this to false to disable csrf-magic's output handler, and therefore,
59 * its rewriting capabilities. If you're serving non HTML content, you should
60 * definitely set this false.
61 */
62$GLOBALS['csrf']['rewrite'] = true;
63
64/**
65 * Whether or not to use IP addresses when binding a user to a token. This is
66 * less reliable and less secure than sessions, but is useful when you need
67 * to give facilities to anonymous users and do not wish to maintain a database
68 * of valid keys.
69 */
70$GLOBALS['csrf']['allow-ip'] = true;
71
72/**
73 * If this information is available, use the cookie by this name to determine
74 * whether or not to allow the request. This is a shortcut implementation
75 * very similar to 'key', but we randomly set the cookie ourselves.
76 */
77$GLOBALS['csrf']['cookie'] = '__csrf_cookie';
78
79/**
80 * If this information is available, set this to a unique identifier (it
81 * can be an integer or a unique username) for the current "user" of this
82 * application. The token will then be globally valid for all of that user's
83 * operations, but no one else. This requires that 'secret' be set.
84 */
85$GLOBALS['csrf']['user'] = false;
86
87/**
88 * This is an arbitrary secret value associated with the user's session. This
89 * will most probably be the contents of a cookie, as an attacker cannot easily
90 * determine this information. Warning: If the attacker knows this value, they
91 * can easily spoof a token. This is a generic implementation; sessions should
92 * work in most cases.
93 *
94 * Why would you want to use this? Lets suppose you have a squid cache for your
95 * website, and the presence of a session cookie bypasses it. Let's also say
96 * you allow anonymous users to interact with the website; submitting forms
97 * and AJAX. Previously, you didn't have any CSRF protection for anonymous users
98 * and so they never got sessions; you don't want to start using sessions either,
99 * otherwise you'll bypass the Squid cache. Setup a different cookie for CSRF
100 * tokens, and have Squid ignore that cookie for get requests, for anonymous
101 * users. (If you haven't guessed, this scheme was(?) used for MediaWiki).
102 */
103$GLOBALS['csrf']['key'] = false;
104
105/**
106 * The name of the magic CSRF token that will be placed in all forms, i.e.
107 * the contents of <input type="hidden" name="$name" value="CSRF-TOKEN" />
108 */
109$GLOBALS['csrf']['input-name'] = '__csrf_magic';
110
111/**
112 * Set this to false if your site must work inside of frame/iframe elements,
113 * but do so at your own risk: this configuration protects you against CSS
114 * overlay attacks that defeat tokens.
115 */
116$GLOBALS['csrf']['frame-breaker'] = true;
117
118/**
119 * Whether or not CSRF Magic should be allowed to start a new session in order
120 * to determine the key.
121 */
122$GLOBALS['csrf']['auto-session'] = true;
123
124/**
125 * Whether or not csrf-magic should produce XHTML style tags.
126 */
127$GLOBALS['csrf']['xhtml'] = true;
128
129// FUNCTIONS:
130
131// Don't edit this!
132$GLOBALS['csrf']['version'] = '1.0.1';
133
134/**
135 * Rewrites <form> on the fly to add CSRF tokens to them. This can also
136 * inject our JavaScript library.
137 */
138function csrf_ob_handler($buffer, $flags) {
139 // Even though the user told us to rewrite, we should do a quick heuristic
140 // to check if the page is *actually* HTML. We don't begin rewriting until
141 // we hit the first <html tag.
142 static $is_html = false;
143 if (!$is_html) {
144 // not HTML until proven otherwise
145 if (stripos($buffer, '<html') !== false) {
146 $is_html = true;
147 } else {
148 return $buffer;
149 }
150 }
151 $tokens = csrf_get_tokens();
152 $name = $GLOBALS['csrf']['input-name'];
153 $endslash = $GLOBALS['csrf']['xhtml'] ? ' /' : '';
154 $input = "\n<div><input type='hidden' name='$name' value=\"$tokens\"$endslash></div>";
155 $buffer = preg_replace('#(<form[^>]*method\s*=\s*["\']post["\'][^>]*>)#i', '$1' . $input, $buffer);
156 if ($GLOBALS['csrf']['frame-breaker']) {
157 $buffer = str_ireplace('</head>', '<script type="text/javascript">if (top != self) {top.location.href = self.location.href;}</script></head>', $buffer);
158 }
159 if ($js = $GLOBALS['csrf']['rewrite-js']) {
160 $buffer = str_ireplace(
161 '</head>',
162 '<script type="text/javascript">'.
163 'var csrfMagicToken = "'.$tokens.'";'.
164 'var csrfMagicName = "'.$name.'";</script>'.
165 '<script src="'.$js.'" type="text/javascript"></script></head>',
166 $buffer
167 );
168 $script = '<script type="text/javascript">CsrfMagic.end();</script>';
169 $buffer = str_ireplace('</body>', $script . '</body>', $buffer, $count);
170 if (!$count) {
171 $buffer .= $script;
172 }
173 }
174 return $buffer;
175}
176
177/**
178 * Checks if this is a post request, and if it is, checks if the nonce is valid.
179 * @param bool $fatal Whether or not to fatally error out if there is a problem.
180 * @return True if check passes or is not necessary, false if failure.
181 */
182function csrf_check($fatal = true) {
183 if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true;
184 csrf_start();
185 $name = $GLOBALS['csrf']['input-name'];
186 $ok = false;
187 $tokens = '';
188 do {
189 if (!isset($_POST[$name])) break;
190 // we don't regenerate a token and check it because some token creation
191 // schemes are volatile.
192 $tokens = $_POST[$name];
193 if (!csrf_check_tokens($tokens)) break;
194 $ok = true;
195 } while (false);
196 if ($fatal && !$ok) {
197 $callback = $GLOBALS['csrf']['callback'];
198 if (trim($tokens, 'A..Za..z0..9:;,') !== '') $tokens = 'hidden';
199 $callback($tokens);
200 exit;
201 }
202 return $ok;
203}
204
205/**
206 * Retrieves a valid token(s) for a particular context. Tokens are separated
207 * by semicolons.
208 */
209function csrf_get_tokens() {
210 $has_cookies = !empty($_COOKIE);
211
212 // $ip implements a composite key, which is sent if the user hasn't sent
213 // any cookies. It may or may not be used, depending on whether or not
214 // the cookies "stick"
215 $secret = csrf_get_secret();
216 if (!$has_cookies && $secret) {
217 // :TODO: Harden this against proxy-spoofing attacks
218 $ip = ';ip:' . csrf_hash($_SERVER['IP_ADDRESS']);
219 } else {
220 $ip = '';
221 }
222 csrf_start();
223
224 // These are "strong" algorithms that don't require per se a secret
225 if (session_id()) return 'sid:' . csrf_hash(session_id()) . $ip;
226 if ($GLOBALS['csrf']['cookie']) {
227 $val = csrf_generate_secret();
228 setcookie($GLOBALS['csrf']['cookie'], $val);
229 return 'cookie:' . csrf_hash($val) . $ip;
230 }
231 if ($GLOBALS['csrf']['key']) return 'key:' . csrf_hash($GLOBALS['csrf']['key']) . $ip;
232 // These further algorithms require a server-side secret
233 if (!$secret) return 'invalid';
234 if ($GLOBALS['csrf']['user'] !== false) {
235 return 'user:' . csrf_hash($GLOBALS['csrf']['user']);
236 }
237 if ($GLOBALS['csrf']['allow-ip']) {
238 return ltrim($ip, ';');
239 }
240 return 'invalid';
241}
242
243/**
244 * @param $tokens is safe for HTML consumption
245 */
246function csrf_callback($tokens) {
247 header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
248 echo "<html><head><title>CSRF check failed</title></head><body>CSRF check failed. Please enable cookies.<br />Debug: ".$tokens."</body></html>
249";
250}
251
252/**
253 * Checks if a composite token is valid. Outward facing code should use this
254 * instead of csrf_check_token()
255 */
256function csrf_check_tokens($tokens) {
257 if (is_string($tokens)) $tokens = explode(';', $tokens);
258 foreach ($tokens as $token) {
259 if (csrf_check_token($token)) return true;
260 }
261 return false;
262}
263
264/**
265 * Checks if a token is valid.
266 */
267function csrf_check_token($token) {
268 if (strpos($token, ':') === false) return false;
269 list($type, $value) = explode(':', $token, 2);
270 if (strpos($value, ',') === false) return false;
271 list($x, $time) = explode(',', $token, 2);
272 if ($GLOBALS['csrf']['expires']) {
273 if (time() > $time + $GLOBALS['csrf']['expires']) return false;
274 }
275 switch ($type) {
276 case 'sid':
277 return $value === csrf_hash(session_id(), $time);
278 case 'cookie':
279 $n = $GLOBALS['csrf']['cookie'];
280 if (!$n) return false;
281 if (!isset($_COOKIE[$n])) return false;
282 return $value === csrf_hash($_COOKIE[$n], $time);
283 case 'key':
284 if (!$GLOBALS['csrf']['key']) return false;
285 return $value === csrf_hash($GLOBALS['csrf']['key'], $time);
286 // We could disable these 'weaker' checks if 'key' was set, but
287 // that doesn't make me feel good then about the cookie-based
288 // implementation.
289 case 'user':
290 if (!csrf_get_secret()) return false;
291 if ($GLOBALS['csrf']['user'] === false) return false;
292 return $value === csrf_hash($GLOBALS['csrf']['user'], $time);
293 case 'ip':
294 if (!csrf_get_secret()) return false;
295 // do not allow IP-based checks if the username is set, or if
296 // the browser sent cookies
297 if ($GLOBALS['csrf']['user'] !== false) return false;
298 if (!empty($_COOKIE)) return false;
299 if (!$GLOBALS['csrf']['allow-ip']) return false;
300 return $value === csrf_hash($_SERVER['IP_ADDRESS'], $time);
301 }
302 return false;
303}
304
305/**
306 * Sets a configuration value.
307 */
308function csrf_conf($key, $val) {
309 if (!isset($GLOBALS['csrf'][$key])) {
310 trigger_error('No such configuration ' . $key, E_USER_WARNING);
311 return;
312 }
313 $GLOBALS['csrf'][$key] = $val;
314}
315
316/**
317 * Starts a session if we're allowed to.
318 */
319function csrf_start() {
320 if ($GLOBALS['csrf']['auto-session'] && !session_id()) {
321 session_start();
322 }
323}
324
325/**
326 * Retrieves the secret, and generates one if necessary.
327 */
328function csrf_get_secret() {
329 if ($GLOBALS['csrf']['secret']) return $GLOBALS['csrf']['secret'];
330 // secret by db l.apolito
331 global $prefix,$dbi;
332 # crea campo secret nella tabella _config se non esiste
333 $campo= mysql_query("SHOW COLUMNS FROM ".$prefix."_config LIKE 'secret' ",$dbi);
334 $esiste=mysql_num_rows($campo);
335 if ($esiste==0) {
336 $result=mysql_query("ALTER TABLE ".$prefix."_config ADD secret VARCHAR(30);",$dbi);
337 }
338
339 $res_secret = mysql_query("SELECT * FROM ".$prefix."_config" , $dbi);
340 $row = mysql_fetch_array($res_secret);
341 $secret = $row['secret'];
342 if (isset($secret)){ return $secret;
343
344 }else{
345 $secret = csrf_generate_secret();
346 mysql_query("UPDATE ".$prefix."_config SET secret='$secret'" , $dbi);
347 return $secret;
348 }
349 return '';
350
351
352 /* nel caso di registrazione del file
353 $dir = dirname(__FILE__);
354 $file = $dir . '/csrf-secret.php';
355 $secret = '';
356 if (file_exists($file)) {
357 include $file;
358 return $secret;
359 }
360
361 if (is_writable($dir)) {
362 $secret = csrf_generate_secret();
363 $fh = fopen($file, 'w');
364 fwrite($fh, '<?php $secret = "'.$secret.'";' . PHP_EOL);
365 fclose($fh);
366 return $secret;
367 }
368 return '';
369 */
370}
371
372/**
373 * Generates a random string as the hash of time, microtime, and mt_rand.
374 */
375function csrf_generate_secret($len = 32) {
376 $r = '';
377 for ($i = 0; $i < 32; $i++) {
378 $r .= chr(mt_rand(0, 255));
379 }
380 $r .= time() . microtime();
381 return sha1($r);
382}
383
384/**
385 * Generates a hash/expiry double. If time isn't set it will be calculated
386 * from the current time.
387 */
388function csrf_hash($value, $time = null) {
389 if (!$time) $time = time();
390 return sha1($GLOBALS['csrf']['secret'] . $value . $time) . ',' . $time;
391}
392
393// Load user configuration
394if (function_exists('csrf_startup')) csrf_startup();
395// Initialize our handler
396if ($GLOBALS['csrf']['rewrite']) ob_start('csrf_ob_handler');
397// Perform check
398if (!$GLOBALS['csrf']['defer']) csrf_check();
Note: See TracBrowser for help on using the repository browser.