', '<', '>', "'", '"', '&', '$', '#', '{', '}', '[', ']', '=', ';', '?', '%20', '%22', '%3c', // < '%253c', // < '%3e', // > '%0e', // > '%28', // ( '%29', // ) '%2528', // ( '%26', // & '%24', // $ '%3f', // ? '%3b', // ; '%3d' // = ); //-------------------------------------------------------------------- /** * Security constructor. * * Stores our configuration and fires off the init() method to * setup initial state. * * @param \Config\App $config */ public function __construct($config) { // Store our CSRF-related settings $this->CSRFExpire = $config->CSRFExpire; $this->CSRFTokenName = $config->CSRFTokenName; $this->CSRFCookieName = $config->CSRFCookieName; $this->CSRFRegenerate = $config->CSRFRegenerate; if (isset($config->cookiePrefix)) { $this->CSRFCookieName = $config->cookiePrefix.$this->CSRFCookieName; } // Store cookie-related settings $this->cookiePath = $config->cookiePath; $this->cookieDomain = $config->cookieDomain; $this->cookieSecure = $config->cookieSecure; $this->CSRFSetHash(); unset($config); } //-------------------------------------------------------------------- /** * CSRF Verify * * @param RequestInterface $request * @return $this * @throws \LogicException */ public function CSRFVerify(RequestInterface $request) { // If it's not a POST request we will set the CSRF cookie if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') { return $this->CSRFSetCookie($request); } // Do the tokens exist in both the _POST and _COOKIE arrays? if ( ! isset($_POST[$this->CSRFTokenName], $_COOKIE[$this->CSRFCookieName]) || $_POST[$this->CSRFTokenName] !== $_COOKIE[$this->CSRFCookieName] ) // Do the tokens match? { throw new \LogicException('The action you requested is not allowed', 403); } // We kill this since we're done and we don't want to pollute the _POST array unset($_POST[$this->CSRFTokenName]); // Regenerate on every submission? if ($this->CSRFRegenerate) { // Nothing should last forever unset($_COOKIE[$this->CSRFCookieName]); } $this->CSRFSetHash(); $this->CSRFSetCookie($request); log_message('info', 'CSRF token verified'); return $this; } //-------------------------------------------------------------------- /** * CSRF Set Cookie * * @codeCoverageIgnore * @param RequestInterface $request * @return $this */ public function CSRFSetCookie(RequestInterface $request) { $expire = time() + $this->CSRFExpire; $secure_cookie = (bool)$this->cookieSecure; if ($secure_cookie && ! $request->isSecure()) { return false; } setcookie( $this->CSRFCookieName, $this->CSRFHash, $expire, $this->cookiePath, $this->cookieDomain, $secure_cookie, true // Enforce HTTP only cookie for security ); log_message('info', 'CSRF cookie sent'); return $this; } //-------------------------------------------------------------------- /** * Returns the current CSRF Hash. * * @return string */ public function getCSRFHash() { return $this->CSRFHash; } //-------------------------------------------------------------------- /** * Returns the CSRF Token Name. * * @return string */ public function getCSRFTokenName() { return $this->CSRFTokenName; } //-------------------------------------------------------------------- /** * Sets the CSRF Hash and cookie. * * @return string */ protected function CSRFSetHash() { if ($this->CSRFHash === null) { // If the cookie exists we will use its value. // We don't necessarily want to regenerate it with // each page load since a page could contain embedded // sub-pages causing this feature to fail if (isset($_COOKIE[$this->CSRFCookieName]) && is_string($_COOKIE[$this->CSRFCookieName]) && preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->CSRFCookieName]) === 1 ) { return $this->CSRFHash = $_COOKIE[$this->CSRFCookieName]; } $rand = random_bytes(16); $this->CSRFHash = bin2hex($rand); } return $this->CSRFHash; } //-------------------------------------------------------------------- /** * Sanitize Filename * * Tries to sanitize filenames in order to prevent directory traversal attempts * and other security threats, which is particularly useful for files that * were supplied via user input. * * If it is acceptable for the user input to include relative paths, * e.g. file/in/some/approved/folder.txt, you can set the second optional * parameter, $relative_path to TRUE. * * @param string $str Input file name * @param bool $relative_path Whether to preserve paths * * @return string */ public function sanitizeFilename($str, $relative_path = false) { $bad = $this->filenameBadChars; if ( ! $relative_path) { $bad[] = './'; $bad[] = '/'; } $str = remove_invisible_characters($str, false); do { $old = $str; $str = str_replace($bad, '', $str); } while ($old !== $str); return stripslashes($str); } //-------------------------------------------------------------------- }