I have a question about tokens. I understand that they are random characters used for security purposes but just how do they work and what do they protect against?
Authentification mechanism creates a token when form displayed, and was stored it on server side.
Also auth mechanism adds token as hidden input to form. When you send it, auth system check is it in server-side storage.
If token found, authentification process will continue and token was removing.
It protects from spamming form action script.
Example using with logout url:
<?php
// Generate token
$logout_token = md5(microtime().random(100, 999));
session_start();
// Store token in session
if (!is_array($_SESSION['logout_tokens']) {
$_SESSION['logout_tokens'] = array();
}
$_SESSION['logout_tokens'][] = $logout_token;
?>
logout
Script, that processing logout:
<?php
$done = false;
if (!empty($_GET['logout_token'])) {
// Get token from url
$logout_token = $_GET['logout_token'];
session_start();
if (!is_array($_SESSION['logout_tokens']) {
$_SESSION['logout_tokens'] = array();
}
// Search get token in session (server-side storage)
if (($key = array_search($logout_token, $_SESSION['logout_tokens'], true)) !== false) {
// Remove used token from storage
unset($_SESSION['logout_tokens'][$key]);
// Do logout
$done = true;
}
}
if ($done === false) {
echo "Something went wrong.";
}
Related
I've tried to be as specific as possible, but I'm sorry that the subject of my question may be broad.
I got used to a habit of sending variables using the $_GET['variable'], for instance, let's say I'm using ajax to get some information from a database, I would probably do something like this:
xmlhttp.open("GET","read.php?option=someoption",true);
And then I would set the PHP page in a way that behave differently according to the $_GET['option'] it would receive.
I then realised that any logged-in user could type the URL and directly modify the database, so I've set some additional $_SESSION['redirect'] variables before each redirection to help prevent access to php pages from URL. By doing a quick ajax call to a "prevent.php" page that would do something like so:
$_SESSION['redirect'] = "true";
header("Location: page.php");
And then having it set this way in the page.php for instance:
if ($_SESSION['redirect']==true) {
// access the database
}
else {
// deny access
}
Is this a reliable way of doing things, or is there a more professional way to sort it out?
No it's not a secure way of doing it.
Here's an example of how you could achieve a secure user system in the simplest of forms:
login.php
<?php
session_start();
$user = isset($_POST['username']) ? $_POST['username'] : false;
$pass = isset($_POST['password']) ? $_POST['password'] : false;
# Check credentials
if (validCredentials($user, $pass)) {
# Credentials are valid
# Set isAdmin session for the user 'admin'
# This is hardcoded for simplicity but
# you could read a value from a database
# and set this session dynamically.
if ($user === 'admin') {
$_SESSION['isAdmin'] = true;
}
# Generate CSRF token (see appendix)
# heads up: random_bytes() is a PHP7 function.
$_SESSION['token'] = bin2hex(random_bytes(32));
# Set logged in session for every user
$_SESSION['user'] = $user;
echo 'Successfully logged in!<br />';
echo 'Go to the user page.';
}
admin-page.php:
<?php
session_start();
if (isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true) {
echo 'Only the admin can see this.';
} else {
echo 'You are either not logged in or you don\'t have the permission to view this page.';
}
user-page.php:
<?php
session_start();
if (isset($_SESSION['user'])) {
$token = $_SESSION['token'];
echo 'Only logged in users can see this. <br />';
echo 'Log me out.';
} else {
echo 'You are not logged in.';
}
Appendix:
Make sure that you protect yourself against CSRF attacks:
For example an insecure way of logging an user out would be:
logout.php:
<?php
session_start();
if (isset($_SESSION['user'])) {
session_destroy();
}
Maybe you ask yourself why this is not secure.
The reason is because for every user the logout link is the same (example.com/logout.php).
So it's not hard at all to guess the logout link (well, we don't even have to guess, we already know for sure).
An attacker could disguise the logout link and as soon as you click on it you would be logged out.
It's very important to understand that the logout is just one example.
Think of a more severe action like deleting a user etc.
So this concept applies to every action an authenticated user can do.
To be safe, you can generate a token as soon as the user has logged in.
For every action taken you then check if the token in the request matches the one you generated.
This way the logout link is unique for every user (example.com/loogut.php?token=random_token_generated_at_login) and is only hardly guessable by an attacker.
safe-logout.php:
<?php
session_start();
if (isset($_SESSION['user'])) {
# Check if the user specified token matches ours
$token = isset($_GET['token']) ? $_GET['token'] : false;
if ($_SESSION['token'] === $token) {
session_destroy();
echo 'Successfully logged out!';
} else {
# We dont logout because the token was not valid
}
}
And NO: a POST request is just as susceptible as a GET request.
So make sure you check the token for every action, regardless of the HTTP method used.
In this PHP script, once the user authenticates himself with his username and password, his user id is stored as an encrypted session variable to be identified throughout the web app. I was wondering if this is a good practice. If not, could you suggest some better alternatives? Can anything be done to avert session hijacking?
(I use the same method for external oauth logins too).
Below is the code snippet:
//------Check POST data
if(!$check_email && !$check_password && !$check_request):
$user = new user_auth($email);
$authenticated = $user->user_login($password);
$status = $user->get_user_status();
if($status==100 && $authenticated)
{
session_start();
//-----Returns an encrypted user id
$_SESSION['account'] = $user->get_user_id();
$response = $redirect;
}
else
$response = ':: Authentication failed - try again ::';
I've got a small problem with my smarty project, logout problem to be precise. I have a index.php page which is the "main" page and it gets POST data and directs actions based on current data. There`s checking if the session variables has been set. Now when I login I have function like this:
function login($value)
{
$res = $this->sql->checkLogin($value);
if($res)
{
//checks if user is admin
$isadm = $this->sql->isAdm($value);
if($isadm == true)
{
$_SESSION['user'] = $value['name'];
$_SESSION['adm'] = true;
$message = 'Admin';
$this->tpl->assign('var', $message);
if($_SESSION['adm'] == true)
{
//sets some variables for admin users
$navigation = 'navi';
$this->tpl->assign('navigation', $navigation);
}
$this->tpl->display('maint_main.tpl');
}
//user is not admin
else
{
$_SESSION['user'] = $value['name'];
$_SESSION['adm'] = false;
$message = 'Perus';
$this->tpl->assign('var', $message);
if($_SESSION['adm'] == true)
{
$navigation = 'navi';
$this->tpl->assign('navigation', $navigation);
}
$this->tpl->display('maint_main.tpl');
}
}
//login failes, show login form and info
else
{
$message = 'Login failed';
$this->tpl->assign('var', $message);
$this->tpl->display('login_form.tpl');
}
}
and logout function :
function logout()
{
setcookie(session_name(), '', time()-42000, '/');
session_unset();
session_destroy();
$this->tpl->display('login_form.tpl');
}
These work just about the way they are supposed to but the real problem occurs when I log out and redirect to the login_form.tpl. If I use the back button of the browser the POST data with username and password is retrieved and the login goes through again. This causes that those pages behind login are still viewable. As I am not quite familiar with Smarty yet I couldn`t figure out any way to fix this. So basically how to prevent access to that POST data after logout?
I don't think this has anything to do with smarty. This is a browser/http generic issue. Most browsers will re-post form data after confirmation from the user.
One approach to make re-posts of the form invalid would be to pass along a secret code/token (perhaps a guid or your session id) which is also stored in session data. When the user logs out, clear their session (or at least the secret code you're checking). When the user logs in, check to make sure that the confirmation code matches the one for the current session.
This pattern is often used to manage csrf attacks and is often known as a 'synchronizer token'. This blog post provides a good explanation https://blog.whitehatsec.com/tag/synchronizer-token/
I have read a lot about it but i still don't completely get it.
I may use a library of an existing solution in the future but i want to understand and implement my own system right now.
In order to be stateless and scalable I think i mustn't store user context on server.
The main problem is a conception one, if i understand the system i will succeed to code it
I have tested code found on Internet which i have modified (french website ref : http://blog.nalis.fr/index.php?post/2009/09/28/Securisation-stateless-PHP-avec-un-jeton-de-session-(token)-protection-CSRF-en-PHP).
Can you tell me if it's correct or if i don't get it?
So to create a token i use this function which takes as parameters, the user's data
define('SECRET_KEY', "fakesecretkey");
function createToken($data)
{
/* Create a part of token using secretKey and other stuff */
$tokenGeneric = SECRET_KEY.$_SERVER["SERVER_NAME"]; // It can be 'stronger' of course
/* Encoding token */
$token = hash('sha256', $tokenGeneric.$data);
return array('token' => $token, 'userData' => $data);
}
So a user can authentified himself and receive an array which contains a token (genericPart + his data, encoded), and hisData not encoded :
function auth($login, $password)
{
// we check user. For instance, it's ok, and we get his ID and his role.
$userID = 1;
$userRole = "admin";
// Concatenating data with TIME
$data = time()."_".$userID."-".$userRole;
$token = createToken($data);
echo json_encode($token);
}
Then the user can send me his token + his un-encoded data in order to check :
define('VALIDITY_TIME', 3600);
function checkToken($receivedToken, $receivedData)
{
/* Recreate the generic part of token using secretKey and other stuff */
$tokenGeneric = SECRET_KEY.$_SERVER["SERVER_NAME"];
// We create a token which should match
$token = hash('sha256', $tokenGeneric.$receivedData);
// We check if token is ok !
if ($receivedToken != $token)
{
echo 'wrong Token !';
return false;
}
list($tokenDate, $userData) = explode("_", $receivedData);
// here we compare tokenDate with current time using VALIDITY_TIME to check if the token is expired
// if token expired we return false
// otherwise it's ok and we return a new token
return createToken(time()."#".$userData);
}
$check = checkToken($_GET['token'], $_GET['data']);
if ($check !== false)
echo json_encode(array("secureData" => "Oo")); // And we add the new token for the next request
Am I right?
Sorry for this long message and sorry for my english.
Thanks in advance for your help!
The problem in your code is: You are basing your entire system on $_GET in the original post is based on Cookies.. You should store the token in cookies (based on your original post, instead of using $_GET
By the way; a few tweaks:
list($tokenDate, $userData) = array_pad(explode("_", $receivedData));
In the next code I don't see how you use $login,$password
function auth($login, $password)
{
// we check user. For instance, it's ok, and we get his ID and his role.
$userID = 1;
$userRole = "admin";
// Concatenating data with TIME
$data = time()."_".$userID."-".$userRole;
$token = createToken($data);
echo json_encode($token);
}
I have run in to the following problem regarding XSRF tokens.
Client: AngularJS
Server: PHP
When the index.php is hit, PHP generates an XSRF token and saves it in a session.
A cookie is set with same value.
AngularJS reads the cookie and stores the value.
On subsequent POSTS, the XSRF token is sent as a header, and the idea is to compare the stored session token to the sent header.
Everything seems fine, no problems whatsoever.
BUT: the issue is, that PHP cannot read the session registered in index.php, because technically there have been no page reloads! If I hit F5 and reloads everything , the session is read nicely.
How can I set the XSRF Session token on index.php and have it available for subsequent ajax requests from the client?? I'm pulling out my hair on this one... appreciate feedback.
UPDATE
After changing the session identifier name, everything suddenly worked!
In index.php:
// Create token and set session
session_start();
$token = hash('sha256', uniqid(mt_rand(), true));
$_SESSION['XSRF']=$token;
Later, also in index.php:
/* Give token to Angular client */
<script>
angular.module("app").constant("CSRF_TOKEN", '<?=$_SESSION['XSRF'];?>');
</script>
Note that I'm not using a cookie, instead I set a constant which is then made available to the .run method in Angular:
in Angular:
angular.module('app').run(['CSRF_TOKEN','$http',function(CSRF_TOKEN,$http) {
$http.defaults.headers.common['CSRF_TOKEN'] = CSRF_TOKEN;
All requests to the server are routed to one common php file. The file checks if the header is set, and compares the two tokens:
// Only POST requests are checked (I don't use PUT/DELETE)
if($_SERVER['REQUEST_METHOD']=="POST"){
session_start();
$headerToken = $_SERVER['HTTP_CSRF_TOKEN'];
$sessionToken = $_SESSION['XSRF'];
if($headerToken!=$sessionToken){
header('HTTP/1.0 401 Unauthorized');
exit;
}
}
This is what I'm doing in my PHP/AngularJS projects:
index.php
session_start();
if (!isset($_SESSION['XSRF-TOKEN'])) {
$uniqueValues = md5($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']); //add more/less/any "unique" values, see comments
$_SESSION['XSRF-TOKEN'] = sha1(uniqid(microtime() . $uniqueValues, true));
setcookie('XSRF-TOKEN', $_SESSION['XSRF-TOKEN']);
}
any script called by AngularJS $http:
(AngluarJS uses the value of the cookie XSRF-TOKEN and will send it in every request as X-XSRF-TOKEN custom header, so we need to compare this value to the value stored in the session.)
function verifyXSRF() {
/*
$headers = apache_request_headers();
$headerToken = "";
foreach ($headers as $header => $value) {
if ($header == "X-XSRF-TOKEN") {
$headerToken = $value;
break;
}
}
*/
//more efficient, see comments
$headerToken = $_SERVER['HTTP_X_XSRF_TOKEN'];
if ($headerToken != $_SESSION['XSRF-TOKEN']) return false;
return true;
}
session_start();
if (!verifyXSRF()) die("XSRF error");
Feedback welcome as I don't know exactly if this is enough XSRF protection.