I have an application that i built in php 7 with the code-igniter framework and my problem is with the session data , storing and retrieving session data works fine , but occasionally when two people log in at close intervals , the session data for the first user is also retrieved for the second user, searched through the site , saw something similar here (wrong data in PHP session) that suggested that it might be a caching issue (my site uses nginx for caching) , but no concrete solutions were suggested. Any suggestions or Ideas will be appreciated.
Edit : Here is the section of my login library for authentication
public function login_account($email,$password)
{
$db = "db";
$data = array("login_mail" => sha1($email));
$query_result = $this->CI->m_eauth->get_login_password($data,$db);
$hash_password ="";
foreach($query_result->result_array() as $value)
{
$hash_password = $value['hash_password'];
$site_name = $value['hash_name'];
$account_type = $value['account_type'];
$site_match_id = $value['site_match_id'];
$site_levels = $value['levels'];
$site_roles = $value['roles'];
}
if(password_verify($password, $hash_password)){
// Success!
$session_data = array(
"site_id"=>$site_match_id,
"site_email"=>$email,
"site_name"=>$site_name,
"site_avatar"=>md5($email).".jpg",
"site_type"=>$account_type,
"site_levels"=>$site_levels,
"site_roles"=>$site_roles
);
$this->CI->session->set_userdata($session_data);
return "successful";
}
else{
// Invalid credentials
return "unsuccessful";
}
}
Let me add that the login works fine and individual sessions work just fine. But every now and then the problem i described happens , and i'ts quite confusing as i don't know where to look.
There's no real way to sugar coat this, sessions aren't some magical part of PHP that you enable you to just call session_start() and go about your day. If your application is leaking sessions then you haven't secured it properly and you need to fix it.
Session security is a pretty big deal, given that a hijacked session basically gives an attacker total access to someone else's account.
I would recommend you read the official PHP session docs and also consider implementing the Nginx userid module as an additional measure for identifying users.
Related
I am building a login form using ajax php and MySql.
I've done my fair share of research and I didn't like much posts found online, so I've built the below code.
My question is, is this secure at all? I'm not using any hashing and I'm not sure how it would be done with ajax. All the examples are much appreciated
INDEX.PHP
<script>
$(document).ready(function(){
$('form[name=loginForm]').submit(function() {
$.post('ajax.php', { username: $('[name=username]').val(),
password: $('[name=password]').val()},
function(data){
if(data.success){
alert('welcome');
}else{
alert("incorrect");
}
}, 'json');
return false;
});
});
</script>
ajax.php
<?php
if($_POST){
/** Fetch data from mysql **/
$u = $_POST['username'];
$p = $_POST['password'];
$sql = "SELECT * FROM users WHERE username='$u' AND password='$p' ";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$_SESSION['userid'] = $row["username"];
$_SESSION['userid'] = $row["username"];
$data['success'] = true;
}
}
else
{
$data['success'] = false;
}
/** Fetch data from mysql **/
echo json_encode($data);
} ?>
THANKS ALOT
My question is, is this secure at all?
No, it is not secure.
I'm not using any hashing and I'm not sure how it would be done with ajax.
Authentication actually cannot be done with ajax. With respect, you're barking up the wrong tree.
Start by reading this part of the PHP manual. http://php.net/manual/en/faq.passwords.php Go read it now. We'll wait.
Welcome back. You should never put your plain text password into a database. If you're not sure why that's true read about the "Ashley Madison data breach" online or go visit https://haveIBeenPwned.com/
You want to make it as hard as possible for a cybercriminal who steals your user table to guess your users' passwords. If you store them as text, they are trivial to guess.
Let's say your users are registered already. The point of your password authentication is to
gather the username and password from the user.
look up the user by name in your database, pulling back the hashed password.
compare the hashed password in your database with the one you gathered from the user. php's password_verify() function does this well.
if the validation fails, refuse the user's information. Do not give them any hint what was wrong. Simply tell them "your login failed." You don't want to tell them "you gave the right username but the wrong password."
if the validation succeeds, you then generate a session for that user so they can continue to use other pages in your web app without logging in again. Read about php sessions here.
you use a session id to represent the session. A session id is a hard-to-guess data token with a limited lifetime. php offers a session_create_id() method for this.
you put that session id in a cookie and feed it back to your browser. You can't reliably feed cookies to browsers with AJAX so your authentication strategy won't work.
Subsequent requests to your web app present the session id in the cookie. You check it to make sure it it's valid and it hasn't timed out. Then you do what the user asks you to do.
I apologize if this question has been asked before, if it has just link me to it in a comment.
So I've created a web-service for an android/iOS app with php that works like this.
The app sends a request to http://www.example.com/ws/getCookingData.php with 2 $_POST paramaters userID and foodType
The php file then queries the database using those two variables and returns a json_encode result.
My worry is that if someone were to discover my web-service link they could spam it with post requests resulting in 100's of call to my database that are just un-needed
Below is an example of my current getData.php file
<?php
$userID = mysql_escape_string($_POST['userID']);
$foodType = mysql_escape_string($_POST['foodType']);
$mysqli = getDB();
echo json_encode(getDate($mysqli, $userID, $foodType); //mysql database interaction is here
$mysqli->close();
?>
There is nothing here preventing hackers from attempting to post malicious SQL statements into my database
So what I'm wondering is if I added a 3rd parameter to my post request called appID would this be a good solution?
For example if I were to update my getData.php file to below would this be much more secure or is there a vulnerability I'm missing?
<?php
$appID = $_POST['appID'];
if($appID === "hardCodedEquivalentID"){
$userID = mysql_escape_string($_POST['userID']);
$foodType = mysql_escape_string($_POST['foodType']);
$mysqli = getDB();
echo json_encode(getDate($mysqli, $userID, $foodType); //mysql database interaction is here
$mysqli->close();
}
?>
If this is a good solution or there is already an equivalent practice that would achieve what I'm trying to do please let me know
First of all, use mysqli or PDO instead of mysql function which is deprecated. Secondly create a function which will authenticate the user and see whether the user has the permission to access the data. And thirdly try to LIMIT the data to 100 or so if possible.
Hardcoding the appId is not the solution. Create unique Id's for each specific registered user and then match that appId against that particular user. And when their session is expired clear the access token. And at the start of their session, you can login in them and create new access token and can use the same for their entire session.
To answer your first question
My worry is that if someone were to discover my web-service link they
could spam it with post requests resulting in 100's of call to my
database that are just un-needed
If someone wants to DoS you then you can't do much in your code to prevent it but you may try using service like cloudflare. Not worth worrying about it at the beginning.
About
There is nothing here preventing hackers from attempting to post
malicious SQL statements into my database
then just read documentation on PDO
1- Use mysql_real_escape_string()
2- Use str_replace(" ","",$_POST['userID']) and str_replace("%20","",$_POST['userID'])(Because malicious attacks involves in using %20 and space to inject sql query)
3- Add this line to the top of the page, so the script only takes request if its been from your website (That's what I am using too!)
$referrer = $_SERVER['HTTP_REFERER'];
if (strpos($referrer,"yourwebsite.com")) {
} else {
die();
}
I'm having a problem with Symfony creating a new session on each page load, rather than carrying data across requests. The auto_start in the session section in the config.yml is set to false, and regular php sessions work fine. It's only when running in symfony that I get the problem.
For example, I created the test action:
public function sessionTestAction()
{
$s_Response = '<html><head></head><body><p>Foo</p></body></html>'; //Initialize response and headers
$a_Headers = array();
$i_StatusCode = 200;
$oSession = $this->get('session');
var_dump($oSession->all());
if(!$oSession->has('Test'))
{
$oSession->set('Test', 'Bar');
}
$oSession->save();
return new Response($s_Response, $i_StatusCode, $a_Headers);
}
The expected action is, that on the first page load, the var_dump will yield nothing, and that on any subsequent executions, it will contain Test=>Bar. However, it never gets that data across requests.
In addition, it creates a new session id for each request.
I am using Symfony v2.0.15, and PHP v5.4
Anyone have any ideas?
Edit:
I made some progress, I think. I made the following changes to the test action:
public function sessionTestAction()
{
//Initialize response and headers
$oRequest = $this->get('request');
$a_Headers = array();
if (isset($oRequest->headers->all()['cookie']))
{
$a_Headers['Set-Cookie'] = $oRequest->headers->all()['cookie'];
}
$i_StatusCode = 200;
$oSession = $oRequest->getSession();
$oSession->start();
$s_Response = print_r($oSession->all(), true);
if(!$oSession->has('Test'))
{
$oSession->set('Test', 'Bar');
}
$oSession->save();
$oResponse = new Response($s_Response, $i_StatusCode, $a_Headers);
return $this->render('Bundle:Default:index.html.twig', array('response' => $s_Response), $oResponse);
}
Where that twig file has just {{response|raw}}. It now holds the session for 2 out of 3 of the requests. However, on the third request, it's cleared.
Turned out the problem was, someone added a line to set a session cookie whenever the app.php was run, not knowing that symfony handled sessions itself, I guess. Problem solved.
I got this problem a couple times, its very annoying. So, let me describe possible solution.
Open dev environment - yourdomain.com/app_dev.php/ Try to refresh page a couple times. If you see session ID changed each time - it means that sessions are broken.
If you are using chrome (if not - you should, its the best for developers ;) ) - you can open developers tools (click F12).
Next, check Network tab, refresh page and locate your main request.
Check headers for your request - if should see "Cookie:PHPSESSID".
If you dont see - something wrong with cookies. In my case it was
framework:
session:
cookie_domain: mydomain.com
i'm trying to use sessions to store the amount of login attempts. When the maximum of login attempts is reached i'm storing the client's ip address in a blacklist table.
Some things i've taken into account, you might need to know about:
I'm using session_regenerate_id(); after i set a session value.
I'm not using any cookies apart from the session since this is not necessary and not 2012 :p
The users ip is blacklisted until i mannually delete his row from the blacklist table.
The SESSION_MAX_ATTEMPTS is a defined constant and set to 5.
The index.php?module=login&task=blacklist page is just showing the user a message that it's blacklisted. This page does not have any functionallity.
i'm using a custom build php framework, so i had to translate some OOP called method's to simplified php code.
The following function is called before a login query is executed:
private function preventAttack()
{
$blocked = getData("SELECT count(*) as blocked FROM blacklist WHERE ip = #Value0;", Array( $_SERVER['REMOTE_ADDR'] ));
if($blocked[0]["blocked"] == "1")
{
redirect("index.php?module=login&task=blacklist");
}
$old = (int)$this->session->get("login_attempts");
if(!empty($old))
{
if($old > SESSION_MAX_ATTEMPTS)
{
setData("INSERT INTO blacklist SET ip = #Value0;", Array( $_SERVER['REMOTE_ADDR'] ));
redirect("index.php?module=login&task=blacklist");
}
else
{
$old++;
$this->session->set("login_attempts",$old);
}
}
else
{
$this->session->set("login_attempts", 0);
}
}
The first if statement works including both query's but i'm stuck at what's the best way to store the amount of attempts and what's the best way to ++ it? Maybe you guys can set me in the right direction.
If you have any questions about my code, please add a comment. I know it's a bit unreadable since it's from my framework, i've translated this a bit before posting it.
Store the number of failed attempts in the database, not the session. N.B.: You probably need to keep each failure along with a timestamp in its own record (and ignore/delete anything older than a threshold).
By the way, in response to deceze's comment:
Ginormous flaw in this approach: sessions depends on the client sending a cookie. Real attackers will simply not send the cookie back. ziiiing You'll have to go by IP for everything.
The solution to this is that you don't accept login attempts that don't come with a valid session cookie, set elsewhere.
Thanks a lot guys, i've learned a lot from your comments. For users with the same problem i'll explain what i've learned and how i'm using that knowledge.
What i've learned so far:
Real attackers will simply not send a cookie back. So using cookies or sessions doesn't make any sense in this case.
If you want to blacklist attackers, use can use a firewall that does this automatically for you. It makes no sense to do this from your script, sessions are too easily circumvented and if you check IP addresses you could block out entire offices or schools on the same external IP.
Whatever you do: store the login attempts data only on the server in a database/flatfile/etc. The user or attacker will not be able to edit this data so easily.
And if you do store data on your web server for performance, store it in a file, not a DB.
If i forgot something please comment and i will edit this post.
I checked with my hosting provider and they are already blocking out a lot of these attackers using solutions like the firewall mentioned above. So i will stop trying to also do this in my scripts.
My script is fixed now and only blacklisting users and noob hackers guessing passwords:
private function preventAttack()
{
$blocked = getData("SELECT count(*) as blocked FROM blacklist WHERE ip = #Value0;", Array( $_SERVER['REMOTE_ADDR'] ));
if($blocked[0]["blocked"] == "1")
{
redirect("index.php?module=login&task=blacklist");
}
$old = (int)$this->session->get("login_attempts");
if($old > 0)
{
if(($old + 1) >= SESSION_MAX_ATTEMPTS)
{
setData("INSERT INTO blacklist SET ip = #Value0;", Array( $_SERVER['REMOTE_ADDR'] ));
$this->session->set("login_attempts", 0);
redirect("index.php?module=login&task=blacklist");
}
else
{
$old++;
$this->session->set("login_attempts",$old);
}
}
else
{
$this->session->set("login_attempts", 1);
}
}
Since i'm not quite sure how many users this platform is going to get and i don't want a possible high mysql server load i've decided not to store the login attempts in the database. Maybe in the future, who knows.
I hope someone can advise / direct / shed some light on :
i have a rails application that uses Authlogic for authentication.
i would like to incorporate cometchat into this application - (in reality any chat IM would do - but cometchat seemed to fit nicely because ajax-im does not like windows)
The environment is the following : The rails app is running on a windows machine - and will be available to the local network (no internet)
So to be able to use cometchat - i am running the WAMP server.
Into the nitty gritty of php(which i dont know well at all)
authlogic keeps my user session for me.
but for cometchat to work i need to pass the getUserID function the current user.
(comet chat assumes that there is a php session variable - but i dont have this with rails)
So how can i pass the rails session user to the getUserID function.
the function looks like this:
*function getUserID() {
$userid = 0;
if (!empty($_SESSION['userid'])) {
$userid = $_SESSION['userid'];
}
return $userid;
}*
the next function has to do with the friends list - but im sure this can be solved with sql inside the php page once i have the current user.
Again - all and any guidance is welcome here. Even if it means an alternate chat solution.
Many thanks in advance.
akza,
You can store the userid(numeric, non-zero, unique preferably the primary key from your users table) of currently logged-in user in a cookie say 'userid_for_cometchat'
and can access it as $_COOKIE['userid_for_cometchat'] in PHP
So your getUserID() should look like:
function getUserID() {
$userid = 0;
if (!empty($_COOKIE['userid_for_cometchat'])) {
$userid = $_COOKIE['userid_for_cometchat'];
}
return $userid;
}
You need to create this cookie on login and make sure to destroy it on logout.
This should work well.
CometChat Support team is real great at doing such stuff and they have performed a lot of custom modifications for me and I'm really satisfied by their job.