php MySQL CRUD: Am I deleting safely? - php

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>

Related

Dealing with people modifying ID's in links

I'm writing a web app in PHP (using Codeigniter). I'm just wanting some security advice here. I have a task controller with a delete method. The link to delete the task is then http://localhost/task/delete/12345
So I'm wondering how to handle the fact that people have the ability to modify the link, thus deleting another task (probably not their own).
So my options I guess are to always do a check that the user owns that particular task before deleting it, or some sort of ID hashing?
Does anyone have any suggestions?
So my options I guess are to always do a check that the user owns that particular task before deleting it
that is the usual, and best, approach, yes. Hashing the ID is too insecure for many use cases: The link containing the hash is stored in the browser's history, might get E-Mailed around, be present in REFERER headers for outgoing links....
Either check ownership, or use a full-blown Access Control List, depending on how complex the permissions are.
Yes, check whether the user is allowed to delete that task and respond with an
HTTP/1.1 403 Forbidden if he isn't. Also, make destructive actions like deleting records POST requests. Otherwise watch Google (or some other fetcher-type client) happily triggering all your delete actions.
It is not recommended to update/delete your data via an http get request. Use post instead.
So my options I guess are to always do
a check that the user owns that
particular task before deleting it
Yup, that's it. There's no point in hashing an id, as if some user guesses another hash id he might delete a task from another user (that would be security through obscurity, and it's not a great security method). So, yes, chech that the task belongs to the user before deleting it.
Also, it isn't clear from your question, but to delete something, the user should go to blablah.com/tasks/delete/1234, and then confirm to delete the task via POST (submitting a form by clicking a button probably).
You can use session to store the item's id to delete and then on the delete page you check that the id given is equal to the session stored id.
Hope it helps you

What's the safest way to remove data from mysql? (PHP/Mysql)

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).

Fast, easy, and secure method to perform DB actions with GET

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.

PHP Security when Passing MySQL Unique ID via Get

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.

secure way to delete records with php

I have the following code snippet to delete records from a database given a primary key. This is called via an AJAX request, through GET. Anyone who were to examine my JavaScript could work out the URL and delete arbitrary records. What can I do to prevent this?
Not use GET?
Use sessions?
if($cmd=="deleterec") {
$deleteQuery = "DELETE FROM AUCTIONS1 WHERE ARTICLE_NO = ?";
if ($delRecord = $con->prepare($deleteQuery)) {
$delRecord->bind_param("s", $pk);
$delRecord->execute();
$delRecord->close();
echo "true";
} else {
echo "false";
}
}
Not using GET won't change a thing. You have to use sessions to manage 'access control' before you even get to this point in the code.
That is to say, when the AJAX request is made you should at that point confirm via the session that the person making the request is allowed to do the delete, before actually doing it.
Think about how you would check permissions in a 'non-AJAX' way and do the same here.
You can give your records a non-sequential random unique id to use in AJAX requests.
Still not the most secure solution although should be easy to integrate into your current system.
Use post rather than get
Embed hidden unique ids in any forms that will perform this action, and check that the id that comes back has been issued by you
Obfuscate the URL/parameter names
Generate unique IDs for the rows, and dont expose them externally, so an attacker cannot pick a row to delete.
Well, if not everyone can delete a record, make sure this one is behind a password, so only the authorized users can do deletes. Make yourself a login that adds a 'Delete' button on the page afterwards and on the PHP side make sure the user is authorized when processing the commands.
Oh and yes, make sure you always pass a confirmation value through POST to validate, not just a GET parameter, it's best practice, even with authorized users.
There are two important aspect to your question.
Are authorized user supposed to be able to delete any record they see fit? If that's the case your issue is one of making sure the request came from an authorized user. In that case using PHP's session is one way of making sure that it's the user you authenticated that made the request. Of course, with all technologies you have to make sure you use PHP session the right way. There are a bunch of articles on that mather. PHPsec.org has one here.
If a user cannot delete any row, in addition to doing authentication and sessions you'll also need to had a check in your code for the verify if the record being deleted can be deleted by that user. Now this is highly dependant on your application. For example, in a project management software, one could be allowed to delete tasks associated with one project but not with another one. That's part of your business logic.
The best would be to perform both check. One, your application must verify if the user making the deletion is a valid user and is allowed to delete (very simple to implement with php sessions) and two make sure that the user can delete the specific record based on some form of ownership.
Don't expose CRUD methods via Ajax.
You have to check before deleting records if user is authorized and allowed to delete.

Categories