Sort of a methods/best practices question here that I am sure has been addressed, yet I can't find a solution based on the vague search terms I enter.
I know starting off the question with "Fast and easy" will probably draw out a few sighs, so my apologies.
Here is the deal.
I have a logged in area where an ADMIN can do a whole host of POST operations to input data relating to their profile. The way I have data structured is pretty distinct and well segmented in most tables as it relates to the ID of the admin.
Now, I have a table where I dump one type of data into and differentiate this data by assigning the ADMIN's unique ID to each record. In other words, all ADMINs have this one type of data writing to this table. I just differentiate by the ADMIN ID with each record.
I was planning on letting the ADMIN remove these records by clicking on a link with a query string - obviously using GET. Obviously, the query structure is in the link so any logged in admin could then exploit the URL and delete a competitor's records.
Is the only way to safely do this through POST or should I pass through the session info that includes password and validate it against the ADMIN ID that is requesting the delete?
This is obviously much more work for me.
As they said in the auto repair biz I used to work in... there are 3 ways to do a job: Fast, Good, and Cheap. You can only have two at a time. Fast and cheap will not be good. Good and cheap will not have fast turnaround. Fast and good will NOT be cheap. haha
I guess that applies here... can never have Fast, Easy and Secure all at once ;)
Thanks in advance...
As a general rule, any operation that alters state (whether its session state, or database state) should use POST. That means the only 'safe' SQL operation you can perform with GET is SELECT. Even if you're only using a back-end admin thing, you shouldn't use get. Imagine re-opening your browser and finding that the last time you closed firefox was on your 'DELETE EVERYTHING' GET->delete page resulting in everything being deleted again.
One of the main reasons for this is preventing cross-site request forgeries. For example, if you had a page that took a GET variable such as http://example.com/account?action=logout, an attacker could post an image on your site like this:
<img src="http://example.com/account?action=logout" />
and anyone who opened a page containing that image tag would be immediately logged out, even if they were an admin. It would be very annoying to then search through your raw database for that data and remove it.
Although POST operations are 'nearly' as easy to forge, as a general rule with any web security issue, the trade-off is speed/simplicity vs. security, so you're going to have to choose one or the other.
You should have some kind of session set up.
Using POST over GET gets you nothing tangible as far as security is concerned. POSTs can be forged just like GETs.
So assuming once your admin logs in, you've got some kind of identifier in the session, you just leverage that.
Consider something roughly similar to this:
<?PHP
session_start();
if (empty ($_SESSION['admin_id'])) die("Log in, son.");
if (empty($_GET['record_id'])) die("You've got to tell me which record to delete!");
if (! is_numeric($_GET['record_id'])) die("Invalid record ID");
//just to be totally safe, even though we just enforced that it's numeric.
$record_id = mysql_real_escape_string($_GET['record_id']));
$sql = "DELETE FROM table WHERE record_id='{$record_id}' AND admin_id = {$_SESSION['admin_id']}";
mysql_query($sql);
echo "Record was deleted (assuming it was yours in the first place)";
?>
In that example, we're avoiding the "delete someone else's records" problem by leveraging the WHERE clause in the DELETE query. Of course, to be more user friendly, you'd want to first fetch the record, then compare the admin_id on the record to the admin_id in $_SESSION, and if they don't match, complain, log something, throw an error, etc.
HTH
You're saying: "What if Admin 123 access the URL of Admin 321 directly, thus deleting his stuff?" no?
If that's so then, every admin that is logged in should have at least one session with some unique identifier to that admin. If not, he shouldn't be able to be in the admin section in the first place. Can't you just compare the identifier in the URL with the identifier in the session?
If any user, not just an admin, access those 'delete me' URLs he should be identified by a session as the original 'owner' (admin) of that data.
Related
I am trying to determine the safest way or best practice to go about Deleting in a CRUD using php and MySQL. The way I was taught was to create Delete link and have delete.php run my delete() method on the id passed in the $_GET variable.
If I allowed a web crawler to see this page couldn't it delete the entire database if it crawled each delete link on each page?
Couldnt someone who somehow figured out the page that calls delete() manually load the url with whatever number they wanted to in the $_GET and delete records?
Is it better to never delete anything through the CRUD but to add a Deleted column to the table and make it appear as though it has been deleted to the user?
Am I just paranoid?
If I allowed a web crawler to see this page couldnt it delete the entire database if it crawled each delete link on each page?
Yes.
Couldnt someone who somehow figured out the page that calls delete() manually load the url with whatever number they wanted to in the $_GET and delete records?
Yes.
Is it better to never delete anything through the CRUD but to add a Deleted column to the table and make it appear as though it has been deleted to the user?
If you actually want to delete it, imo, deleting is better.
Am I just paranoid?
Not at all.
If you just had a delete.php script that had something like the following (ignoring CRUD atm and talking about MySQL):
$q = "DELETE FROM mytable WHERE row = $_GET['id']";
$r = mysql_query($q);
Then yes, your first two concerns are actually what would happen. A spider who stumbled upon these links would cause DB records to be deleted and a malicious user who discovered the links could similarly delete rows from the DB.
This is usually handled two ways:
Don't offer the delete.php link to people who shouldn't have it (people who are not logged in, people who are not logged in as admins, etc.) -- but note this is just a nicety and doesn't protect your DB from accidental / malicious deletion.
In the delete.php script, verify that the person running it is allowed to delete rows. Most of the time is done by checking session variables that were set upon login (e.g. if($_SESSION['can_delete'] != true){ die('Insufficient Privileges'); }).
Although it is helpful not to offer the delete.php link to people who shouldn't have it, you need to explicitly check for permissions in the delete.php script if you want it to be secure.
Read The Spider of Doom.
You can use a link to delete things, but the link should never be publicly exposed where it could be crawled, or even crawled by an internal indexing spider. Instead, you must check user authorization on every delete action to be sure the user making the deletion owns or is otherwise authorized to modify it.
So your delete() method must do more than just make the deletion (or the controller script which calls delete()). Always check for user authorization before performing any modification or deletion.
Whether you actually perform a deletion or simply mark rows as deleted depends on whether or not you need your application to be able to undelete them. Either way, check for authorization before acting.
Yes. This has happened to a fair bit of people, google crawls a "delete" page and remove their contents. I remember there being a really good blog post about it, but can't seem to find it.
Yes, this is entirely possible and very easy to do. That's why you need to build an authorization system. This means that before anything is performed, the system should check whether the user is allowed to do what he has requested, before performing the action.
Again, this dependings. If there is no use for the data anymore, then by all means delete it and get rid of it. But if you want to support some sort of "undeleting", then just mark the item as deleted.
No :)
One simple way would be to initiate a session like:
$somevar = 'abc123'
$_SESSION['secureDelete'][$id] = $somevar;
and then print this:
Delete
When you want to delete the file you check if that session exists with that $somevar value:
if (isset($_SESSION['secureDelete'][$id]) && $_SESSION['secureDelete'][$id] == $_GET['action']) {
deleteFunction($_GET['id']);
}
Try using POST method and hide the ID, it should be like this
<form action="" method="POST">
<input type="hidden" name="id" value="<?= $id; ?>"/>
<button type="submit">Delete</button>
</form>
Whats the best way to keep track of how many users and guests are online? Im making a forum for fun and learning
Right Now I have a 2 fields in the users table called is_online and last_access_time.
If current time is 5 minutes or more than last_access_time i set is_online it to zero. And if the signed in user refreshes browser i set it to 1.
But what about guests? I wanna keep track on how many guests are on also
Another thing that would be really cool is to show what page the user is viewing. and on the page, forum thread for example, 5 guests, Homer and Shomer are viewing this page. But how should i structure this? Hmm maybe i should make another question for that.
i dont know what i should do
What do you suggest?
I'd use cookies for this. Set a cookie when the user enters (checking first to make sure one doesnt exist). Easy way to generate a unique id for that user is to hash their IP plus the current time.
$id = md5($_SERVER['REMOTE_ADDR'] . time());
Store that id in your database and use that to reference
You can check what page they are viewing by grabbing either $_SERVER['PHP_SELF'] or $_SERVER['REQUEST_URI'] near the top of your php source. Store that in the table. I'd take a look at php.net's explanation of whats stored in the _SERVER global, as it should help out quite a bit if you find that you need more then just the document they are on (ex index.php). Found here.
You may need to pull apart of the query string that was used to access that page, parse out the variables to determine the page they are requesting. Either way, this could all be done through cookies, or just use a single cookie to store the unique id and use your table for storing everything else.
You cannot know for certain which page a user is viewing, but you can keep track of which page they last viewed. Every time you deliver a page to a user, record that page's path in a database row associated with them. Voila.
To keep the number of guests, I suggest tracking the number of distinct unauthenticated IP/HTTP-User-Agent combinations seen on a certain page in the last X minutes.
I found this article on Web Monkey that might help you.
http://www.webmonkey.com/2010/02/how_many_users_are_on_your_site_right_now/
I want to allow users as well as me(the admin) to delete data in mysql.
I used to have remove.php that would get $_GETs from whatever that needed to be deleted such as... remove.php?action=post&posting_id=2. But I learned that anyone can simply abuse it and delete all my data.
So what's the safest way for users and me to delete information without getting all crazy and hard? I am only a beginner :)
I'm not sure if I can use POSTs because there is no forms and the data isn't changing.
Is sessions good? Or would there be too many with postings, user information, comments, etc.
Ex: James wants to delete one of his postings(it is posting_id=5). So he clicks the remove link and that takes him to remove.php?action=post&posting_id=5.
EDIT: Alright, so now I am a little confused. While I can't be 100% secure, how do I do this with $_POSTs?
SOO I should use GETs to get all the data to remove.php, THEN have a confirmation submit button and when users click on it, it put all the data into POSTs and delete from the dbc?
Deleting records is a kind of a scary practice. If you or someone makes a mistake there's no real recourse to resolve the issue. Expunged records are very hard to resurrect.
Instead of deleting records, you could add an "active" bit (e.g. Boolean) column that is toggled off when users "delete" records. Essentially your users would be suspending records by toggling them off and the records would be saved in case mistakes or abuse but appear "deleted" to the user. To make this work with your other queries, just add a where clause of active = 1.
You could then have a utility script that's run at some specific date interval that would clean out deprecated, past dated records. You'd also need some type of timestamp for this type of maintenance.
Just a thought. Take if for what it's worth.
I'll echo gurun8 in preferring to 'mark' records as deleted, instead of actually removing data. And then obviously, you'll need to check that the authenticated user has permission to delete the post.
However, it seems very important to mention that $_GET is not safe even with authentication because of cross-site request forgery.
Imagine if Amazon adding things to your cart based on a GET request. All I'd have to do is put an image on my page with that URL, and everyone who visited that page and logged into Amazon will have products added automatically.
To match your example, I don't like Jame's post, so i put an image on my site like this:
<img src='http://example.com/remove.php?action=post&posting_id=5'>
And I send him a link to my page, and ask him to check it out, hoping that at the time he's logged in to your site. Because, of course, he clicked that little 'keep me logged in' button.
So you are right to be concerned about using GET. If you don't want to litter pages with forms, then confirm the action by POST.
Well you have to start by authenticating the users with a login script.
If you want the simplest solution possible, then I'd suggest protecting the directory in which you have remove.php with a simple .htaccess username and password.
If different users have different rights for deleting database entries, then you probably should create a PHP login script and use PHP session.
Bonk me if I'm stupid, but I searched for quite some time for a simple PHP login tutorial that could be placed on a real site (doesn't use session_register(), uses mysql_real_escape_string(), htmlspecialchars() etc) and I simply couldn't find one!
Probably this one comes the closest, you just have to replace session_register() variables with $_SESSION ones for it to work without register_globals (default in PHP5).
Hey guys, im building a small product catalog for a client. I am currently trying to figure out how to go about allowing the user to sort by "popularity". I figured the easiest/simplest way to do this would be by having a "views" field in each product record.
Originally I didn't think this through (duh) and thought I would just add one to the "views" count when the function to retrieve a specific product is called. Of course the problem with this is that it doesn't account for someone refreshing the page 30,000 times.
What is the best way to go about this? Does php have any sort of way to see if a visit is unique?
I am using php/codeigniter/mysql/html/css/javascript/jquery.
You could store if the client has already visited the page in a session variable, and run the same views query. Now this won't prevent a user from closing the browser, reopening, and returning, but it should provide a decent level of protection. If that's not enough you can move to cookies, followed by user registration, or IP tracking.
Okay, the session approach. On your page you run:
session_start();
if(!isset($_SESSION['page_views']['some_unique_string'])){
$_SESSION['page_views']['some_unique_string'] = true;
// update your database
}
Note that you'll want to make sure 'some_unique_string' is unique to the page you're tracking views on.
The approach is nearly identical for cookies.
If you want to make sure the page views are unique you have to store the remote IPs of all the page views. There's really no other reliable way to know if a visitor has visited or not. Placing a cookie on their system will fail at a later date for a million different reasons, they cleared their cookies, they used a different browser, etc. You need a views table with a foreign key back to whatever you're counting views for.
You can find all of this sort of code in my free rated images script here:
http://destiney.com/php
Here's a demo:
http://ratedsite.com/
Look at the addImageRating.php script.
Lets say I have a website with links to various books on my main page.
<a href='books.php?id=1'>Book 1</a>
<a href='books.php?id=2'>Book 2</a>
<a href='books.php?id=4'>Book 3</a>
Books 1-3 are in my system, however id=3 is apart of another catelog that I'm not showing or authorizing through this section of the site. So if a user clicked on Book 3, then changed the id=4 to id=3, they could simply pull up the record (assuming I don't have proper session checking).
Is there a good way to obscure the get id that you're passing when trying to pull a specific record? it seems by passing just the id would be easy to request pages that, without proper querying and session checking, you would be able to get another result.
Is there a better way to do this?
Cheers
You need to always, always, check that user is able to access the page. That is the only way to verify that you don't show wrong data, because someone can always modify the link they are going to, even if you somehow hide it.
There is just no escaping it. You always need to verify that the record can be accessed.
You probably could hash your id or something using md5 or whatever to make it harder to manually enter, but that's really not a good idea.
What you should do is to implement server side security in your books.php script that will prevent users from unauthorized access. That's the only thing that will keep your site secure.
Just check id if it is allowable to display or not.
With get's a good practice is when you check whatever parameters you may have.
just thinking out loud:
you can make a column in your book mysql table saying "visible" ... or userlevel in your users tables.
do a php function or if statement that:
if (user is not in user_level) // user is not allowed to see everything
{
redirect to previous page;
}
else if (user is in user_level) // user is allowed to see everything
{
display all items;
}
You definitely need to check whether the user is allowed to view the page.
But, what if you separated the ids for the different catalogs? URL's could look like books.php?cat=foo&id=1. That wouldn't necessarily make things any more secure, but it would prevent people from accidentally finding the wrong pages and organize things a little better.