[ Index ] |
PHP Cross Reference of vtigercrm-6.1.0 |
[Summary view] [Print] [Text view]
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'] = 'vtResponseForIllegalAccess'; //'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'] = 'libraries/csrf-magic/csrf-magic.js'; 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 // nota bene: library code should use csrf_get_secret() and not access 57 // this global directly 58 59 /** 60 * Set this to false to disable csrf-magic's output handler, and therefore, 61 * its rewriting capabilities. If you're serving non HTML content, you should 62 * definitely set this false. 63 */ 64 $GLOBALS['csrf']['rewrite'] = true; 65 66 /** 67 * Whether or not to use IP addresses when binding a user to a token. This is 68 * less reliable and less secure than sessions, but is useful when you need 69 * to give facilities to anonymous users and do not wish to maintain a database 70 * of valid keys. 71 */ 72 $GLOBALS['csrf']['allow-ip'] = true; 73 74 /** 75 * If this information is available, use the cookie by this name to determine 76 * whether or not to allow the request. This is a shortcut implementation 77 * very similar to 'key', but we randomly set the cookie ourselves. 78 */ 79 $GLOBALS['csrf']['cookie'] = '__vtrfck'; // __csrf_cookie 80 81 /** 82 * If this information is available, set this to a unique identifier (it 83 * can be an integer or a unique username) for the current "user" of this 84 * application. The token will then be globally valid for all of that user's 85 * operations, but no one else. This requires that 'secret' be set. 86 */ 87 $GLOBALS['csrf']['user'] = false; 88 89 /** 90 * This is an arbitrary secret value associated with the user's session. This 91 * will most probably be the contents of a cookie, as an attacker cannot easily 92 * determine this information. Warning: If the attacker knows this value, they 93 * can easily spoof a token. This is a generic implementation; sessions should 94 * work in most cases. 95 * 96 * Why would you want to use this? Lets suppose you have a squid cache for your 97 * website, and the presence of a session cookie bypasses it. Let's also say 98 * you allow anonymous users to interact with the website; submitting forms 99 * and AJAX. Previously, you didn't have any CSRF protection for anonymous users 100 * and so they never got sessions; you don't want to start using sessions either, 101 * otherwise you'll bypass the Squid cache. Setup a different cookie for CSRF 102 * tokens, and have Squid ignore that cookie for get requests, for anonymous 103 * users. (If you haven't guessed, this scheme was(?) used for MediaWiki). 104 */ 105 $GLOBALS['csrf']['key'] = false; 106 107 /** 108 * The name of the magic CSRF token that will be placed in all forms, i.e. 109 * the contents of <input type="hidden" name="$name" value="CSRF-TOKEN" /> 110 */ 111 $GLOBALS['csrf']['input-name'] = '__vtrftk'; // __csrf_magic 112 113 /** 114 * Set this to false if your site must work inside of frame/iframe elements, 115 * but do so at your own risk: this configuration protects you against CSS 116 * overlay attacks that defeat tokens. 117 */ 118 $GLOBALS['csrf']['frame-breaker'] = true; 119 120 /** 121 * Whether or not CSRF Magic should be allowed to start a new session in order 122 * to determine the key. 123 */ 124 $GLOBALS['csrf']['auto-session'] = true; 125 126 /** 127 * Whether or not csrf-magic should produce XHTML style tags. 128 */ 129 $GLOBALS['csrf']['xhtml'] = true; 130 131 // FUNCTIONS: 132 133 // Don't edit this! 134 $GLOBALS['csrf']['version'] = '1.0.4'; 135 136 /** 137 * Rewrites <form> on the fly to add CSRF tokens to them. This can also 138 * inject our JavaScript library. 139 */ 140 function csrf_ob_handler($buffer, $flags) { 141 // Even though the user told us to rewrite, we should do a quick heuristic 142 // to check if the page is *actually* HTML. We don't begin rewriting until 143 // we hit the first <html tag. 144 static $is_html = false; 145 static $is_partial = false; 146 147 if (!$is_html) { 148 // not HTML until proven otherwise 149 if (stripos($buffer, '<html') !== false) { 150 $is_html = true; 151 } else { 152 153 // Customized to take the partial HTML with form 154 $is_html = true; 155 $is_partial = true; 156 157 // Determine based on content type. 158 $headers = headers_list(); 159 foreach ($headers as $header) { 160 if ($is_html) break; 161 else if (stripos('Content-type', $header) !== false && stripos('/html', $header) === false) { 162 $is_html = false; 163 } 164 } 165 166 if (!$is_html) return $buffer; 167 } 168 } 169 $count=1; 170 $tokens = csrf_get_tokens(); 171 $name = $GLOBALS['csrf']['input-name']; 172 $endslash = $GLOBALS['csrf']['xhtml'] ? ' /' : ''; 173 $input = "<input type='hidden' name='$name' value=\"$tokens\"$endslash>"; 174 $buffer = preg_replace('#(<form[^>]*method\s*=\s*["\']post["\'][^>]*>)#i', '$1' . $input, $buffer); 175 if ($GLOBALS['csrf']['frame-breaker'] && !$is_partial) { 176 $buffer = preg_replace('/<\/head>/', '<script type="text/javascript">if (top != self) {top.location.href = self.location.href;}</script></head>', $buffer,$count); 177 } 178 if (($js = $GLOBALS['csrf']['rewrite-js']) && !$is_partial) { 179 $buffer = preg_replace( 180 '/<\/head>/', 181 '<script type="text/javascript">'. 182 'var csrfMagicToken = "'.$tokens.'";'. 183 'var csrfMagicName = "'.$name.'";</script>'. 184 '<script src="'.$js.'" type="text/javascript"></script></head>', 185 $buffer,$count 186 ); 187 $script = '<script type="text/javascript">CsrfMagic.end();</script>'; 188 189 $buffer = preg_replace('/<\/body>/', $script . '</body>', $buffer, $count); 190 if (!$count) { 191 $buffer .= $script; 192 } 193 } 194 return $buffer; 195 } 196 197 /** 198 * Checks if this is a post request, and if it is, checks if the nonce is valid. 199 * @param bool $fatal Whether or not to fatally error out if there is a problem. 200 * @return True if check passes or is not necessary, false if failure. 201 */ 202 function csrf_check($fatal = true) { 203 if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true; 204 csrf_start(); 205 $name = $GLOBALS['csrf']['input-name']; 206 $ok = false; 207 $tokens = ''; 208 do { 209 if (!isset($_POST[$name])) break; 210 // we don't regenerate a token and check it because some token creation 211 // schemes are volatile. 212 $tokens = $_POST[$name]; 213 if (!csrf_check_tokens($tokens)) break; 214 $ok = true; 215 } while (false); 216 if ($fatal && !$ok) { 217 $callback = $GLOBALS['csrf']['callback']; 218 if (trim($tokens, 'A..Za..z0..9:;,') !== '') $tokens = 'hidden'; 219 $callback($tokens); 220 exit; 221 } 222 return $ok; 223 } 224 225 /** 226 * Retrieves a valid token(s) for a particular context. Tokens are separated 227 * by semicolons. 228 */ 229 function csrf_get_tokens() { 230 $has_cookies = !empty($_COOKIE); 231 232 // $ip implements a composite key, which is sent if the user hasn't sent 233 // any cookies. It may or may not be used, depending on whether or not 234 // the cookies "stick" 235 $secret = csrf_get_secret(); 236 if (!$has_cookies && $secret) { 237 // :TODO: Harden this against proxy-spoofing attacks 238 $ip = ';ip:' . csrf_hash($_SERVER['IP_ADDRESS']); 239 } else { 240 $ip = ''; 241 } 242 csrf_start(); 243 244 // These are "strong" algorithms that don't require per se a secret 245 if (session_id()) return 'sid:' . csrf_hash(session_id()) . $ip; 246 if ($GLOBALS['csrf']['cookie']) { 247 $val = csrf_generate_secret(); 248 setcookie($GLOBALS['csrf']['cookie'], $val); 249 return 'cookie:' . csrf_hash($val) . $ip; 250 } 251 if ($GLOBALS['csrf']['key']) return 'key:' . csrf_hash($GLOBALS['csrf']['key']) . $ip; 252 // These further algorithms require a server-side secret 253 if (!$secret) return 'invalid'; 254 if ($GLOBALS['csrf']['user'] !== false) { 255 return 'user:' . csrf_hash($GLOBALS['csrf']['user']); 256 } 257 if ($GLOBALS['csrf']['allow-ip']) { 258 return ltrim($ip, ';'); 259 } 260 return 'invalid'; 261 } 262 263 function csrf_flattenpost($data) { 264 $ret = array(); 265 foreach($data as $n => $v) { 266 $ret = array_merge($ret, csrf_flattenpost2(1, $n, $v)); 267 } 268 return $ret; 269 } 270 function csrf_flattenpost2($level, $key, $data) { 271 if(!is_array($data)) return array($key => $data); 272 $ret = array(); 273 foreach($data as $n => $v) { 274 $nk = $level >= 1 ? $key."[$n]" : "[$n]"; 275 $ret = array_merge($ret, csrf_flattenpost2($level+1, $nk, $v)); 276 } 277 return $ret; 278 } 279 280 /** 281 * @param $tokens is safe for HTML consumption 282 */ 283 function csrf_callback($tokens) { 284 // (yes, $tokens is safe to echo without escaping) 285 header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); 286 $data = ''; 287 foreach (csrf_flattenpost($_POST) as $key => $value) { 288 if ($key == $GLOBALS['csrf']['input-name']) continue; 289 $data .= '<input type="hidden" name="'.htmlspecialchars($key).'" value="'.htmlspecialchars($value).'" />'; 290 } 291 echo "<html><head><title>CSRF check failed</title></head> 292 <body> 293 <p>CSRF check failed. Your form session may have expired, or you may not have 294 cookies enabled.</p> 295 <form method='post' action=''>$data<input type='submit' value='Try again' /></form> 296 <p>Debug: $tokens</p></body></html> 297 "; 298 } 299 300 /** 301 * Function to echo response when CSRF check fails 302 * This should be helpful in production. For debigging use csrf_callback(). 303 * It is configurable by setting $GLOBALS['csrf']['callback'] in this file 304 */ 305 function vtResponseForIllegalAccess() { 306 echo 'Invalid request'; 307 } 308 309 /** 310 * Checks if a composite token is valid. Outward facing code should use this 311 * instead of csrf_check_token() 312 */ 313 function csrf_check_tokens($tokens) { 314 if (is_string($tokens)) $tokens = explode(';', $tokens); 315 foreach ($tokens as $token) { 316 if (csrf_check_token($token)) return true; 317 } 318 return false; 319 } 320 321 /** 322 * Checks if a token is valid. 323 */ 324 function csrf_check_token($token) { 325 if (strpos($token, ':') === false) return false; 326 list($type, $value) = explode(':', $token, 2); 327 if (strpos($value, ',') === false) return false; 328 list($x, $time) = explode(',', $token, 2); 329 if ($GLOBALS['csrf']['expires']) { 330 if (time() > $time + $GLOBALS['csrf']['expires']) return false; 331 } 332 switch ($type) { 333 case 'sid': 334 return $value === csrf_hash(session_id(), $time); 335 case 'cookie': 336 $n = $GLOBALS['csrf']['cookie']; 337 if (!$n) return false; 338 if (!isset($_COOKIE[$n])) return false; 339 return $value === csrf_hash($_COOKIE[$n], $time); 340 case 'key': 341 if (!$GLOBALS['csrf']['key']) return false; 342 return $value === csrf_hash($GLOBALS['csrf']['key'], $time); 343 // We could disable these 'weaker' checks if 'key' was set, but 344 // that doesn't make me feel good then about the cookie-based 345 // implementation. 346 case 'user': 347 if (!csrf_get_secret()) return false; 348 if ($GLOBALS['csrf']['user'] === false) return false; 349 return $value === csrf_hash($GLOBALS['csrf']['user'], $time); 350 case 'ip': 351 if (!csrf_get_secret()) return false; 352 // do not allow IP-based checks if the username is set, or if 353 // the browser sent cookies 354 if ($GLOBALS['csrf']['user'] !== false) return false; 355 if (!empty($_COOKIE)) return false; 356 if (!$GLOBALS['csrf']['allow-ip']) return false; 357 return $value === csrf_hash($_SERVER['IP_ADDRESS'], $time); 358 } 359 return false; 360 } 361 362 /** 363 * Sets a configuration value. 364 */ 365 function csrf_conf($key, $val) { 366 if (!isset($GLOBALS['csrf'][$key])) { 367 trigger_error('No such configuration ' . $key, E_USER_WARNING); 368 return; 369 } 370 $GLOBALS['csrf'][$key] = $val; 371 } 372 373 /** 374 * Starts a session if we're allowed to. 375 */ 376 function csrf_start() { 377 if ($GLOBALS['csrf']['auto-session'] && !session_id()) { 378 session_start(); 379 } 380 } 381 382 /** 383 * Retrieves the secret, and generates one if necessary. 384 */ 385 function csrf_get_secret() { 386 if ($GLOBALS['csrf']['secret']) return $GLOBALS['csrf']['secret']; 387 $dir = dirname(__FILE__); 388 $file = $dir . '/../../config.csrf-secret.php'; 389 $secret = ''; 390 if (file_exists($file)) { 391 include $file; 392 return $secret; 393 } 394 if (is_writable($dir)) { 395 $secret = csrf_generate_secret(); 396 $fh = fopen($file, 'w'); 397 fwrite($fh, '<?php $secret = "'.$secret.'";' . PHP_EOL); 398 fclose($fh); 399 return $secret; 400 } 401 return ''; 402 } 403 404 /** 405 * Generates a random string as the hash of time, microtime, and mt_rand. 406 */ 407 function csrf_generate_secret($len = 32) { 408 $r = ''; 409 for ($i = 0; $i < 32; $i++) { 410 $r .= chr(mt_rand(0, 255)); 411 } 412 $r .= time() . microtime(); 413 return sha1($r); 414 } 415 416 /** 417 * Generates a hash/expiry double. If time isn't set it will be calculated 418 * from the current time. 419 */ 420 function csrf_hash($value, $time = null) { 421 if (!$time) $time = time(); 422 return sha1(csrf_get_secret() . $value . $time) . ',' . $time; 423 } 424 425 // Load user configuration 426 if (function_exists('csrf_startup')) csrf_startup(); 427 // Initialize our handler 428 if ($GLOBALS['csrf']['rewrite']) ob_start('csrf_ob_handler'); 429 // Perform check 430 if (!$GLOBALS['csrf']['defer']) csrf_check();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:08:37 2014 | Cross-referenced by PHPXref 0.7.1 |