I use Codeigniter/PHP. I use CSRF tokens (not the CI native version as I have my own form implementation) and from time to time the token is not validated.
The CSRF token is created once per session:
function create_csrf_token() //If needed, creates a session variable; returns a hash to use for CSRF protection
{
$CI =& get_instance();
if($CI->session->userdata('csrfToken')) //the token already exists: use its hash
{
$csrfHash = $CI->session->userdata('csrfToken');
}
else //no token yet: create session variable + set its hash
{
$csrfHash = base64_encode(hash('sha256', uniqid(serialize($_SERVER), true), true));
$CI->session->set_userdata(array('csrfToken' => $csrfHash));
}
return $csrfHash;
}
It is passed to the form without issues in the csrfToken hidden input field, with htmlspecialchars applied to it (using urlencode makes no difference):
echo '<input type="hidden" name="'.$this->name.'" value="'.htmlspecialchars($this->value).'">';
This field has a validation rule verify_csrf:
public function verify_csrf($token)
{
$CI =& get_instance();
if($CI->session->userdata('csrfToken') && $CI->session->userdata('csrfToken') == $token) return true;
else
{
$this->set_message('verify_csrf', 'Invalid token');
return false;
}
}
This is where things get weird. Sometimes $token is not correct and looks like corrupted data. Here are a couple of examples:
Error:
Value in $CI->session->userdata('csrfToken'): 6cT3O0KTOk7cVlear71lU7KKFlGONt4rS2HjNoSVFRM= (correct)
Value in $token: 6cT O0KTOk7cVlear71lU7KKFlG (4th character changed and missing end of string)
No error:
Value in $CI->session->userdata('csrfToken'): AiAgGqqxTxuCxN7h5HHRtcJjmHJVMRksBYbq6Dx4Kv4=
Value in $token: AiAgGqqxTxuCxN7h5HHRtcJjmHJVMRksBYbq6Dx4Kv4=
Any idea? I have checked and rechecked, the CRSF token is correctly set everywhere except in $token in my validation callback. And it only happens for certain tokens...
EDIT: so it seems that the base64 encoding was causing the issue (why, I don't know). I have replaced
$csrfHash = base64_encode(hash('sha256', uniqid(serialize($_SERVER), true), true));
by
$csrfHash = random_string('sha1');
This is just a wild guess, but could it be the combination of the Base64 encoding and submitting the form via HTTP POST, like described here:
POST Base64 encoded data in PHP
The solution could then be to urlencode() the token before posting?
EDIT: The solution turns out to be dropping the base64 encoding of the token in favor of a plain sha256-hash as a token. See comments below.
Please use the config for the CSRF.
You can enable csrf protection by opening your application/config/config.php file and setting this:
$config['csrf_protection'] = TRUE;
// This will automatically generate the hidden field for form_open(params) function, so you no need to do it every time. This feature is included the frame work.
To avoid CSRF to specific URIs
http://ellislab.com/forums/viewthread/182631/
Related
I received some pen tests results.
The results say that anybody can update any record by just changing a certain id.
How could I ensure that the user can only update his own record in this function?
public function actionUpdateProfile()
{
$postdata = file_get_contents("php://input");
$response = array("status" => "1", "message" => "Profile update successful.");
$data = json_decode($postdata);
$model = HosDoctors::model()->findByPk($data->doctor_id);
foreach ($data->fields1 as $field) {
$_POST[$field->name] = $field->value;
}
$enc = NEW bCrypt();
$model->attributes = $_POST;
if ($model->save()) {
$response = array("status" => "1", "message" => "Profil erfolgreich aktualisiert");
} else {
pr($model->getErrors());
}
echo json_encode($response);
die;
}
Would it be sufficient to simply check for
if (cookie == $data->doctor_id)
{
//ok
}
else
{
//we are not logged as the user id that we want to update, so deny updating
die;
}
I am assuming the person has "logged in" in some way so that you know "who they are". While security is complex and is definitely not a one-answer topic; at its simplest level, once you have identified the user, use PHP session handling to persist their identity across one or many http/s requests, then internally access any related information using the session cookie id for the duration of the session.
There are several potential issues in this function (unless you posted a heavily edited version). I'll note them as comments.
public function actionUpdateProfile()
{
$postdata = file_get_contents("php://input");
$response = array("status" => "1", "message" => "Profile update successful.");
// Never initialize responses until you really *must*. Chances that a partially prepared response might be output are slight, but why run risks?
// And actually you **do** reinitialize $response later on!
$data = json_decode($postdata);
// You are not verifying that $data *exists* (i.e. the JSON data was, indeed, JSON). You should check that $data is not NULL and that it does have **all** the required fields and that they are valid.
// This is the point where you validate $data->doctor_id, by the way. Or you check that patient_data matches with whatever you have in your $_SESSION or Session app object.
$model = HosDoctors::model()->findByPk($data->doctor_id);
// This is a bad practice. Yes, you have some code that relies on
// _POST. If necessary, wrap it in another code that will set up
// _POST from an input and then delete it. Otherwise you're leaking
// data into a superglobal. You don't want to do that.
foreach ($data->fields1 as $field) {
$_POST[$field->name] = $field->value;
}
// Why are you initialising $enc?
$enc = NEW bCrypt();
// This is not very good. $_POST could contain *other* information
// unless it's been sanitized outside the function.
// I would prepare a setter function, $model->setArray($data), that
// would verify the validity of the attributes before setting them.
$model->attributes = $_POST;
if ($model->save()) {
$response = array("status" => "1", "message" => "Profil erfolgreich aktualisiert");
} else {
pr($model->getErrors());
}
// This works in 90% of the browsers and scenarios. But I'd
// set up a function that would also send the appropriate
// Content-Type headers to satisfy the remaining 10%.
echo json_encode($response);
die;
// e.g. Utilities::jsonResponse($response);
}
Would it be sufficient to simply check for
if (cookie == $data->doctor_id) {
//ok } else {
//we are not logged as the user id that we want to update, so deny updating
die; }
try checking by user token instead of user-id because user-id can be found within some page url or body but the token is hard to get unless using network sniffing technics.
you can generate user token by any token generation method and then store it as a column in the user table in your database.
token generation method e.g
$token = bin2hex(openssl_random_pseudo_bytes(16));
# or in php7
$token = bin2hex(random_bytes(16));
Would it be sufficient to simply check for
if (cookie == $data->doctor_id)
{
//ok
}
else
{
//we are not logged as the user id that we want to update, so deny updating
die;
}
No, in principle this is actually the same as you already have: the ID is still part of the request and can be changed like you got it reported.
Instead ensure the request is first of all authorized, then fetch the related data from the identity and access management system.
If you do not have anything to manage identity and access, choose a system and integrate it first.
From the code you've posted so far it is not visible (to me) whether or not such a system exists. Therefore I can not specifically answer on that detail.
Authentication/Authorization of requests is part of the HTTP protocol and is supported at a basic level in PHP out of the box. See:
HTTP authentication with PHP Docs
If you want to roll your own completely you can consider HTTP state management with PHP sessions, but as you ask the question the way you ask it (with all due respect), my suggestion is to take something that already exists, is proven and easy for you to understand and integrate (less complexity = better software and less flaws/holes).
Take the event of the pen test and its result to address the design issues first and find a high level solution not that you need to patch multiple endpoints individually only to address the same basic flaw.
Let the system work for you.
After the pen-test I would also suggest the code is reviewed with dedication towards the found issues and the code writers are trained based on the results of both.
Would it be sufficient to simply check for ...
Yes, given the cookie is from a safe source and all its properties are secure. E.g. only the identity and access management system can add it to the HTTP request that hits the PHP SAPI.
I would rate this event very unlikely as you needed to ask the question.
And it would be very unorthodox if it would contain a concrete id. There should always be a HTTP state management and it should be rolling to protect against replay attacks with credentials or at least limit the time-frame in which they are valid.
But purely technically, with the right design and implementation I can imagine there can be systems that work this way (HTTP request with cookie as secure token).
Take care.
I'm using laravel's Auth password reset method and not sure i fully understand what part plays the token in all of this.
I'm sending the user an email with Password::remind('email#email.com') , which generates a token in my password_reminders table. The token is fully visible in the url.
The user goes to a url that looks something like: mywebsite.com/remindpass/xxxxxx[token] .
Then he fills out a form with his email and a new password , sending it trough post to a controller - which uses Password::reset('email','password','xxxxxx') .
The question is how is this secure? What does the generated token do to prevent someone just going to mywebsite.com/remindpass/xxxxxx[token] and change the email & password as he likes?
Can someone please clarify the proccess?
I'm sure someone could answer this question better than I could.
Short answer:
The token makes it more difficult for someone to guess the credentials needed to reset the password while making the reset link in the email available.
Long answer:
In the file vendor/laravel/framework/src/Illuminate/Auth/Guard.php, you'll see the method createRememberTokenIfDoesntExist. This method actually references another method right above it called refreshRememberToken to set your token.
It uses the laravel helper function str_random. If you trace this function back to it's source, you'll find it uses the vendor/laravel/framework/src/Illuminate/Support/Str.php class' random method.
public static function random($length = 16)
{
if (function_exists('openssl_random_pseudo_bytes'))
{
$bytes = openssl_random_pseudo_bytes($length * 2);
if ($bytes === false)
{
throw new \RuntimeException('Unable to generate random string.');
}
return substr(str_replace(array('/', '+', '='), '', base64_encode($bytes)), 0, $length);
}
return static::quickRandom($length);
}
Now we finally get down to where the token is built. This method uses the function openssl_random_pseudo_bytesto generate the token. You can read about that function in the PHP manual page for openssl_random_pseudo_bytes, but basically it generates a cryptographically strong random string.
Laravel then takes this string (still in the random method), base 64 encodes it, replaces some characters, and takes a slice of that string based on either the default setting of 16 (seen in the parameter definition $length = 16) or whatever length is passed into the method by the caller.
So, you get a string that is cryptographically strong and then manipulated as your token.
If you look at the file vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php and find the method retrieveByToken, you'll see that laravel uses both the user record ID and the token to find the user who's password needs to change.
For someone to guess that string AND the id of you user record that has that token would be incredibly difficult and would require knowledge of your application's business logic.
What does the generated token do to prevent someone just going to mywebsite.com/remindpass/xxxxxx[token] and change the email & password as he likes?
Because only you and the person you sent the email to (i.e. the account holder) know what the token is.
A strong implementation will takes steps to make it hard to guess tokens:
Long (harder to guess) tokens
Time limited tokens
IP based rate limiting for access to /remindpass/*
I am wondering if this is a secure way to set a token, unless there actually is a token generated, I generate one, and use it throughout the applications and those forms. One token per session?
if (!isset($_SESSION['token'])) {
$data['token'] = uniqid(rand(), true);
session_regenerate_id();
$_SESSION['token'] = $data['token'];
}
Would it be necessary to clear out the token on a submitted form? or just stay with it, even though I submitted a form?
If you don't know these links, this should help you understand some scenarios and specifically this will tell you the DOs and DONT's. Hope it helps.
Personally I would generate a new token for every form I want to display. If you do it this way, someone just needs a session cookie to read your token and use it as long as the session stays active.
In my applications I generate a token for each form display like this:
<?php
$token = uniqid(rand(), true);
$_SESSION['csrf_tokens'][$token] = true;
HTML
<form>
<input type="hidden" name="token" value="<?php echo $token ?>" />
</form>
On form validation I check for that token like this:
if (isset($_SESSION['csrf_tokens'][$token]) && $_SESSION['csrf_tokens'][$token] === true) {
unset($_SESSION['csrf_tokens'][$token]);
// additional code here
}
I am wondering if this is a secure way to set a token
It depends on how secure your web app needs to be. This line is not cryptographically secure (As warned in PHP docs for uniqid() and rand()):
uniqid(rand(), true);
It may be feasible for an attacker to determine/brute force this if the time of token generation is known/determined and the rand() seed is known/determined. However, for your purposes it may be fine as it will still prevent CSRF attacks where the attacker has no knowledge of the token value.
One token per session?
Using one token per session may be fine for your purposes. However, be aware:
If a session is n minutes long then an attacker has an n minute window to attempt to determine or obtain your token value and execute a CSRF attack. Whereas this risk is reduced when tokens are generated per form or when the token is regenerated periodically as they are not long lived enough.
Using a single token per session exposes all of your application's functionality (that uses that token) to attack should an attacker determine/obtain the token. Whereas using a token per form restricts an attack to a single form.
Would it be necessarery to clear out the token on a submitted form? or just stay with it, even though i submitted a form?
It depends upon how high value a target your application is for attackers and the level of disruption an attack would cause you. Your existing measure makes it difficult to execute CSRF attacks but if it is high value and you have very determined attackers then you may want to reduce the risk of CSRF more by:
Using cryptographically secure tokens to prevent risk of determining or brute forcing the token value.
Regenerating the token periodically to reduce token lifespan, decreasing the attack window if the token is determined or obtained.
Generating tokens per form to restrict attacks to a single form in the event that the token be determined or obtained.
Rather than use per-session token i would prefer per-form/url token for additional security some might argue that per-request token is most secured but affects usability.
I also think its better to separate your session storage from your token storage and use something like Memcache. This is better when you need speed using multiple application servers etc. I also prefer it because i can add a custom expiration to the token without having to affect the whole session
Here is a typical example
HTML
<form method="POST" action="#">
IP:<input type="text" name="IP" /> <input type="hidden" name="token"
value="<?php echo Token::_instance()->generate(); ?>" /> <input
type="Submit" value="Login" />
</form>
Processing
$id = "id44499900";
Token::_instance()->initialise($id); // initialise with session ID , user ID or IP
try {
Token::_instance()->authenticate();
// Process your form
} catch ( TokenException $e ) {
http_response_code(401); // send HTTP Error 401 Unauthorized
die(sprintf("<h1>%s</h1><i>Thief Thief Thief</i>", $e->getMessage()));
}
Class Used
class Token {
private $db;
private $id;
private static $_instance;
function __construct() {
$this->db = new Memcache();
$this->db->connect("localhost");
}
public static function _instance() {
self::$_instance === null and self::$_instance = new Token();
return self::$_instance;
}
public function initialise($id) {
$this->id = $id;
}
public function authenticate(array $source = null, $key = "token") {
$source = $source !== null ? $source : $_POST;
if (empty($this->id)) {
throw new TokenException("Token not Initialised");
}
if (! empty($source)) {
if (! isset($source[$key]))
throw new TokenException("Missing Token");
if (! $this->get($this->id . $source[$key])) {
throw new TokenException("Invalid Token");
}
}
}
public function get($key) {
return $this->db->get($key);
}
public function remove($key) {
return $this->db->delete($key);
}
public function generate($time = 120) {
$key = hash("sha512", mt_rand(0, mt_getrandmax()));
$this->db->set($this->id . $key, 1, 0, $time);
return $key;
}
}
class TokenException extends InvalidArgumentException {
}
Note : Note that the example might affect "Back" button or refresh because the token would be automatically deleted after 120 sec and this might affect user friendly capability
Can you please refer following site, this may get some ideas.
1.) https://docs.djangoproject.com/en/dev/ref/contrib/csrf/
2.) http://blog.whitehatsec.com/tag/session-token/
Thanks for reply.
I've already answered a similar question on a different forum: here. Hopefully that's helpful. It explains the basic process of the CSRF prevention and links to some code for a CSRF framework.
If you want higher security, change the token after each request for each session. If you want better usability, keep one token per session.
Help! I'm writing some code to update a mySQL database using similar to the code below:-
$.post('http://myURL.com/vote.php?personID=' + personID + '&eventID=123');
The vote.php code takes the querystring values and inserts a record into a database with those values in it.
This kind of code is working fine, but I've realised the problem is that people could just type something like:
http://myURL.com/vote.php?personID=5&eventID=123
into their address bar and essentially spam the app...
Is there a straightforward way I can ensure this doesn't happen? I'm reasonably new to these technologies so not aware of how everything works or fits together, but I'm learning fast so any pointers would be super useful.
It is not a good idea to use GET parameters for data that goes to a database. Generally, you want to use POST parameters which are not visible in the URL. So instead of :
$.post('http://myURL.com/vote.php?personID=' + personID + '&eventID=123');
You would do it like this :
$.post('http://myURL.com/vote.php', { "personID" : personID, "eventID" : 123 });
And in your PHP script, you would access your data with the $_POST array like this :
$personID = $_POST['personID'];
$eventID = $_POST['eventID'];
However, don't forget to properly filter input before saving to the database to prevent bad things like SQL Injection.
This is not a silver bullet : spam will still be possible because any HTTP client will be able to send a post request to your site. Another thing you can look at is Security Tokens to make it even less vulnerable to spam. Or implement a system that limits the number of request/minute/user... but I'm getting too far from the original question.
Correct syntax of $.post is
$.post(url,data_to_send,callback_function)
By using this method your user will never be able to damage your site.Use like
$.post('http://myURL.com/vote.php',{"personID":personID,"eventID":123);
Whether you're using POST or GET, you could always consider signing important fields in your page by using hash_hmac. This prevents people from changing its value undetected by adding a signature that no one else can guess.
This also makes CSRF more difficult, though not impossible due to fixation techniques. It's just yet another technique that can be put in place to make it more difficult for "fiddlers".
The following function adds a salt and signature to a given person id to form a secured string.
define('MY_SECRET', 'an unguessable piece of random text');
function getSecurePersonId($personId)
{
$rnd = uniqid("$personId-", true);
$sig = hash_hmac('sha1', $rnd, MY_SECRET);
return "$rnd-$sig";
}
You would pass the output of getSecuredPersonId() to JavaScript to pass as data in the $.post() or $.get(); posting would be recommended btw.
When the form is submitted your person id would end up in either $_GET['personID'] or $_POST['personID'] depending on the request method. To validate the given value, you run it through this function:
function validateSecurePersonId($securePersonId)
{
if (3 != count($parts = explode('-', $securePersonId))) {
return false;
}
// reconstruct the signed part
$rnd = "{$parts[0]}-{$parts[1]}";
// calculate signature
$sig = hash_hmac('sha1', $rnd, MY_SECRET);
// and verify against given signature
return $sig === $parts[2] ? $parts[0] : false;
}
If the value is properly signed, it will return the original person id that you started out with. In case of failure it would return false.
Small test:
$securePersonId = getSecurePersonId(123);
var_dump($securePersonId);
if (false === validateSecurePersonId($securePersonId)) {
// someone messed with the data
} else {
// all okay
}
Having recently protected my site the best I can against XSS I am now in the process of protecting against CSRF, having watched and read some articles on the matter I have created the below code.
I would like to know whether my implementation is correct in using a string in the database to help security. Is the anything I should be doing differently?? Should I be checking the database on both sides???
Code:
if(!isset($_SESSION['register_token'])){
$keytype = 'register';
$getregisterkey = mysql_query("SELECT key FROM tokenkeys WHERE type='".$keytype."' ") or die(mysql_error());
while ($row = mysql_fetch_array($getregisterkey))
{
$registerkey = mysql_real_escape_string($row['key']);
};
$_SESSION['register_token']=sha1(uniqid(rand(), TRUE).$registerkey);
$_SESSION['register_token_time']=time();
}
<input type="hidden" name="token" value="<?php echo $_SESSION['register_token'];?>" />
if($_POST['register_token']==$_SESSION['register_token']){
$register_token_age=time()-$_SESSION['register_token_time'];
if($register_token_age=>300){
//process form
} else{
//valid token but expired
}
} else{
die('Access Forbidden')
}
XSRF tokens are only as safe as the channel over which the page is sent. Use https and only https on this form and only submit it to an https endpoint. Otherwise a MITM can get your XSRF token from the form as it is served, or as it is submitted.
while ($row = mysql_fetch_array($getregisterkey))
{
$registerkey = mysql_real_escape_string($row['key']);
};
will never execute when there are zero rows which case you don't get much entropy from $getregisterkey when you later do
$_SESSION['register_token']=sha1(uniqid(rand(), TRUE).$registerkey);
so I would make sure that your implementation fails-fast if there are zero rows returned. Maybe change to if ($row = mysql_fetch_array(...)) { ... } else { /* abort */ } since you get no benefit from extra rows.
The rand() needs to be either truly random or a cryptographically strong PRNG.
I am not familiar with PHP's standard libraries but [wikipedia] suggests rand() is not cryptographically strong. wikipedia says
There are proposals for adding strong random number generation to PHP.
Strong cryptography in PHP suggests using openssl_random_pseudo_bytes()
Don’t use rand() or mt_rand()
To generate a cryptographically strong random number in PHP you have to use the function openssl_random_pseudo_bytes() of the OpenSSL library.
If you use weak randomness then an attacker can observe the numbers you generate (by requesting multiple version of the form and parsing out the hidden input) and use that to figure out what the next numbers might be and forge CSRF tokens.
If an attacker modify the 'register_token_time' session property then they can avoid your XSRF checks.
For example, if you have a page that does
$_SESSION[$_POST['x']] = $_POST['y'];
then an attacker can POST
x=register_token&y=pwnd
to replace the register_token stored in the session and then send a post with
token=pwnd
and bypass your XSRF protection.