I would like to be nice to my users but don't want to get malicious code by mistake.
Looking in SO many questions I decided to go whitelisting, so I get only characters I approve.
How is it possible to be kind to the user, so I let them enter many characters and still keep my inputs safe from malicious code?
I am using Apache with PHP 5.3.8, I haven't incorporated PDO because the time I heard of it it I was deep into the project and feared it might be too big of a change.
Any ideas would help!
If it can be output to a user, you must prevent the potentially malicious user from including HTML tags in his code. For example, if, in that post, I could include a script tag, that could be very dangerous for any user reading my post. To prevent this, you want to use htmlentities:
$clean_data = htmlentities($_POST['data']);
That way, my <script> tag will be translated as <script> so it won't harm users when displayed by their browsers.
Now, if you want to save my post in your database, you must beware of SQL injections. Let's say you're saving my post with that query (yes, you shouldn't use mysql_* functions as they are deprecated, but that's just to explain the idea) :
mysql_query($db, "INSERT INTO posts(data) values('$clean_data');");
Sounds good ? Well, if I'm nasty, I'll try to include that post :
test'); DELETE FROM posts; SELECT * FROM posts WHERE data = '
What your MySQL gets is then
INSERT INTO posts(data) values('test'); DELETE FROM posts; SELECT * FROM posts WHERE data = '');
Ouch. So, you must basically prevent your user from including quotes and double quotes in his post, or, more precisely, you should escape them. That really depends on the library you are using, but in the obsolete one I used, that would be written :
$really_clean_data = mysql_real_escape_string($db, $clean_data);
mysql_query($db, "INSERT INTO posts(data) values('$really_clean_data');");
So, with the above malicious post, MySQL would now receive
INSERT INTO posts(data) values('test\'); DELETE FROM posts; SELECT * FROM posts WHERE data = \'');
Now, to MySQL, the whole INSERT INTO posts(data) values('test'); DELETE FROM posts; SELECT * FROM posts WHERE data = ''); part is a correct string, so what happens is what you want to happen.
Basically, that's all you need in almost all the cases. Just remember that, when you feed user data to an interpreter (it can be a web browser, a SQL engine or many other things), you should clean that data, and the best way to do it is to use the dedicated functions that should come with the library you are using.
Just to add to the answer that Fabien already gave, text data types are not the only item you need to be concerned with. Numbers are just as important. Type casting your variables is one way to handle that. For example,
$really_clean_data = mysql_real_escape_string($db, $clean_data);
$query = "UPDATE posts SET post = '".$really_clean_data."' WHERE id = '".(int)$_POST['id']."'";
All data submitted, including the id number of the post you'll be looking to update, is just as suspect and just as open to malicious fiddling as a textarea or text input entry. When you know it should be an integer (number) you can cast it as such. If it's not numeric, that (int) will cast it as 0. You can't update row 0 so the SQL query will fail.
To check for that failure prior to executing the query, you can check the posted data to see if it is a number using the is_numeric() function.
if(!is_numeric($_POST['id'])){
die("Post id is not a number.");
}
For input fields, most notably username and password fields, you can set a max length and check. For example,
if(strlen($_POST['username']) > 24){
$error = "username is too long";
}
If you can get creative about it, preventing SQl injection is actually fun! Keep in mind that a year from now everything on this page could be completely outdated and irrelevant. Security is a process, not a step, so keep on top of it.
Related
I have discovered that some old (2009) code that was written for a website, did, under certain circumstances on a search query save the SQL as a a $_GET variable!
When the search was carried out, the details are POSTED and then sanitized, and the results are paginated with the LIMIT clause in MySQL. If there is more than one page (ie +30 results) the pages are anchor links in the HTML with a GET var containing the SQL statement.
I know, this is absolutely not the way to do this. It's old code I've just seen it by chance. This needs to be fixed.
So I've fixed it, sanitized it and used an alternative method to reload the SQL, BUT:
My question is thus:
The page outputs the data relating to thumbnail images, all data is output as named array var (the original clause is a SELECT * clause), so if someone does abuse the GET variable, the page itself will only output the columns named,
I have managed to DELETE rows from the DB using the GET abuse, I would like to think the abuse is only effective if the result is not involving any returned output (such as DELETE) but I don't know; so given that the user can input anything into the GET clause but only get the displayed output of what's coded (ie named columns in a 30 row array) -- what other abuses can this gaping hole be open to?
Further details: The code is MySQLi
A tool like SQLMAP can probably take over the entire server and do with it whatever the user wants.
Having an unsanitized database input isn´t even hacking anymore, it´s waiting for someone to run a script on your machine and basically own it from that point on.
What the attacker can do depends on your database configuration and database user access. If you create a new user with a permission to only SELECT that one specified table, and use that user for that particular script, the harm it can do is reading data from that table.
Still this is bad practice. Never use it.
I've just hit a weird display issue.
I have an ecommerce site which allows the user to add free text in a description field. This value is stored in an mysql database and later read out to the screen in a description.
During some testing I've found that if the user puts 'select * from table' it is stored and returned correctly but the html is turning it into
<span class="idesc break-word">TESTING TESTING %$#$!OIUOQI#UQO#)( <select *="" from="" table=""></select></span>
which in turn appears as an empty select drop down box.
I already use ? bind variables in the php to mysql code so firstly:
Is there something else I should be doing to protect against dodgy data entry?
Is this indicative that I could be open to other such attacks?
If values of the field that was created in that manner are being passed further down he application and can end up sent to database than yes you are open to SQL injection attacks.
Is there some code that is looking for "select" and decides that a field should be created from that? If yes, that looks somewhat dangerous and there could be more areas where this ecommerce app makes potentially dangerous decisions.
Binding is a good step in preventing unexpected data types from being entered, but going a step further like limiting some characters e.g. ;<> is also a good way. What worries me with your example is that you were not expecting a select field, but got one; that means that once broken that mechanism could be further exploited to interact with your database.
I'll jump straight into it. Using php I'm having a problem deleting a record from the database if i use a variable as the value. The line of code below works perfectly
mysqli_query($con,"DELETE FROM highScores WHERE Name='David'");
But the name of the user will change, therefore I need to declare it as a variable. I've tried all kinds of variations but nothing seems to work. My latest failed attempt was the code below, which is the way i declare a varible when i'm inserting.
mysqli_query($con,"DELETE FROM highScores WHERE Name='{$name}'");
In situations like this it is good to check that variables actually contain something you expect it to. And I find also that echoing entire query strings is a good way to find out why a query isn''t working.
$sqlquery = "DELETE FROM highScores WHERE Name='{$name}'";
// have a look at the query...
echo "<pre>$sqlquery</pre>";
// use it...
mysqli_query($conn,$sqlquery);
I should warn you that if $name comes from somewhere untrusted, such as a publicly viewable html form, then it needs to be made 'safe' before using it in a query. Look into 'prepared statements'. Once you know your code is correctly populating your variable, make sure it is made safe before putting it in your query.
I'm not sure if..
{$variable} is valid in a query.
The way I insert a variable into a query, is called concentration.
mysqli_query($con,"DELETE FROM highScores WHERE Name='" . $name . "'");
The period adds 2 strings together.
However, what you're trying to do is vulnerable to SQL injection. If I were you, I'd be careful on what could be inside $name.
EDIT: My mistake, I'm used to a class that inserts the quotes for me.
Try this to get it running:
mysqli_query($con,"DELETE FROM highScores WHERE Name='".$name."'");
Make sure $name is a proper formed string like string(5) David, otherwise it might not lead to the desired results or may even break your query completely. You can make sure of this if you put a mysqli_real_escape_string like this
$name = mysqli_real_escape_string($con,$name);
before you execute the query
I landed here while searching for solutions to the same problem, but just discovered my database user didn't have delete privileges. I had earlier removed this privilege for security reasons.
i always like to use other sanitazing methods (and prepared statments) but i am under certain conditions where the client doesn`t want me to change a single line from his original script (im only going to add a plugin)
im worried someone could exploit this by modifying the $POST values and he could blame me (like clients usually do):
extract($_POST, EXTR_PREFIX_ALL, "POST");
$q = sprintf(ARQ, trim($POST_string));
$res = doexec($q);
DEFINE("ARQ", "INSERT INTO library (string) VALUES ('%s')");
i tried several combinations but all i could get was an error, no mysql inject
so, is this secure enough, as is?
I suppose doexec calls something that does not accept multiple commands chained with ;? You could still use a subquery to insert passwords and other sensitive data from other tables (assuming the user can read back the data he saved - otherwise you can use a timing attack). If the filesystem security is as poor as that of the database access code, you can use SELECT INTO to create a php script you can execute.
I am using the get method to perform some operation like, approve, markasspam, delete, for commenting system. i know it is highly insecure to go this way but i cannot help it out. because the reason for using $_GET method is to perform the operation within the page itself using PHP_SELF, and FYI i am using the post method using checkbox to perform the operation too.
now for making it bit secure i want to randomize the number or generate the hash or something and then compare it, get the id and perform the operation
my current code is somewhat like this.
<?php
if($approve == 1 )
{
?>
Unapprove
<?php
} else
{
?>
Approve
<?php
}
?>
| Spam
| <a class="edit-comments" href="edit-comments.php?id=<?php echo $id; ?>">Edit</a>
| Delete
and i perform the operation using this code..
if(isset($_GET['approve'])) {
$id = intval($_GET['approve']);
$query = "UPDATE comments SET approve = '0' WHERE id = '$id'";
$result = mysql_query($query);
}
if(isset($_GET['unapprove'])) {
$id = intval($_GET['unapprove']);
$query = "UPDATE comments SET approve = '1' WHERE id = '$id'";
$result = mysql_query($query);
}
if(isset($_GET['delete'])) {
$id = intval($_GET['delete']);
$query = "DELETE FROM comments WHERE id = '$id'";
$result = mysql_query($query);
}
if(isset($_GET['spam'])) {
$id = intval($_GET['spam']);
$query = "UPDATE comments SET spam = '1' WHERE id = '$id'";
$result = mysql_query($query);
}
instead of using approve or unapprove or delete or spam, i want to randomize or hash that words and want it as lengthy as possible and then perform the operation.
how do i do it? what is your take on this?
EDIT: Please Note Only the
Authenticated User i.e Admin will be
able to perform this operation. even
though it pass through authentication
system i want to add more security
even for admin. to avoid experiments
or accident
the code is not exact it is just the sample to make you understand what i want to achieve.
Whether you use GET or POST parameters here doesn't matter much in this context - what the script needs first is some sort of authentication. (After that is done, you can go into security details where GET is slightly less secure than POST - see the comments for details.)
I'd say you have two options:
Protecting the entire script using .htaccess - no changes needed to the script itself
Introducing PHP side user authentication and perform the operations only if a logged in user makes the request. Needs fundamental changes to the script but is most flexible.
Re your edit:
It turns out your script is already protected. In that case I assume you are uncomfortable with incremental ID numbers turning up in the URLs, getting cached in the browser etc. etc. The usual solution to that is to generate a random key for each comment when it is created (in addition to the incremental ID). That key gets stored in a separate column (don't forget to add an index) and you'd match against that.
A step even further would be to create temporary hashes for every action, which is the ultimate protection against a number of outside attacks.
Re your edit about using one-time hashes:
I've never implemented one-time hashes in an admin interface yet so I have no experience with this, but I imagine that a very simple implementation would store action hashes in a separate table with the columns hash, record and action. Whenever your tool lists a number of records and outputs "delete / approve / unapprove" links, it would generate three record in the hash table for each comment: One for delete, one for approve, one for unapprove. The "delete / approve /unapprove" links would then, instead of the record ID and command, get the correct hash as the only parameter.
Add a time-out function for unused hashes (plus delete any hashes that were actually used) and you're done.
You can do it that way, the $_GET is not the unsecure thing in your code. The unsecurity comes from you not checking wether the user is e.g. authorized to delete comments.
In your current code, anyone can delete anything at anytime and as often as they want.
If you have a wrapping code that ensures the if-statements postet by you are not executed if enter good reason here, then it's okay.
But you should try verifying, that the content of the parameters are really integers instead of just int_val'ing them and using them directly on the database.
On your edit
You should check your parameter is really an int. intval("test") will also return an integer, mostly 0.
You might consider regex for that, to verify the string only consists of numbers: preg_match('/[0-9]+/', $_GET['id']);
If so, you can perform the action.
You shouldn't use GET for any operations that change data on server. NEVER. You use it only to get data.
If you can't use forms for operation buttons (because there is another form outside them) you should consider this design:
You use AJAX to perform POST requests to your server
In javascript-disabled environments you use GET links like user.php?action=delete, which shows you very simple form on a separate page. The header in the form asks: "Are you sure you want to delete user X?" and it has two buttons: 1) "Yes" - that submits POST request to operation script, 2) "No" - which sends user back to the page where he has been