I have a form and I want it to be as secure as possible. So I have this function that generates a Form Token.
function generateFormToken($form) {
// generate a token from an unique value
$token = hash('sha256', uniqid(microtime()));
// Write the generated token to the session variable to check it against the hidden field when the form is sent
$_SESSION[$form.'_token'] = (string)$token;
return $token;
}
Here is how it is activated:
if (empty($_POST)){
$newToken = generateFormToken('form1');
}
Here is the function I use to check it:
function verifyFormToken($form) {
// check if a session is started and a token is transmitted, if not return an error
if(!isset($_SESSION[$form.'_token'])) {
return false;
}
// check if the form is sent with token in it
if(!isset($_POST['token'])) {
return false;
}
// compare the tokens against each other if they are still the same
if ($_SESSION[$form.'_token'] !== $_POST['token']) {
return false;
}
return true;
}
My HTML form has a hidden input in which I put the new token variable. The form is sending to the same page via form action="#".
The problem that I have is that for some reason, when the page refresh, even though the function is not calling again, the token changes in the session but it stays the same in the form.
I'm really lost since I thought that asking PHP to store a string in a session it wouldn't change when the page refreshed under these conditions, but it keeps giving me a different response. I tried to call die() before when it enters the function to make sure that the function was not somehow loading again farther in the script, but nothing happens. It should in theory not refresh.
Help me please.
Here is the HTML Form:
<form action="#" method="POST" id="form1">
<label for="firstname">Firstname</label>
<input type="text" name="firstname">
<input type="hidden"name="token" value="<?= $newToken; ?>">
</form>
And here is the validation logic that I use at the top of my page:
$msg = "";
//If the form has been filled
if (!empty($_POST)){
//If the form has been sent from our website
if (verifyFormToken('form1') == true){
//TODO CAPTCHA
if (1 == 1){
//Everything's fine
}
else{
$msg = 'Veuillez cocher la case «Je ne suis pas un robot».';
}
}
//Else, user tried to send data with his own script (Possible XSS attack)
else{
$msg = 'Veuillez utiliser le formulaire directement sur notre site Web afin d\'appliquer sur l\'offre d\'emploi';
}
}
else{
//If form is not sent, we generate a new token and store it in the user's session
$newToken = generateFormToken('form1');
}
if (!empty($msg)){
echo $msg;
}
Usually, when a $_SESSION token is changing even though you set it properly, it is because you haven't set session_start() properly.
Try looking at the beginning of the file and add a session_start() there if you haven't already
You're missing an ending semi-colon in your code: <input type="hidden" value="<?= $newToken ?>">. This -> $newToken;
The following has been tested.
Apache version: 2.2.29, PHP version: 5.4.24, MySQL version: 5.5.42-37.1, Architecture: x86_64, Operating system: linux
form.php
<?php
include('so-sessions.php');
$form = "form1"; // set form id
// check if form has been submitted
IF (isset($_POST['token'])) {
$msg = ""; // default response
// Validate Token
IF (VerifyFormToken($form)) {
$msg = "Token is valid";
}ELSE{
$msg = "Token is invalid";
}
echo("<p>".$msg."</p>");
}ELSE{
// form not submitted
$token = Set_TokenSession($form); // Set token
// debugging
echo("<p>Token: ".$token."</p>");
// form
echo('
<form action="#" method="post" id="'.$form.'">
<input type="hidden" name="token" value="'.$token.'">
<label for="firstname">First Name: </label><input type="text" name="firstname" />
<input type="submit" name="submit" value="submit" />
</form>');
}
?>
so-sessions.php
<?php
Function HasSessionStarted() {
$result = false; // default to false
// Check if session has started
IF ((session_status() == PHP_SESSION_NONE) || (session_id() == '')) {
$result = true;
}
return $result;
}
Function GenerateFormToken() {
// Create token
$result = hash('sha256', uniqid(microtime()));
return $result;
}
Function Set_TokenSession($form) {
$new_session = HasSessionStarted(); // Check Session Status
$token = GenerateFormToken(); // Create token
// If the session hasn't started do so
IF ($new_session) { session_start(); }
// Unset any pre-set token
Unset_TokenSession($form);
// Set new session token
$_SESSION[$form.'_token'] = $token;
// End the current session and store session data
IF ($new_session) { session_write_close(); }
return $token;
}
Function Unset_TokenSession($form) {
$new_session = HasSessionStarted(); // Check Session Status
IF ($new_session) { session_start(); }
// Unset session token
IF (isset($_SESSION[$form.'_token'])) { unset($_SESSION[$form.'_token']); }
// End the current session and store session data
IF ($new_session) { session_write_close(); }
}
Function VerifyFormToken($form) {
$result = false; // default result to fail
$new_session = HasSessionStarted(); // Check Session Status
IF ($new_session) { session_start(); }
$sesstoken = $_SESSION[$form.'_token'];
Unset_TokenSession($form); // Unset the used token
IF ($new_session) { session_write_close(); }
// debugging
#echo("<!-- Tokens (Form/Session) : ".$_POST['token']." / ".$sesstoken." -->");
// check if session and post token are not empty
IF ((!empty($sesstoken)) && (!empty($_POST['token']))) {
// compare the tokens against each other
IF ($sesstoken == $_POST['token']) {
$result = true;
}
}
return $result;
}
?>
Related
I am trying to pass data in a form that has code that checks for csrf attacks first and then sends dat using php, but the code always tells me that there is an attack even though there is not attack The code always just executes the error message and the rest of the code is not considered.
php code:
<?php
$csrf_avoid= bin2hex(random_bytes(20));
$_SESSION['auth_token'] = $csrf_avoid;
if (isset($_POST["save_entry"])) {
if ($_POST["auth_token"] !== $csrf_avoid) {
// show an error message
echo '<h1 class="error">Error: invalid form submission</h1><p>Your request was denied as this request could not be verified.</p>';
// return 405 http status code
exit();
}
else{
// do anything here there is no csrf attacks
}
}
?>
html
<input type="hidden" name="auth_token" value="<?php echo $csrf_avoid;?>">
first of, don't generate a token every time, and check the session when submitting :
if(!isset($_SESSION['auth_token'])) {
$csrf_avoid = bin2hex(random_bytes(20));
$_SESSION['auth_token'] = $csrf_avoid;
}
if (isset($_POST["save_entry"])) {
//check the session
if ($_SESSION['auth_token'] !== $csrf_avoid) {
// show an error message
echo '<h1 class="error">Error: invalid form submission</h1><p>Your request was denied as this request could not be verified.</p>';
// return 405 http status code
exit();
}
else {
// do anything here there is no csrf attacks
// you could regenerate token on submit, so the current one becomes invalid
$csrf_avoid = bin2hex(random_bytes(20));
$_SESSION['auth_token'] = $csrf_avoid;
}
}
Also change your input to :
<input type="hidden" name="auth_token" value="<?php echo $_SESSION['auth_token'];?>">
I have a fairly straight-forward validation system on my registration page within my website. It all works fine, however, it seems unnecessarily messy; with always checking if a variable ($regOpen) is true, and then setting a variable ($errors) to true each time there is an error.
This is the very simplified script and relative HTML:
<?php
$regOpen = false;
$errors = false;
if(Input::is("register")){ // if a user has clicked register
$regOpen = true;
}
if($regOpen){ // checking if input is set first time
if(Input::empty("email")){
echo '<span>Your email address must not be left blank.</span>';
$errors = true; // setting to true for the first time
}
if($email->exists()){
echo '<span>A user with that email already exists.</span>';
$errors = true; // 2nd
}
if(!filter_var(Input::get("email"), FILTER_VALIDATE_EMAIL)){
echo '<span>That is not a valid email type.</span>';
$errors = true; // 3rd
}
}
?>
<input type="text" name="email">
<?php
if($regOpen){ // 2nd
if(Input::empty("password")){
echo '<span>Your password must not be left blank.</span>';
$errors = true; // 4th
}
if(strlen(Input::get("password")) < 4){
echo '<span>Your password must be a minimum of 4 characters.</span>';
$errors = true; // 5th
}
}
?>
<input type="password" name="password">
<?php
if($errors){ // if there are errors
echo '<span>Registration failed.</span>';
} else {
// register user
echo '<span>Registration successful.</span>;
}
?>
In reality, I actually have about several fields I need to check (each with their own list of errors to check), so as you can imagine; checking and setting all these variables seems a bit tedious and unnecessary.
What I want to know is, if there is a way to only have to set the $errors variable to true, once. Not only that, if there is a way to reduce the way I check if $regOpen is true (instead of checking each time I need to check for errors).
Thanks.
You could use $errors as an array for errors instead of being just an indicator. Then you could check if $errors array is not empty, then it contains errors.
Here's a clearer version of your code:
<?php
function print_errors($errors) {
foreach($errors as $error) {
echo '<span>' . $error . '</span>';
}
}
$regOpen = Input::is("register");
$errors = [];
if($regOpen){ // checking if input is set first time
if(Input::empty("email")){
$errors['email'][] = "Your email address must not be left blank.";
}
if($email->exists()){
$errors['email'][] = "A user with that email already exists.";
}
if(!filter_var(Input::get("email"), FILTER_VALIDATE_EMAIL)){
$errors['email'][] = "That is not a valid email type.";
}
if(Input::empty("password")){
$errors['password'][] = "Your password must not be left blank.";
}
if(strlen(Input::get("password")) < 4){
$errors['password'][] = "Your password must be a minimum of 4 characters.";
}
}
?>
<?php isset($errors['email']) ? print_errors($errors['email']) : null; ?>
<input type="text" name="email">
<?php isset($errors['password']) ? print_errors($errors['password']) : null; ?>
<input type="password" name="password">
<?php
if(count($errors) > 0){ // if there are errors
echo '<span>Registration failed.</span>';
} else {
// register user
echo '<span>Registration successful.</span>';
}
?>
You may now get the idea.
First of all, many of the checking are not necessary at PHP level, you can use the HTML 5 form validation for many cases. Secondly, for a few case that HTML5 form validation can't handle, you don't need to purposely set $errors=true, you could do something like $error=$email->exists();.
I've been trying to create an admin panel for my website. I created a login form but whenever I try to log in, it says that the user does not exist. I can't seem to find where I made a mistake.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login - Admin panel</title>
</head>
<body>
<?php
include 'db.php';
?>
<?php
include 'functions.php';
?>
<?php
include 'title_bar.php';
?>
<h3>Login Here: </h3>
<?php
if(isset($_POST['submit'])) {
$username = $_POST['username'];
$password = $_POST['password'];
if(empty($username) or empty($password)){
echo "<p>Fields should not be empty</p>";
} else {
$check_login=mysqli_query($con,"SELECT id, type FROM users WHERE username='$username' AND password='$password'");
if(mysqli_num_rows($check_login) == 1){
$run=mysqli_fetch_array($check_login);
$user_id=$run['id'];
$type=$run['type'];
if($type =='d') {
echo "<p>Your acount is deactivated by an admin!</p>";
} else {
$_SESSION['user_id'] = $user_id;
header('location: adminpanel.php');
}
} else {
echo "<p>Wrong Username or Password</p>";
}
}
}
?>
<form method='post'>
User name:
<input type ='text' name = 'username' />
<br/><br/>
Password:
<input type = 'password' name = 'password' />
<br/><br/>
<input type = 'submit' name = 'submit' value='Login' />
</form>
</body>
</html>
Any help would be appreciated.
Just because I see this all the time on SO, I will address some of my comments. There are a lot of reasons why it could fail based on what you have. First off, a solid framework would do almost all this for you, you would just have to do basic logic but not all the grunt work. Second, just because you want to echo some text in a specific part of your page, doesn't mean you should do a bunch of logic that leads up to echo in the same part of the page. The idea is that the browser output is the last thing to happen so you will want to do the bulk of your logic before the page outputs.
First break up your logic into a specific-task functions/class/methods that will be easily understood and ready to be re-used:
/functions.php
<?php
// I am going to use PDO because I am more familiar with it
function verifyUser($username,$password,$con)
{
// Get the basics from the database
$query = $con->prepare("SELECT `password`,`type`,`id` FROM `users` WHERE `username` = :0");
// Bind the value for security
$query->execute(array(":0"=>$username));
// Get the results
$result = $query->fetch(PDO::FETCH_ASSOC);
// If empty, return false
if(!$result)
return array('verified'=>false);
// You need to store the password using password_hash()
$verified = password_verify($password,$result['password']);
// If user is revoked
$revoked = is_deactivated($result);
// Return all the validation settings
return array(
'type'=>$result['type'],
'id'=>$result['id'],
'revoked'=> $revoked,
'verified'=>$verified,
'valid'=>($verified && !$revoked)
);
}
function getUserById($id,$con)
{
$query = $con->prepare("SELECT * FROM `users` WHERE `id` = :0");
$query->execute(array(":0"=>$id));
$result = $query->fetch(PDO::FETCH_ASSOC);
if(!$result)
return false;
return $result;
}
function is_deactivated($userArr = false,$con = false)
{
// Set default user empty
$user = false;
// If value is numeric (id)
if(is_numeric($userArr)) {
// Get the data by from database, assign to user
$user = getUserById($userArr,$con);
}
// If the value is an array, just assign to user
elseif(is_array($userArr))
$user = userArr;
// If the value is still empty, stop, return deactivated
if(empty($user))
return true;
else
// If user is valid (in db), return bool if they are revoked
return ($user['type'] == 'd');
}
function loginObserver(&$error,$con)
{
// See if the action to log in is set
if(isset($_POST['action']) && $_POST['action'] == 'login') {
// Run the verify function
$verify = verifyUser($_POST['username'],$_POST['password'],$con);
// If user is in db
if($verify['verified']) {
// See if they are revoked, send back error
if($verify['revoked']) {
$error = 'revoked';
return false;
}
// Assign your session id
$_SESSION['user_id'] = $verify['id'];
// Return true for success
return true;
}
else {
// User was not in system, send invalid error
$error = 'invalid';
return false;
}
}
else
// Return a string so the subsequent logic knows that
// no attempt was made to log in.
return 'invalid';
}
Secondly, now that you have all your business logic stored away in contained functions (classes/methods) you can cleanly apply them to the page.
/login.php
<?php
// Put this at the very beginning. I would consider putting it on a config page and
// including it would be better because then you will have some consistency
// through your site
session_start();
// Add your functions and or classes, better yet would be to have an autoloader
// to load classes and a pseudo-autoloader to load functions
include('functions.php');
// Put your database connection at the top, on the config page would be better
include('db.php');
// Move logic to the top and pass errors to the page
$error = false;
// Run the observer function
$login = loginObserver($error,$con);
// Invalid means no attempt was made to login
if($login != 'invalid')
// If there are no errors (empty), redirect
if(!$error) {
// This needs to go before output of html to browser
header('location: adminpanel.php');
// Stops the script from processing the rest of the page
exit;
}
}
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login - Admin panel</title>
</head>
<body>
<?php
// This is probably fine, it's likely just html
include('title_bar.php');
?>
<h3>Login Here: </h3>
<?php if($error) {
echo ($error == 'invalid')? 'Wrong username or password.':'Your access has been revoked by admin.';
} ?>
<form method='post'>
<!-- add an action here -->
<!-- You should do a token system for verifying submission authenticity -->
<input type="hidden" name="action" value="login" />
User name:
<input type='text' name='username' />
<br/><br/>
Password:
<input type='password' name='password' />
<br/><br/>
<input type='submit' name='submit' value='Login' />
</form>
</body>
</html>
Finally, this code is not tested so there may be errors in logic. It is intended to show you how to apply my (and perhaps other's comments practically). I don't want to say "Don't do this and don't do that" but don't show an alternative. This script is based on yours so you can identify similarities easier, but is no way implied this is the only way, or the correct way to do a login.
I'm trying to implement a simple CSRF protection on a form, but I can't get it right. If someone can point out what I'm doing wrong, I would appreciate it.
The error: Every time I submit the form, I get "Invalid Submission2" because the token changes (after the form is submitted), since is being generated on the same page?
Edit -- I forgot to mention, another file (config.php) already has session_start().
<?php
class Module_Post extends Base_Module {
public function start()
{
requireLogin();
if (isset($_GET['act'])) {
switch($_GET['act']) {
case 'text':
$this->text();
break;
case 'image':
$this->image();
break;
default:
$this->text();
break;
}
} else {
$this->text();
}
}
private function text()
{
// Generate random unique token to prevent cross-site request forgery (CSRF).
if(empty($_SESSION['form_token']))
{
$form_token = md5(uniqid(rand(), TRUE));
$_SESSION['form_token'] = $form_token;
}
if(isset($_POST['submit']))
{
// Clean the content from cross-site scripting (xss)
$content = trim($_POST['content']);
$content = Xss::clean($content);
// Validate that the form token
if(!isset($_POST['form_token'], $_SESSION['form_token'])) {
$err = 'Invalid Submission';
} elseif ($_POST['form_token'] != $_SESSION['form_token']) {
$err = 'Invalid Submission2';
} elseif (strlen($content) < 10) {
$err = 'Your content contains too few characters.';
}
if(isset($err)) {
unset( $_SESSION['form_token']);
$this->setMessage($err, 'FAIL');
header('Location: index.php?mod=post');
exit;
}
// Insert database data here, then redirect
$this->setMessage('Your post was published successfully.', 'GOOD');
header('Location: index.php');
exit;
}
$this->tpl->assign('form_token', $form_token);
$this->tpl->display('new/text.tpl');
}
}
?>
The HTML (text.tpl file)
<form method='post' enctype='multipart/form-data' action='#'>
<fieldset>
<textarea rows="8" id="new_post" name="content" class="input-block-level"></textarea>
<input type="hidden" name="form_token" value="{$form_token}" />
<button type="submit" name="submit" class="btn btn-info pull-left">Create Post</button>
</fieldset>
</form>
You need to change this line
$this->tpl->assign('form_token', $form_token);
into:
$this->tpl->assign('form_token', $_SESSION['form_token']);
That's because you generate your token only with this condition:
if(empty($_SESSION['form_token']))
{
$form_token = md5(uniqid(rand(), TRUE));
$_SESSION['form_token'] = $form_token;
}
and unset it using this condition:
if(isset($err)) {
unset( $_SESSION['form_token']);
}
So if you send your form once and then reload page (without submitting form just url), $form_token variable is unknown because there is $_SESSION['form_token'] is not empty and then in your form you have empty token.
If you had displaying errors turned on in PHP you would see in this case in PHP:
Undefined variable: form_token in
At the moment I am attempting to create an application that passes on data to delete a row in my database. This row will be known by the ID passed on by the html file to js.
Currently I have a Html file, Javascript and PHP file which all work together to get this data passed in.
What im looking to do is secure it so no-one unauthorised can just send data to the javascript document in-order to delete the row.
HTML -- > JS --> PHP
JS:
function deleteListing(id) {
var answer = confirm("Are you sure you want to delete this listing?")
if (answer) {
$.post("assets/scripts/deleteListing.php", {
id: id
},
function (result) {
var response = jQuery.parseJSON(result);
if (response.available === true) {
location.reload();
} else if (response.available === false) {
// alert("FAILURE DELETING USER");
} else if (response.root === true) {
// alert("Cannot Delete Root User..");
}
});
} else {
return;
}
}
PHP:
<?
require("../../../assets/config/config.php");
$id_retrieve = $_POST['id'];
$data = new stdClass();
$sth= $dbh-> prepare("SELECT * FROM listings WHERE id='".$id_retrieve."'");
$sth -> execute();
$row = $sth -> fetch();
$data->available = true;
$dbh->exec("DELETE FROM listings WHERE id = '".$id_retrieve."'");
echo json_encode($data);
?>
Before anyone says the statement is not created using the prepared statement, I am aware of this and will fix it asap. Apart from that, is there anyway I can secure the Javascript file from unauthorised access? or could I limit it somehow?
Thanks!
There are a couple of solutions.
As #Tobias said above: Use sessions to handle the authentication. That will protect you some.
However, that alone doesn't stop Cross-Session attacks.
Take a look at this page: http://phpsec.org/projects/guide/2.html
It suggests putting a token value on the form and saving it in the session. That way, when the form is submitted you can compare the incoming token against the one in your session and verify that the form did, indeed, come from your site.
<?php
session_start();
if (isset($_POST['message']))
{
if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token'])
{
$message = htmlentities($_POST['message']);
$fp = fopen('./messages.txt', 'a');
fwrite($fp, "$message<br />");
fclose($fp);
}
}
$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token;
?>
<form method="POST">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="text" name="message"><br />
<input type="submit">
</form>
<?php
readfile('./messages.txt');
?>