diff --git a/api.php b/api.php index b3bdabb..b33c18c 100644 --- a/api.php +++ b/api.php @@ -14,6 +14,9 @@ include("session_verif.php"); include("bdd.php"); + include('php-csrf.php'); + $csrf = new CSRF(); + // Get the requested URL $request_uri = $_SERVER['REQUEST_URI']; @@ -177,8 +180,16 @@ if($_SERVER['REQUEST_METHOD'] === 'POST'){ verifier_session(); + switch(array_pop($url_parts)){ case "aj_doc": + + + if(!$csrf->validate($context='televersement',$_POST["jeton-csrf"])){ + echo( json_encode(["status"=> "2","msg"=>"jeton csrf manquant.".$_POST["jeton-csrf"]]) ); + break; + } + try{ ajouter_doc($_POST); @@ -188,6 +199,11 @@ break; case "valider_ensemble": + + if(!$csrf->validate($context='valider_ensemble',$_POST["jeton-csrf"])){ + echo( json_encode(["status"=> "2","msg"=>"jeton csrf manquant.".$_POST["jeton-csrf"]]) ); + break; + } try{ valider_ensemble($_POST["ensemble_id"]); echo(json_encode(["status"=>"1","msg"=>"Ensemble validé."])); @@ -197,6 +213,12 @@ break; case "supprimer_ensemble": + + if(!$csrf->validate($context='supprimer_ensemble',$_POST["jeton-csrf"])){ + echo( json_encode(["status"=> "2","msg"=>"jeton csrf manquant." ]) ); + break; + } + try{ supprimer_ensemble($_POST["ensemble_id"]); echo(json_encode(["status"=>"1","msg"=>"Ensemble supprimé."])); diff --git a/index.php b/index.php index 84d5770..5e9d48e 100644 --- a/index.php +++ b/index.php @@ -8,6 +8,7 @@ diff --git a/php-csrf.php b/php-csrf.php new file mode 100644 index 0000000..940c8b4 --- /dev/null +++ b/php-csrf.php @@ -0,0 +1,340 @@ +, + * + * ); + * // Generate an input for a form with a token + * // Tokens on the list are binded on a group so that + * // they can only be matched on that group + * // You can use as a group name the form name + * echo $csrf_tokens->input(); + */ +class CSRF { + + private $name; + private $hashes; + private $hashTime2Live; + private $hashSize; + private $inputName; + + /** + * Initialize a CSRF instance + * @param string $session_name Session name + * @param string $input_name Form name + * @param integer $hashTime2Live Default seconds hash before expiration + * @param integer $hashSize Default hash size in chars + */ + function __construct ($session_name='csrf-lib', $input_name='key-awesome', $hashTime2Live=0, $hashSize=64) { + // Session mods + $this->name = $session_name; + // Form input name + $this->inputName = $input_name; + // Default time before expire for hashes + $this->hashTime2Live = $hashTime2Live; + // Default hash size + $this->hashSize = $hashSize; + // Load hash list + $this->_load(); + } + + /** + * Generate a CSRF_Hash + * @param string $context Name of the form + * @param integer $time2Live Seconds before expiration + * @param integer $max_hashes Clear old context hashes if more than this number + * @return CSRF_Hash + */ + private function generateHash ($context='', $time2Live=-1, $max_hashes=5) { + // If no time2live (or invalid) use default + if ($time2Live < 0) $time2Live = $this->hashTime2Live; + // Generate new hash + $hash = new CSRF_Hash($context, $time2Live, $this->hashSize); + // Save it + array_push($this->hashes, $hash); + if ($this->clearHashes($context, $max_hashes) === 0) { + $this->_save(); + } + + // Return hash info + return $hash; + } + + /** + * Get the hashes of a context + * @param string $context the group to clean + * @param integer $max_hashes max hashes to get + * @return array array of hashes as strings + */ + public function getHashes ($context='', $max_hashes=-1) { + $len = count($this->hashes); + $hashes = array(); + // Check in the hash list + for ($i = $len - 1; $i >= 0 && $len > 0; $i--) { + if ($this->hashes[$i]->inContext($context)) { + array_push($hashes, $this->hashes[$i]->get()); + $len--; + } + } + return $hashes; + } + + /** + * Clear the hashes of a context + * @param string $context the group to clean + * @param integer $max_hashes ignore first x hashes + * @return integer number of deleted hashes + */ + public function clearHashes ($context='', $max_hashes=0) { + $ignore = $max_hashes; + $deleted = 0; + // Check in the hash list + for ($i = count($this->hashes) - 1; $i >= 0; $i--) { + if ($this->hashes[$i]->inContext($context) && $ignore-- <= 0) { + array_splice($this->hashes, $i, 1); + $deleted++; + } + } + if ($deleted > 0) { + $this->_save(); + } + return $deleted; + } + + /** + * Generate an input html element + * @param string $context Name of the form + * @param integer $time2Live Seconds before expire + * @param integer $max_hashes Clear old context hashes if more than this number + * @return integer html input element code as a string + */ + public function input ($context='', $time2Live=-1, $max_hashes=5) { + // Generate hash + $hash = $this->generateHash ($context, $time2Live, $max_hashes); + // Generate html input string + return ''; + } + + /** + * Generate a script html element with the hash variable + * @param string $context Name of the form + * @param string $name The name for the variable + * @param integer $time2Live Seconds before expire + * @param integer $max_hashes Clear old context hashes if more than this number + * @return integer html script element code as a string + */ + public function script ($context='', $name='', $declaration='var', $time2Live=-1, $max_hashes=5) { + // Generate hash + $hash = $this->generateHash ($context, $time2Live, $max_hashes); + // Variable name + if (strlen($name) === 0) { + $name = $this->inputName; + } + // Generate html input string + return ''; + } + + /** + * Generate a javascript variable with the hash + * @param string $context Name of the form + * @param string $name The name for the variable + * @param integer $time2Live Seconds before expire + * @param integer $max_hashes Clear old context hashes if more than this number + * @return integer html script element code as a string + */ + public function javascript ($context='', $name='', $declaration='var', $time2Live=-1, $max_hashes=5) { + // Generate hash + $hash = $this->generateHash ($context, $time2Live, $max_hashes); + // Variable name + if (strlen($name) === 0) { + $name = $this->inputName; + } + // Generate html input string + return $declaration . ' ' . $name . ' = ' . json_encode($hash->get()) . ';'; + } + + /** + * Generate a string hash + * @param string $context Name of the form + * @param integer $time2Live Seconds before expire + * @param integer $max_hashes Clear old context hashes if more than this number + * @return integer hash as a string + */ + public function string ($context='', $time2Live=-1, $max_hashes=5) { + // Generate hash + $hash = $this->generateHash ($context, $time2Live, $max_hashes); + // Generate html input string + return $hash->get(); + } + + /** + * Validate by context + * @param string $context Name of the form + * @return boolean Valid or not + */ + public function validate ($context='', $hash = null) { + // If hash was not given, find hash + if (is_null($hash)) { + if (isset($_POST[$this->inputName])) { + $hash = $_POST[$this->inputName]; + } + else if (isset($_GET[$this->inputName])) { + $hash = $_GET[$this->inputName]; + } + else { + return false; + } + } + + // Check in the hash list + for ($i = count($this->hashes) - 1; $i >= 0; $i--) { + if ($this->hashes[$i]->verify($hash, $context)) { + array_splice($this->hashes, $i, 1); + return true; + } + } + return false; + } + + + /** + * Load hash list + */ + private function _load () { + $this->hashes = array(); + // If there are hashes on the session + if (isset($_SESSION[$this->name])) { + // Load session hashes + $session_hashes = unserialize($_SESSION[$this->name]); + // Ignore expired + for ($i = count($session_hashes) - 1; $i >= 0; $i--) { + // If an expired found, the rest will be expired + if ($session_hashes[$i]->hasExpire()) { + break; + } + array_unshift($this->hashes, $session_hashes[$i]); + } + if (count($this->hashes) != count($session_hashes)) { + $this->_save(); + } + } + } + + /** + * Save hash list + */ + private function _save () { + $_SESSION[$this->name] = serialize($this->hashes); + } +} + +class CSRF_Hash { + + private $hash; + private $context; + private $expire; + + /** + * [__construct description] + * @param string $context [description] + * @param integer $time2Live Number of seconds before expiration + */ + function __construct($context, $time2Live=0, $hashSize=64) { + // Save context name + $this->context = $context; + + // Generate hash + $this->hash = $this->_generateHash($hashSize); + + // Set expiration time + if ($time2Live > 0) { + $this->expire = time() + $time2Live; + } + else { + $this->expire = 0; + } + } + + /** + * The hash function to use + * @param int $n Size in bytes + * @return string The generated hash + */ + private function _generateHash ($n) { + return bin2hex(openssl_random_pseudo_bytes($n/2)); + } + + /** + * Check if hash has expired + * @return boolean + */ + public function hasExpire () { + if ($this->expire === 0 || $this->expire > time()) { + return false; + } + return true; + } + + /** + * Verify hash + * @return boolean + */ + public function verify ($hash, $context='') { + if (strcmp($context, $this->context) === 0 && !$this->hasExpire() && hash_equals($hash, $this->hash)) { + return true; + } + return false; + } + + /** + * Check Context + * @return boolean + */ + public function inContext ($context='') { + if (strcmp($context, $this->context) === 0) { + return true; + } + return false; + } + + /** + * Get hash + * @return string + */ + public function get () { + return $this->hash; + } +} \ No newline at end of file diff --git a/televerser.php b/televerser.php index 1720b58..4375de0 100644 --- a/televerser.php +++ b/televerser.php @@ -8,7 +8,11 @@ @@ -61,7 +65,10 @@ function uploadFiles() { i ++; } - // Append captured images as files to the FormData + //csrf token + formData.append("jeton-csrf","string($context="televersement")?>"); + + // Append captured images as files to the FormData const capturedImages = document.querySelectorAll('#selectedImages img'); i = 0; diff --git a/validation.php b/validation.php index e197822..954f7eb 100644 --- a/validation.php +++ b/validation.php @@ -1,8 +1,12 @@