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.
Related
I'm raising the security levels of a software I'm working on.
The goal, as stated in the title, is to prevent CSRF attacks.
In a scenario that involves the use of the backend by a user who has to manage a control panel, let's take the example that the user must initialize a command that deletes a data from the database, obviously if it were used:
Delete Post
it would be an announced suicide.
By reading the PHP documentation (https://www.php.net/manual/en/function.random-bytes.php)
I found this that interested me a lot but it also made me ask questions.
If you implement the code in it:
function RandomToken($length = 32){
if(!isset($length) || intval($length) <= 8 ){
$length = 32;
}
if (function_exists('random_bytes')) {
return bin2hex(random_bytes($length));
}
if (function_exists('mcrypt_create_iv')) {
return bin2hex(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM));
}
if (function_exists('openssl_random_pseudo_bytes')) {
return bin2hex(openssl_random_pseudo_bytes($length));
}
}
function Salt(){
return substr(strtr(base64_encode(hex2bin(RandomToken(32))), '+', '.'), 0, 44);
}
$token = (RandomToken())."\n".Salt()."\n";
we will get such a thing that every time the page is refreshed it will change:
13356ac7fc5e058b61bbad693d84ca2e1d9ae584db356dfa928098800d46ed6d F0ToG948CsaUF2wGDSdt.DuyUMKY1VC/liEAyjTB6ME=
Which is good ...
but:
If you choose the way of storing codes in the db and check that the generated code is new and never used then I validate the query statements for the elimination, who guarantees me that a user with bad intentions cannot generate the same 108-character code that maybe it has never been used?
So a solution of a unique time code would be better?
How to solve this?
Regarding your questions:
If you choose the way of storing codes in the db (...)
Why store the codes in the DB? Store them in the users session. You can have one anti-CSRF token for the whole session and it makes handling of the token easier.
who guarantees me that a user with bad intentions cannot generate the same 108-character code that maybe it has never been used?
Math. 32 byte random variable has an entropy of 256 bits. 128 bits would be sufficient to prevent a successful brute force attack and this is way above this.
So a solution of a unique time code would be better?
No. You already have all you need. And you don't need salt for the purpose of token creation too.
I'm using a $_GET function in php to perform functions for my Java server.
The website is similar to this: http://api.somewebsite.com/perform/function.php?authkey1=randomMD5&authkey2=randomMD5¶meters=something
Where the variables authkey1 and authkey2 represent a randomly generated MD5. The PHP script does connect to MySQL, and I've already prevented injection. However, before anything else gets parsed by the code, the URL has to include those 2 authkeys or else they get a message saying, "Improper authentication".
Here's an example of what my code does:
// Security key check
$key1 = "skghlskfhgj42u6928749856478937683471095sndgfnsvnrandom";
$key2 = "dbnksgh794ytowhjklgn934ngmsnnmlrj9096345u075u80375ngsr";
$g_key1 = $_GET['authkey1'];
$g_key2 = $_GET['authkey2'];
if (empty($g_key1) || empty($g_key2)) {
die("Improper authorization");
}
if ($g_key1 != $key1 || $g_key2 != $key2) {
die("Improper authorization");
}
// If authentication passes, move on to other functions
Is this method secure or not? Thanks!
You are on the right track by having two secret keys, but the people in the comments above are all correct. This is not particularly secure. What would I do? I would make one shared key that your two servers know about. A key like
$key1 = "skghlskfhgj42u6928749856478937683471095sndgfnsvnrandom";
This key become the salt to some sort of operation you perform on all the parameters. Both servers use the salt to build a hash which is passed on the request. The receiving end of the script will check to make sure the passed hash is the same that it would build from the request.
$passed_hash = $_GET['hash'];
$params = $_GET;
unset($params['hash']);
ksort($params);
$str = '';
foreach($params as $k => $v){
$str .= $v;
}
// the key is the salt
$str .= $key1;
$calculated_hash = md5($str);
if($passed_hash == $calculated_hash){
// you know everything is good
}
Make sure to include timestamp in the request so no two requests are the same.
No, that method is not secure, but rather "security by obscurity".
First and most important, use TLS/SSL. Second, find a 3rd party library to handle request authentication for you. I couldn't find one that did what I needed, so I put one together myself.
The library is called Query Auth and I wrote it to handle this exact use case. It's a generic implementation of the Signature Version 2 implementation from the AWS SDK for PHP 2. It handles API key and API secret generation, signature creation and validation, and can optionally protect against replay attacks. Documentation and an example implementation are available.
I have read about users being able to manipulate website cookie and use it to exploits security loopholes. I did a search and came across an idea posted online. Here is the code below that is, after the username and password of the user are authenticated;
$Separator = '--';
$uniqueID = 'jhlhgjh12u0#345';
$Data = $userID.' '.md5('65748');
$expire=time()+60*24;
setcookie('verify-user', $Data.$Separator.md5($Data.$uniqueID), $expire);
The code above will set the cookie using a uniqueID, the userID, a MD5 hash numbers and a separator. The uniqueID, md5 hash numbers and separator are set by the developer. The idea is that a user won't be able to manipulate the cookie because the don't know the UniqueID, and the md5 hash numbers. The code below is used to test each cookie if they are manipulated or not
if ($_COOKIE) {
$Separator="--";
$uniqueID = 'jhlhgjh12u0#345';
$Cut = explode($Separator, $_COOKIE['verify-user']);
if (md5($Cut[0].$uniqueID) === $Cut[1]) {
$_COOKIE['verify-user'] = $Cut[0];
} else {
echo "fake cookie";
}
}
else {
echo "fake cookie";
}
I am wondering if this method is security tight or if there are loopholes too. criticism and corrections are welcomed
This is known as message signing. You hash the message together with a secret and attach that "signature" to the message itself. This allows the recipient to verify that the creator/signer of the message is in possession of the secret, without revealing the secret itself.
The problem with your particular implementation is that
the secret is too small
the hashing algorithm is unsuitable for the task
the cookies never change and never expire; if a cookie is stolen there's no recourse
You should use a longer secret, the longer the better. You should also use a hashing algorithm that is suited for the task, namely something like HMAC (hash-based message authentication). E.g.:
hash_hmac('sha512', $data, $secret)
You can see an implementation of a similar thing, including expiration of values, here.
The most important thing though: think thrice about whether a signed plain text message is the best way to go here in the first place. Perhaps you want a session-like system, in which an entirely meaningless random string is used as an id for data that is stored on the server. This completely eliminates the problem of users manipulating the cookie.
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.
I am running a website, and there is a scoring system that gives you points for the number of times you play a game.
It uses hashing to prove the integrity of http request for scoring so users cannot change anything, however as I feared might happen, someone figured out that they didn't need to change it, they just needed to get a high score, and duplicate the http request, headers and all.
Previously I'd been prohibited from protecting against this attack because it was considered unlikely. However, now that it has happened, I can. The http request originates from a flash game, and then is validated by php and php enters it into the database.
I'm pretty sure nonces will solve the issue, but I'm not exactly sure how to implement them. What is a common, and secure way of setting up a nonce system?
It's actually quite easy to do... There are some libraries out there to do it for you:
PHP Nonce Library
OpenID Nonce Library
Or if you want to write your own, it's pretty simple. Using the WikiPedia page as a jumping off point, In pseudo-code:
On the server side, you need two client callable functions
getNonce() {
$id = Identify Request //(either by username, session, or something)
$nonce = hash('sha512', makeRandomString());
storeNonce($id, $nonce);
return $nonce to client;
}
verifyNonce($data, $cnonce, $hash) {
$id = Identify Request
$nonce = getNonce($id); // Fetch the nonce from the last request
removeNonce($id, $nonce); //Remove the nonce from being used again!
$testHash = hash('sha512',$nonce . $cnonce . $data);
return $testHash == $hash;
}
And on the client side:
sendData($data) {
$nonce = getNonceFromServer();
$cnonce = hash('sha512', makeRandomString());
$hash = hash('sha512', $nonce . $cnonce . $data);
$args = array('data' => $data, 'cnonce' => $cnonce, 'hash' => $hash);
sendDataToClient($args);
}
The function makeRandomString really just needs to return a random number or string. The better the randomness, the better the security... Also note that since it's fed right into a hash function, the implementation details don't matter from request to request. The client's version and the server's version don't need to match. In fact, the only bit that needs to match 100% is the hash function used in hash('sha512', $nonce . $cnonce . $data);... Here's an example of a reasonably secure makeRandomString function...
function makeRandomString($bits = 256) {
$bytes = ceil($bits / 8);
$return = '';
for ($i = 0; $i < $bytes; $i++) {
$return .= chr(mt_rand(0, 255));
}
return $return;
}
Nonces are a can of worms.
No, really, one of the motivations for several CAESAR entries was to design an authenticated encryption scheme, preferably based on a stream cipher, that is resistant to nonce reuse. (Reusing a nonce with AES-CTR, for example, destroys the confidentiality of your message to the degree a first year programming student could decrypt it.)
There are three main schools of thought with nonces:
In symmetric-key cryptography: Use an increasing counter, while taking care to never reuse it. (This also means using a separate counter for the sender and receiver.) This requires stateful programming (i.e. storing the nonce somewhere so each request doesn't start at 1).
Stateful random nonces. Generating a random nonce and then remembering it to validate later. This is the strategy used to defeat CSRF attacks, which sounds closer to what is being asked for here.
Large stateless random nonces. Given a secure random number generator, you can almost guarantee to never repeat a nonce twice in your lifetime. This is the strategy used by NaCl for encryption.
So with that in mind, the main questions to ask are:
Which of the above schools of thought are most relevant to the problem you are trying to solve?
How are you generating the nonce?
How are you validating the nonce?
Generating a Nonce
The answer to question 2 for any random nonce is to use a CSPRNG. For PHP projects, this means one of:
random_bytes() for PHP 7+ projects
paragonie/random_compat, a PHP 5 polyfill for random_bytes()
ircmaxell/RandomLib, which is a swiss army knife of randomness utilities that most projects that deal with randomness (e.g. fir password resets) should consider using instead of rolling their own
These two are morally equivalent:
$factory = new RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();
$_SESSION['nonce'] [] = $generator->generate(32);
and
$_SESSION['nonce'] []= random_bytes(32);
Validating a Nonce
Stateful
Stateful nonces are easy and recommended:
$found = array_search($nonce, $_SESSION['nonces']);
if (!$found) {
throw new Exception("Nonce not found! Handle this or the app crashes");
}
// Yay, now delete it.
unset($_SESSION['nonce'][$found]);
Feel free to substitute the array_search() with a database or memcached lookup, etc.
Stateless (here be dragons)
This is a hard problem to solve: You need some way to prevent replay attacks, but your server has total amnesia after each HTTP request.
The only sane solution would be to authenticate an expiration date/time to minimize the usefulness of replay attacks. For example:
// Generating a message bearing a nonce
$nonce = random_bytes(32);
$expires = new DateTime('now')
->add(new DateInterval('PT01H'));
$message = json_encode([
'nonce' => base64_encode($nonce),
'expires' => $expires->format('Y-m-d\TH:i:s')
]);
$publishThis = base64_encode(
hash_hmac('sha256', $message, $authenticationKey, true) . $message
);
// Validating a message and retrieving the nonce
$decoded = base64_decode($input);
if ($decoded === false) {
throw new Exception("Encoding error");
}
$mac = mb_substr($decoded, 0, 32, '8bit'); // stored
$message = mb_substr($decoded, 32, null, '8bit');
$calc = hash_hmac('sha256', $message, $authenticationKey, true); // calcuated
if (!hash_equals($calc, $mac)) {
throw new Exception("Invalid MAC");
}
$message = json_decode($message);
$currTime = new DateTime('NOW');
$expireTime = new DateTime($message->expires);
if ($currTime > $expireTime) {
throw new Exception("Expired token");
}
$nonce = $message->nonce; // Valid (for one hour)
A careful observer will note that this is basically a non-standards-compliant variant of JSON Web Tokens.
One option (which I mentioned in comment) is recording gameplay and replay it in secure environment.
The other thing is to randomly, or at some specified times, record some seemingly innocent data, which later can be used to validate it on server (like suddenly live goes from 1% to 100%, or score from 1 to 1000 which indicate cheat). With enough data it might just not be feasible for cheater to try to fake it. And then of course implement heavy banning :).
This very simple nonce changes every 1000 seconds (16 minutes)
and can be used for avoiding XSS where you are posting data to and from the same application. (For example if you are in a single page application where you are posting data via javascript. Note that you must have access to the same seed and nonce generator from the post and the receiving side)
function makeNonce($seed,$i=0){
$timestamp = time();
$q=-3;
//The epoch time stamp is truncated by $q chars,
//making the algorthim to change evry 1000 seconds
//using q=-4; will give 10000 seconds= 2 hours 46 minutes usable time
$TimeReduced=substr($timestamp,0,$q)-$i;
//the $seed is a constant string added to the string before hashing.
$string=$seed.$TimeReduced;
$hash=hash('sha1', $string, false);
return $hash;
}
But by checking for the previous nonce, the user will only be bothered if he waited more than 16.6 minutes in worst case and 33 minutes in best case. Setting $q=-4 will give the user at least 2.7 hours
function checkNonce($nonce,$seed){
//Note that the previous nonce is also checked giving between
// useful interval $t: 1*$qInterval < $t < 2* $qInterval where qInterval is the time deterimined by $q:
//$q=-2: 100 seconds, $q=-3 1000 seconds, $q=-4 10000 seconds, etc.
if($nonce==$this->makeNonce($seed,0)||$nonce==$this->makeNonce($seed,1)) {
//handle data here
return true;
} else {
//reject nonce code
return false;
}
}
The $seed, could be the any function call or user name, etc. used in the process.
It is not possible to prevent cheating. You can only make it more difficult.
If someone came here looking for a PHP Nonce Library: I recommend not using the first one given by ircmaxwell.
The first comment on the website describes a design flaw:
The nonce is good for one certain time window, i.e. the nearer the
user gets to the end of that windows the less time he or she has to
submit the form, possibly less than one second
If you are looking for a way to generate Nonces with a well-defined lifetime, have a look at NonceUtil-PHP.
Disclaimer: I am the author of NonceUtil-PHP