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.
Related
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.
I'm developing a mobile app which has to access to an external webapp (PHP + Codeigniter) to administrate the actions queried by ajax.
So by this way, there is a problem. If anyone see the urls used, could delete rows, or modify the user's info from the database. So I thought in this system to aboid this:
After a sucessful login I would do this:
// getToken : https://stackoverflow.com/a/13733588/2154101
$this->session->set_userdata('private_token', getToken(50));
$public_token = getToken(50);
$this->session->set_userdata('secure_token', md5("$private_token:$public_token"));
$data['token'] = $public_token;
// some stuff ...
// send $data in JSON
Then the client would the public token in the next query I would do this on the server:
$public_token = $this->input->post('token');
$data['token'] = get_public_token($public_token);
// some stuff ...
// send $data in JSON
Where get_public_token is within a helper with this code:
public get_public_token($public_token) {
$last_secure_token = $this->session->userdata('secure_token');
$private_token = $this->session->userdata('private_token');
$actual_token = md5("$private_token:$public_token");
if ($actual_token === $last_secure_token) {
$public_token = getToken(50);
$this->session->set_data('private_token', getToken(50));
$this->session->set_data('secure_token', md5("$private_token:$public_token"));
return $public_token;
} else { // you are cheating me ...
$this->session->sess_destroy();
redirect('/');
}
}
So only the user of this session could modify the data of the database.
I'm just trying to do the same explained here: https://stackoverflow.com/a/17371101/2154101
The session are encrypted, and I store them in a database too.
Do you think this method will work ok? Am I missing something important?
You should create an API for your mobile application. Create a authentication mechanism.
If your database holds user specific data, then you should create account for each user. So if the user sniffs the network and tries to call the api manually, then he could only change he's own data.
There are some API libraries for php out there, you should look into that.
Actually your solution is doing more than necessary. The only token of interest is the public_token sent back and forth. So you can throw away private_token and secure_token from session data, keeping only public_token for checking. Your current check is something like (X + 5)/2 == (14 + 5)/2 (is [received_token + 5]/2 equal to [14 + 5]/2 ?) when you can simplify to X == 14.
However if someone is sniffing the network, he can get the last token sent to a client and use it to hijack into that session. He can execute anything while the original client doesn't send a request with the outdated token, killing the session.
A better solution would be creating a secure_key after login and keep it at both ends (client and server). Then server would keep sending a new public_token at each response, but the client would send a md5(secure_key + public_token) at requests. This would narrow even more the hijacking window to the exact point where the session started. Without the original key, attackers can't create a valid md5.
However we are talking about minor hacking fans here. Anyone more zealous could hack that anyway. If you are concerned about that, then throw away all that stuff and simply use a HTTPS connection. With a trusted connection your sessions and access control rules are protected.
The better way is create API using SOAP or SAML2.
OAuth can be a very good solution: http://oauth.net/. It takes care of token and has a very secured API! If you wish to support secure authentication of web application + mobile application then it can be a good/proven solution!
On the other hand, it really depends on how complex your current system is and how the system is going to be in future.
I've already got a database set up with a table that is successfully populated with the final (permanent?) OAuth User Token and OAuth User Secret. The thing I don't understand is how I'm supposed to know what the current user's ID is, especially when it's been 2 weeks since their last login. My app is authorized by all of its users, so theoretically Twitter can look at the list of authorized apps for the current user and share the Twitter User ID, right? Isn't there some good way of requesting (on behalf of the current user) what his ID is? I feel like the temporary tokens should be able to facilitate this somehow... If it helps, every user in my app is just a Twitter account with some extra info. I'm just looking for the best way to utilize the tokens and secrets that are in my database...
I'm using PHP (libraries: Codebird-PHP & tmhOAuth) so if you could show an example in PHP that'd be nice, but really I just want to know how I'm supposed to use this information that I'm storing.
Thanks!
I'm assuming you store the data together with some username or user id that identifies the users of your website and links them to their proper twitter id. In order to get the basic info of your user, after authorization, you have to use the endpoint https://api.twitter.com/1.1/account/verify_credentials.json with a GET.
The documentation for the 1.1 API can be found here.
This returns an array. You find the username uder "screen_name" and the user id under "id" or "id_string".
The question is a possible duplicate of Get current user's info from Twitter API, but I've added an answer because that discussion points to the deprecated API. The code you find there, nevertheless, is still useful (it appears to use Abraham William's library, but the steps are basically the same). Replace the classes and functions with those you have in Matt Harris' library. I don't know codebird, sorry!
EDIT: I am also providing a code sample (tested and working, although I have issues with tmhOAuth, so I use it occasionally only for myself. I have noticed that, when I try to post, it sometimes returns some weird error codes and I can't figure out why):
// Authentication page, with button. You have already connected to your database
$mywebsiteuser = $_SESSION['website_user_id'];
$query= "SELECT * FROM `table_where_you_store_twitter` WHERE website_user_id ='$mywebsiteuser'";
$sql= $mysqli->query($query) or die($mysqli->error.__LINE__); // or whatever else to check is the query fails.
if ($sql->num_rows != 0){
//etc. retrieve data and set the sessions.
// already got some credentials stored?
if ( isset($_SESSION['access_token']) ) {
$tmhOAuth->config['user_token'] = $_SESSION['access_token']['oauth_token'];
$tmhOAuth->config['user_secret'] = $_SESSION['access_token']['oauth_token_secret'];
$code = $tmhOAuth->request('GET', $tmhOAuth->url('1/account/verify_credentials'));
if ($code == 200) {
$resp = json_decode($tmhOAuth->response['response']);
echo $resp->screen_name;
echo $resp->id;
//Etc. Instead of printing them you it's a good idea to store them in the db.
} else {
outputError($tmhOAuth);
}
// we're being called back by Twitter
} elseif (isset($_REQUEST['oauth_verifier'])) {
$tmhOAuth->config['user_token'] = $_SESSION['oauth']['oauth_token'];
$tmhOAuth->config['user_secret'] = $_SESSION['oauth']['oauth_token_secret'];
$code = $tmhOAuth->request('POST', $tmhOAuth->url('oauth/access_token', ''), array(
'oauth_verifier' => $_REQUEST['oauth_verifier']
));
if ($code == 200) {
//etc.
Anyhow, all in all, in order to get the info of a user you need them to authorize your app first. I check if I have something from my user with the user's session variables on my website, not through twitter. If I have nothing stored, I ask them to authorize the app. I hope this helps.
Access Token : 1274865264-QiVY50RGnmJz6AU9IPRxxiXfv4DYqo0nj6wg8hS
Access Token Secret : fZQnHSuSpwARicIdLqkqQLy1JeG9LxrbNIRKypWcGR
First part of Access Token is user id
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
}
We have some problems with users performing a specific action twice, we have a mechanism to ensure that users can't do it but somehow it still happens. Here is how our current mechanism works:
Client side: The button will be disabled after 1 click.
Server side: We have a key hash in the URL which will be checked against the key stored in SESSIONS, once it matches, the key is deleted.
Database side: Once the action is performed, there is a field to be flagged indicating the user has completed the action.
However, with all these measures, still there are users able to perform the action twice, are there any more safer methods?
Here is the partial code for the database side:
$db->beginTransaction();
// Get the user's datas
$user = $db->queryRow("SELECT flag FROM users WHERE userid = {$auth->getProperty('auth_user_id)}");
if ($user['flag'] != 0) {
$db->rollback();
// Return with error
return false;
}
// Proceed with performing the action
// --- Action Here ---
// Double checking process, the user data is retrieved again
$user = $db->queryRow("SELECT flag FROM users WHERE userid = {$auth->getProperty('auth_user_id)}");
if ($user['flag'] != 0) {
$db->rollback();
// Return with error
return false;
}
// --- The final inserting query ---
// Update the flag
$db->query("UPDATE users SET flag = 1 WHERE userid = {$auth->getProperty('auth_user_id)}");
$db->commit();
return true;
It is good to see that you have taken all measures to defeat the bad guys. Speaking in terms of bad guys:
Client side: This can easily be bypassed by simply disabling javascript. Good to have anyways but again not against bad guys.
Server side: This is important, however make sure that you generate a different hash/key with each submission. Here is a good tutorial at nettutes on how to submit forms in a secure fashion.
Database side: Not sure but I suspect, there might be SQL injection problem. See more info about the SQL Injection and how to possibly fix that.
Finally:
I would recommend to you to check out the:
OWASP PHP Project
The OWASP PHP Project's goal (OWASP PHP Project Roadmap) is to enable developers, systems administrators and application architects to build and deploy secure applications built using the PHP programming language.
Well the JS method and Hash method may be cheated by some notorious guy, but 3rd method seems to be very good in order to protect the redundancy. There must be some programming flaw to get passed this.
Why don't u just check the flag field on the page where you are inserting the values rather than where user performing the action (if you are doing it now)
Pseudocode follows:
<?
$act_id; // contains id of action to be executed
$h = uniqid('');
// this locks action (if it is unlocked) and marks it as being performed by me.
UPDATE actions SET executor = $h WHERE act_id = $act_id AND executor = '';
SELECT * FROM actions WHERE executor = $h;
//
// If above query resulted in some action execute it here
//
// if you want to allow for executing this exact action in the future mark it as not executed
UPDATE actions SET executor = '' WHERE act_id = $act_id;
Important things:
First query should be update claiming
the action for me if it is yet
unclaimed.
Second should be query
grabbing action to execute but only
if it was claimed by me.