session_start();
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
This is how I start my code.
I , then, pass taht $token variable to a hidden field in the form:
<input type="hidden" name="token" value="<?php echo $token; ?>" />
but that condition is never true:
if($_POST['token'] == $_SESSION['token'])
{
echo 'Session was valid<br/>';
}
why?
Because you regenerate your token BEFORE checking it. You have to change it AFTER you're sure it's not the same
On page submit, a new value will be assigned to the session.
Try,
session_start();
if(!isset($_SESSION['token']))
{
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
}
Try echoing $_SESSION['token'] into the form instead of $token, in case you accidentally re-assigned it between the creation of the token and the form output.
I once had a problem very similar to this when I was developing something that took me ages to find, and it turned out that it was because I had left an <img src=''> in, because I didn't yet have the image - this caused the page to be requested again, since an empty src means 'this page', so the token was re-generated after the page was loaded.
Another thing to check is if the form's action attribute is missing, or some value that points to itself, the token maybe regenerated when the form is submitted, before the if (...) statement.
You could do something like this in order to prevent resetting the token on every page load:
session_start();
if (!isset($_SESSION['token']))
{
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
}
But... you may be better off session.referer_check or using some open source Session class that incorporates finer grained security than tacking this on top of the procedural standard. (if done correctly your code won't have to change wherever you actually use the session.)
see php.net/session_set_save_handler for more information.
the Code you provide is in one file and you submitting it to itself.
that regenerate the token and assign to the session variable. As said by "AVD" is the code to prevent the reassigning it
session_start();
if(empty($_SESSION['token'])){
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
}
then you can compare your condition where you are going to check.
Related
I want to correctly implement a CSRF token with validation into the forms of my website.
Here is how the token is generated:
$_SESSION["token"] = bin2hex(random_bytes(32));
Here is the hidden field inside of my form that changes every time the form is submitted or every time the page is refreshed:
<input type="hidden" name="token" value="<?=$_SESSION["token"]?>">
Here is an example of what is seen when the form in inspected:
<input type="hidden" name="token" value="3c33c5dc178293f9bcaff264b90836780887efe16c339d01c1cbe34bf9ecbddd">
Now when the form is submitted this is the validation I put in place for the token:
if($_SERVER["REQUEST_METHOD"] == "POST")
{
//CSRF TOKEN + VALIDATION
if (!isset($_POST['token']) || ($_POST['token'] !== $_SESSION['token'])) {
exit;
}
I want to know if this validating that the ($_POST['token'] !== $_SESSION['token']) ?
Because when I change my script to this:
if($_SERVER["REQUEST_METHOD"] == "POST")
{
//CSRF TOKEN + VALIDATION
if (!isset($_POST['token'])) {
exit;
}
Nothing changes and the form is submitted exactly like before.
Is my original IF statement only checking if a $_POST isset?
If so, is this still a secure way to protect against CSRF attacks?
if (!isset($_POST['token']) || ($_POST['token'] !== $_SESSION['token'])) {
This checks:
If there is a token (and fails if there isn't) and
If the token in the submission matches the token in the session (and fails if it isn't).
if (!isset($_POST['token'])) {
This checks:
If there is a token (and fails if there isn't) and
… but doesn't care if it matches the one in the session.
This means the second version is vulnerable because an attacker making a CSRF attack could shove any old token in the form they submit and it will work so long as the user is logged in.
The first version isn't vulnerable because the attacker has no way to know what token is in the visitor's session on your site, so they can't put it in their form.
This depends or what version of PHP...
You are generating a token the right way, one of several ways. however for PHP 8 and up you are going to have to more than likely do something a little different.
I'm afraid the way you are evaluating the token will not work in PHP 8.
You can try this:
$token = md5(uniqueid(rand(), true));
$_SESSION['csrf_token_time'] = $token;
$_SESSION['csrf_token_time'] = time();
//CSRF token time validation
$max_time = 60*60*24;
if(isset($_SESSION[['csrf_token_time']))
{
$token_time = $_SESSION[['csrf_token_time'];
if(($token_time + $max_time >= time())
{
//do not so anything
}
else
{
unset($_SESSION['csrf_token']);
unset($_SESSION['csrf_token_time']);
echo 'CSRF Token Expired';
}
}
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.
I use <input type="hidden" /> to pass some value so I want to make the hidden field is safe I have a user profile page I get username of member by this
$userprofilename = $_GET['username'];
when some body post any thing to user I get username of profile by
<input type="hidden" name="userprofile" value="<? echo $userprofilename; ?>" />
but in google chrome any body can change userprofile value, how can I make it safe?
thanks
Then instead of storing it in a public form, store it inside a server session
Instead of
$userprofilename = $_GET['username'];
Do
session_start();
$userprofilename = $_SESSION['username'];
And when they login, add their value to the session
// on login
session_start();
$_SESSION['username']=$userprofilename;
The best would be to not pass the client at all but keep it on the server-side. This is what sessions are for: store the value in the server-side session then you can retrieve it on form submission.
However, if you don’t want to use sessions, you can at least detect whether the hidden values got tampered by signing the hidden values using a MAC like hash_hmac. Here’s a simple example:
function create_hmac($key, $uri, array $params)
return hash_hmac('sha256', json_encode(array($uri, $params)), $key);
}
$secret = '7wzvtNgAVCTLPZ27P4L52yzc';
# on form creation
$hidden = array(
'userprofile' => $_GET['username']
);
$hmac = create_hmac($secret, '/form/submit.php', $hidden);
echo '<input type="hidden" name="hidden" value="'.htmlspecialchars(json_encode($hidden)).'">';
echo '<input type="hidden" name="hmac" value="'.$hmac.'">';
# on form submission
$hidden = json_decode($_POST['hidden'], true);
if ($_POST['hmac'] !== create_hmac($secret, $_SERVER['REQUEST_URI'], $hidden)) {
// invalid HMAC
} else {
$_POST = $hidden + $_POST;
}
your form page
create a session variable like this
session_start();
$_SESSION['userprofilename'] = $userprofilename;
instead of this hidden
<input type="hidden" name="userprofile" valu="<? echo $userprofilename; ?>" />
on your submit page
you can retrieve it as follows
session_start();
$userprofilename = $_SESSION['userprofilename'];
// use it as $userprofilename and then unset it
unset($_SESSION['userprofilename']);
this would be safe because session resides on server and can't be seen by the client. by view html source or firebug etc.
You can try by encrypting the username and then decrypting it where ever needed, if you don't want to use sessions
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.
I am trying to add some security to the forms on my website. One of the forms uses AJAX and the other is a straightforward "contact us" form. I'm trying to add a CSRF token. The problem I'm having is that the token is only showing up in the HTML "value" some of the time. The rest of the time, the value is empty. Here is the code I am using on the AJAX form:
PHP :
if (!isset($_SESSION)) {
session_start();
$_SESSION['formStarted'] = true;
}
if (!isset($_SESSION['token'])) {
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
}
HTML :
<input type="hidden" name="token" value="<?php echo $token; ?>" />
Any suggestions?
For security code, please don't generate your tokens this way: $token = md5(uniqid(rand(), TRUE));
rand() is predictable
uniqid() only adds up to 29 bits of entropy
md5() doesn't add entropy, it just mixes it deterministically
Try this out:
Generating a CSRF Token
PHP 7
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];
Sidenote: One of my employer's open source projects is an initiative to backport random_bytes() and random_int() into PHP 5 projects. It's MIT licensed and available on Github and Composer as paragonie/random_compat.
PHP 5.3+ (or with ext-mcrypt)
session_start();
if (empty($_SESSION['token'])) {
if (function_exists('mcrypt_create_iv')) {
$_SESSION['token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
} else {
$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
}
}
$token = $_SESSION['token'];
Verifying the CSRF Token
Don't just use == or even ===, use hash_equals() (PHP 5.6+ only, but available to earlier versions with the hash-compat library).
if (!empty($_POST['token'])) {
if (hash_equals($_SESSION['token'], $_POST['token'])) {
// Proceed to process the form data
} else {
// Log this as a warning and keep an eye on these attempts
}
}
Going Further with Per-Form Tokens
You can further restrict tokens to only be available for a particular form by using hash_hmac(). HMAC is a particular keyed hash function that is safe to use, even with weaker hash functions (e.g. MD5). However, I recommend using the SHA-2 family of hash functions instead.
First, generate a second token for use as an HMAC key, then use logic like this to render it:
<input type="hidden" name="token" value="<?php
echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />
And then using a congruent operation when verifying the token:
$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
// Continue...
}
The tokens generated for one form cannot be reused in another context without knowing $_SESSION['second_token']. It is important that you use a separate token as an HMAC key than the one you just drop on the page.
Bonus: Hybrid Approach + Twig Integration
Anyone who uses the Twig templating engine can benefit from a simplified dual strategy by adding this filter to their Twig environment:
$twigEnv->addFunction(
new \Twig_SimpleFunction(
'form_token',
function($lock_to = null) {
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
if (empty($_SESSION['token2'])) {
$_SESSION['token2'] = random_bytes(32);
}
if (empty($lock_to)) {
return $_SESSION['token'];
}
return hash_hmac('sha256', $lock_to, $_SESSION['token2']);
}
)
);
With this Twig function, you can use both the general purpose tokens like so:
<input type="hidden" name="token" value="{{ form_token() }}" />
Or the locked down variant:
<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />
Twig is only concerned with template rendering; you still must validate the tokens properly. In my opinion, the Twig strategy offers greater flexibility and simplicity, while maintaining the possibility for maximum security.
Single-Use CSRF Tokens
If you have a security requirement that each CSRF token is allowed to be usable exactly once, the simplest strategy regenerate it after each successful validation. However, doing so will invalidate every previous token which doesn't mix well with people who browse multiple tabs at once.
Paragon Initiative Enterprises maintains an Anti-CSRF library for these corner cases. It works with one-use per-form tokens, exclusively. When enough tokens are stored in the session data (default configuration: 65535), it will cycle out the oldest unredeemed tokens first.
Security Warning: md5(uniqid(rand(), TRUE)) is not a secure way to generate random numbers. See this answer for more information and a solution that leverages a cryptographically secure random number generator.
Looks like you need an else with your if.
if (!isset($_SESSION['token'])) {
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
$_SESSION['token_time'] = time();
}
else
{
$token = $_SESSION['token'];
}
The variable $token is not being retrieved from the session when it's in there
You can use time() method with md5() to make it unique.
if (!isset($_SESSION['token']))
{
$time = time();
$_SESSION['token'] = md5($time);
$_SESSION['token_time'] = $time;
}
else
{
$token = $_SESSION['token'];
}