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
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'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 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;
}
?>
I have a problem with my PHP script, which checks 3 variables (code below):
$auth (The mail author)
$subj (The mail subject)
$text (The mail message)
FORM:
(NOTE: I used the "GET" method because for some strange reason the "POST" method didn't work)
<div id="contact_form">
<form method="get" name="contact" action="home.php">
<input type="hidden"
name="method"
value="send"/>
E-Mail:<br/>
<input type="text"
id="author"
name="author"
class="require input_field"
value=""/>
<br/>
Subject:<br/>
<input type="text"
id="subject"
name="subject"
class="require input_field"
value=""/>
<br/>
Message:<br/>
<textarea id="text"
name="text"
rows="0"
cols="0"
class="required"
value=""></textarea>
<br/>
<input type="submit"
class="submit_btn"
name="submit"
id="submit"
value="Submit" />
</form>
</div>
The form works just fine now.
PHP:
<?php // ||HOME.PHP||
$method = $_GET['method'];
$auth = $_GET['author'];
$subj = $_GET['subject'];
$text = $_GET['text'];
$recv = "mymail#stuff.com";
function redirect($location) {
if($location == "true") {
header("Location: http://mysite.com/home.php?method=result&status=true");
} else {
header("Location: http://mysite.com/home.php?method=result&status=false");
}
}
//...
//Other methods...
//...
//METHOD SEND
if($method == "send") {
//HERE IS THE PROBLEM
//These are apparently not working
//If i leave the form blank and submit it
//these won't redirect to "false" (mail not sent),
//and the script will just continue, send the empty mail
//and redirect to "true" (mail sent)
if(empty($auth)) { redirect(""); }
if(empty($subj)) { redirect(""); }
if(empty($text)) { redirect(""); }
if(!strstr($auth, '#')) { redirect(""); }
if(!strstr($auth, '.')) { redirect(""); }
if(strlen($auth) < 5) { redirect(""); }
if(strlen($subj) < 4) { redirect(""); }
if(strlen($text) < 4) { redirect(""); }
//From here it should work just fine
//As i'm not sure the "RESULT" method (below) is working fine, i
//posted it too.
$auth = "From: " . $auth;
mail($recv,$subj,$text,$auth);
redirect("true");
require("template/footer.html");
exit(0);
}
//METHOD RESULT
if($method == "result") {
$status = $_GET['status'];
if($status == "true") {
echo "mail sent";
} else {
echo "mail not sent";
}
?>
<script language="javascript">
setTimeout("location.href = 'http://adteam.altervista.org/home.php';", 5000);
</script>
<?php
exit(0);
} ?>
The problem is explained in the PHP code (in the comments below the "SEND" method).
Do you guys have any suggestion?
You need to stop the script execution after you've set the redirect headers. Otherwise it will just continue to sending the mail and set new redirect headers before any headers are sent to the browser.
function redirect($location) {
if($location) {
header("Location: http://mysite.com/home.php?method=result&status=true");
} else {
header("Location: http://mysite.com/home.php?method=result&status=false");
}
die();
}
Note that if( $location == "true" ) is kind of an anti-pattern; it's better to use boolean true and false instead of strings.
Should be easy. Your saying that "" is false. But it ain't: because "" is true, but empty which is true. false is not set or specified false. So you should do:
function redirect($location) {
if($location) {
header("Location: http://mysite.com/home.php?method=result&status=true");
exit();
} else {
header("Location: http://mysite.com/home.php?method=result&status=false");
exit();
}
}
And use: redirect(true) / redirect(false);
A string will always evaluate to true, even if it is empty. That's precisely the reason why we check strings using empty() as opposed to isset(). A few more things:
You should be using POST to submit the email.
You should probably check if the form was actually submitted before validating the input.
You should create and display specific error messages telling the user what required fields they did not complete.
You should run some careful validation routines on the input to avoid having your email form used to send spam and malware.
Simply add exit in false branch:
function redirect($location) {
if($location == "true") {
header("Location: http://mysite.com/home.php?method=result&status=true");
} else {
header("Location: http://mysite.com/home.php?method=result&status=false");
exit(0); //simply add it here
}
}
header function does not stop further execution by itself and you must stop doing anything else if do not want to send an email when something is wrong.
Actually, you may simply add exit after if statement.
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');
?>