This question already has an answer here:
How to prevent SQL Injection in Wordpress?
(1 answer)
Closed 6 years ago.
My website was recently got Hacked/Compromised. Via google I have learnt it is a victim of site injections. I believe I have cleaned and hopefully secured my website but I'm looking for ways to prevent it from ever happening again. I came across a code (see below) and wanted to know whether it will
1) work to prevent such attacks in the future? and
2) where should I add this code as my website is built in WordPress.
Any help or even better codes anyone can provide will be greatly appreciated, I'm new to programming.
Code:
<?php
if(isset($_REQUEST["id"])){
if(!is_int($_REQUEST["id"])){
//redirect this person back to homepage
} else {
$id_raw = trim(htmlentities($_REQUEST["id"]));
$id_secure = mysql_real_escape_string($id_raw);
$sql = "SELECT * FROM databasetable WHERE id='".$id_secure."'";
}
}
?>
PDO is an acronym for PHP Data Objects.
PDO is a lean, consistent way to access databases. This means developers can write portable code much easier. PDO is not an abstraction layer like PearDB. PDO is a more like a data access layer which uses a unified API (Application Programming Interface).
You basically have two options to achieve this:
Example:
$qry = $con->prepare('SELECT * FROM student WHERE name = :name');
$qry->execute(array('name' => $name));
foreach ($qry as $get) {
// do something with $get
}
Setting up database using PDO
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDBPDO";
A DSN is basically a string of options that tell PDO which driver to use, and the connection details... You can look up all the options here PDO MYSQL DSN.
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username,$password);
Note: If you get an error about character sets, make sure you add the charset parameter to the DSN. Adding the charset to the DSN is very important for security reasons, most examples you'll see around leave it out. MAKE SURE TO INCLUDE THE CHARSET!
You can also set some attributes after PDO construction with the setAttribute method:
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $conn->prepare("SELECT id, firstname, lastname FROM MyGuests");
$stmt->execute();
// set the resulting array to associative
$result = $stmt->setFetchMode(PDO::FETCH_ASSOC);
foreach(new TableRows(new RecursiveArrayIterator($stmt->fetchAll())) as $k=>$v) {
echo $v;
}
The way injection type attacks work, is by somehow getting an interpreter (The database) to evaluate something, that should have been data, as if it was code. This is only possible if you mix code and data in the same medium (Eg. when you construct a query as a string).Parameterised queries work by sending the code and the data separately, so it would never be possible to find a hole in that.
SQL Injection is a type of vulnerability in applications that use an SQL database. The vulnerability arises when a user input is used in a SQL Statement.
$n = $_GET['user'];
$sql = "SELECT password FROM tbl_login WHERE name = '$n' ";
As you can see the value the user enters into the URL variable user will get assigned to the variable $n and then placed directly into the SQL statement. This means that is possible for the user to edit the SQL statement.
$name = "admin' OR 1=1 -- ";
$query = "SELECT password FROM tbl_login WHERE name = '$n' ";
The SQL database will then receive the SQL statement as the following:
SELECT password FROM tbl_login WHERE name = 'admin' OR 1=1 -- '
To prevent SQL injections we will have to use something called prepared statements which uses bound parameters. Prepared Statements do not combine variables with SQL strings, so it is not possible for an attacker to modify the SQL statement. Prepared Statements combine the variable with the compiled SQL statement, this means that the SQL and the variables are sent separately and the variables are just interpreted as strings, not part of the SQL statement.
Prepared Statements with mySQLi.
Using the methods in the steps below, you will not need to use any other SQL injection filtering techniques such as mysql_real_escape_string(). This is because with prepared statements it is not possible to do conventional SQL injection.
mySQLi SELECT Query.
$n = $_GET['user'];
// Prepare the statement
if ($sql = $mysqli->prepare("SELECT password FROM tbl_login WHERE name=?")) {
// Bind a variable to the parameter as a string.
$sql->bind_param("s", $n);
// Execute the statement.
$sql->execute();
// Get the variables from the query.
$sql->bind_result($pass);
// Fetch the data.
$sql->fetch();
// Close the prepared statement.
$sql->close();
}
You will need to understand this:
Nothing is 100% secure.
All you can do is increase your level of security, by
implementing different security measures like filtering user input
before querying databases, using prepared statements.
Using a secure connection for server interaction by encrypting
the data using SHA or MD5 or some other salt encryption.
Using captcha in your forms to filter out bot attacks.
As far as your above code is concerned :
it is just checking whether the request id is an integer or not.
It is filtering out the special characters and then running the
query.
I would like to suggest you to check the below link :
https://www.owasp.org/index.php/PHP_Top_5
It will give you an insight of how to implement security in an application.
Related
I want to make a quick and easy demonstration about how SQL injection work. And I've solved some of my problems. I have a table with random usernames, passwords and emails in, and I'm able to "inject" SQL code to view all of the users in a search with this injection:
' OR '1'='1
This is how my PHP code looks for searching for "members":
if (isset($_POST['search'])) {
$searchterm = $_POST['searchterm'];
echo $searchterm . '<br>';
/* SQL query for searching in database */
$sql = "SELECT username, email FROM Members where username = '$searchterm'";
if ($stmt = $conn->prepare($sql)) {
/* Execute statement */
$stmt->execute();
/* Bind result variables */
$stmt->bind_result($name, $email);
/* Fetch values */
while ($stmt->fetch()) {
echo "Username: " . $name . " E-mail: " . $email . "<br>";
}
}
else {
die($conn->error);
}
}
Now I want to demonstrate some more fatal problems, like someone truncating your whole table. So I tried this code in the search bar:
'; TRUNCATE TABLE Members; --
But I get this error message:
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'TRUNCATE TABLE Members; -- '' at line 1
It seems like I get an extra ', but I don't know how to get rid of it, though the -- would comment that out. First I thought that the problem was that I had no whitespace behind the -- but adding a whitespace didn't make any difference.
I have tried switching to PDO, because I thought there was a problem with mysqli not accepting multiple queries, but then I somewhere read that PDO doesn't support that either, but I don't know.
Is there a way I can make it work?
I later found that PDO supports multi-querying by default, but when I tried it it didn't work. Maybe I bound the parameters wrong. But I couldn't even make a simple select query to work.
mysqli_query() does not support multi-query by default. It has a separate function for that: mysqli_multi_query().
SQL injection is not only about running multiple statements, the famous XKCD cartoon notwithstanding.
Your code has a bad SQL injection vulnerability. Do you think that using prepare() somehow makes a query safe, even though you interpolate content from your $_POST request data directly into the SQL string?
Your code is this:
$searchterm = $_POST['searchterm'];
$sql = "SELECT username, email FROM Members where username = '$searchterm'";
if ($stmt = $conn->prepare($sql)) {
/* execute statement */
$stmt->execute();
...
It's easy for unsafe input to make SQL injection mischief this way. It might even be innocent, but still result in problems. Suppose for example the search is: O'Reilly. Copying that value directly into your SQL would result in a query like this:
SELECT username, email FROM Members where username = 'O'Reilly'
See the mismatched ' quotes? This won't do anything malicious, but it'll just cause the query to fail, because unbalanced quotes create a syntax error.
Using prepare() doesn't fix accidental syntax errors, nor does it protect against copying malicious content that modifies the query syntax.
To protect against both accidental and malicious SQL injection, you should use bound parameters like this:
$searchterm = $_POST['searchterm'];
$sql = "SELECT username, email FROM Members where username = ?";
if ($stmt = $conn->prepare($sql)) {
$stmt->bind_param('s', $searchterm);
/* execute statement */
$stmt->execute();
...
Bound parameters are not copied into the SQL query. They are sent to the database server separately, and never combined with the query until after it has been parsed, and therefore it can't cause problems with the syntax.
As for your question about mysqli::query(), you may use that if your SQL query needs no bound parameters.
Re your comment:
... vulnerable to injection, so I can show the students how much harm a malicious attack may [do].
Here's an example:
A few years ago I was an SQL trainer, and during one of my trainings at a company I was talking about SQL injection. One of the attendees said, "ok, show me an SQL injection attack." He handed me his laptop. The browser was open to a login screen for his site (it was just his testing site, not the real production site). The login form was simple with just fields for username and password.
I had never seen his code that handles the login form, but I assumed the form was handled by some code like most insecure websites are:
$user = $_POST['user'];
$password = $_POST['password'];
$sql = "SELECT * FROM accounts WHERE user = '$user' AND password = '$password'";
// execute this query.
// if it returns more than zero rows, then the user and password
// entered into the form match an account's credentials, and the
// client should be logged in.
(This was my educated guess at his code, I had still not seen the code.)
It took me 5 seconds to think about the logic, and I typed a boolean expression into the login form for the username, and for the password, I typed random garbage characters.
I was then logged into his account — without knowing or even attempting to guess his password.
I won't give the exact boolean expression I used, but if you understand basic boolean operator precedence covered in any Discrete Math class, you should be able to figure it out.
Did you try something like this ?
'(here put something);
in this way you are going to close the query with ' and add other stuff to it, when you add ; everything else is going to be discarded
Any way to prevent malicious sql statements without using prepared statements and parameterized queries?
Example after simplify:
<?php
$con = mysqli_connect($_POST['db_server'], $_POST['db_user'],
$_POST['db_password'], $_POST['db_database']) or die(mysql_error());
$result = mysqli_query($con, $_POST['query_message']);
?>
Is it possible to check out the parameter $_POST['query_message'] is safe or not?
You should always build your queries within your code and then sanitise any variables you're going to use within them. NEVER pass the query or the database connection variables in via $_POST unless your user is querying the database via that form, in which case I'd recommend you just install phpMyAdmin.
As for sanitising your variables, if you really don't want to use PDO's prepared statements, you can sanitise incoming integers as follows:
$id = (isset($_POST['id']) ? (int)$_POST['id'] : null);
if ($id) {
$sql = "SELECT *
FROM `table`
WHERE `id` = {$id}";
}
And for strings use this:
$username = (isset($_POST['username']) ? mysqli_real_escape_string($con, $_POST['username']) : null);
if ($username) {
$sql = "SELECT *
FROM `table`
WHERE `username` = {$username}";
}
You can also call real_escape_string() directly on your $con object as follows:
$username = (isset($_POST['username']) ? $con->real_escape_string($con, $_POST['username']) : null);
However, as with #Shankar-Damodaran above, I highly suggest you do use PDO prepared statements to query your database.
Why you don't wanna use Prepared Statements ? That is really weird. I strongly suggest you should go for it.
You could make use of mysqli::real_escape_string for escaping quotes that is commonly used for SQL Injection Attacks.
Something like...
OOP Style
$message = $mysqli->real_escape_string($_POST['query_message']);
Procedural Style
$message = mysqli_real_escape_string($link,$_POST['query_message']);
other way is using:
htmlentities($query);
as an extra you could use preg_match() regular expressions to avoid
the inclusion of certain words (SELECT, DROP, UNION .......)
Example:
try{
$query = sprintf("SELECT * FROM users WHERE id=%d", mysqli_real_escape_string($id));
$query = htmlentities($query);
mysqli_query($query);
}catch(Exception $e){
echo('Sorry, this is an exceptional case');
}
There are real world cases where prepared statements are not an option.
For a simple example, a web page page where you can do a search on any number of any columns in the database table. SAy that table has 20 searchable columns. you would need a huge case statement that has all 20 single column queries, all 19+18+17+16+15+14+13+... 2 column queries, all possible 3 column queries... that's a LOT of code. much less to dynamically construct the where clause. That's what the OP means by prepared statements being less flexible.
Simply put, there is no generic case. If there was, php would have it already.
real_escape_string can be beaten. a common trick is to % code the character you are trying to escape so real_escape_string doesn't see it. then it gets passed to mysql, and decoded there. So additional sanitizing is still required. and when all characters used in injection are valid data, it's a PITA, because you can't trust real_escape_string to do it.
If you are expecting an integer, it's super easy.
$sanitized=(int)$unsanitized;
done.
If you are expecting a small text string, simply truncating the string will do the trick. does't matter that it's not sanitized if there's not enough room to hold your exploit
But there is no one size fits all generic function that can sanitize arbitrary data against sql injection yet. If you write one, expect it to get put into php. :)
I saw several examples and people using this way to query the database in a login form.
I'm not fully sure is this is the best way to do a login form secure.
This is the query in PHP:
$query = "SELECT * FROM users WHERE usern = '".$_POST['username']."' AND passw = '".md5($_POST['password'])."'";
Is enough having md5() on the password post to avoid sql injection?.
I think that the md5 function will convert all characters and sql strings to a 32 char string.
Which other ways can I protect the login form?
mysql_real_escape_string($_POST['username']), etc.
Although it's better to use the mysqli extension and use prepared statements.
(Assuming you're using MySQL)
Edit: In response to the comment below, it might be good to use this for LIKE queries:
addcslashes(mysql_real_escape_string($_POST['username']), '%_')
You must sanitize your data before you let it near your database. The simplest way to do this is by using mysql_real_escape_string($_POST['username']) but this is only the very least you need to do.
If you're using a framework like CodeIgniter, you can use their in-build functionality which strips $_POST or $_GET inputs of any XSS risk. Otherwise, I'd recommend these posts:
What's the best method for sanitizing user input with PHP?
Clean & Safe string in PHP
You need to escape $_POST['username'] as well
and yes md5 will protect you from sql injection.
For this example, something like this would be ok
$query = "SELECT * FROM users WHERE MD5(usern) = '".md5($_POST['username'])."' AND passw = '".md5($_POST['password'])."'";
The way you have build your query easily allows to inject pieces of code in the username. You can use prepared statements to avoid that:
http://php.net/manual/en/pdo.prepared-statements.php
Prepared statements basically will describe how the statement will be structured, and adds the data afterwards. This way, the user can not alter the structure of the statement with the input data.
If you make a function which sanitizes all POSTS and GETS you are safe
function clean() {
foreach($_POST as $key => $val) {
$_POST[$key] = mysql_real_escape_string($val);
}
}
You can also use PDO and statements with variables, and PDO will clean automatically.
<?php
try {
$dbh = new PDO("mysql:host=$hostname;dbname=$db", $dbusername, $dbpassword);
$query = "SELECT * FROM users WHERE usern = ? AND passw = ?";
$sth=$dbh->prepare($sql);
$sth->execute(array($_POST['username'], md5($_POST['password']));
$result = $sth->fetch();
}
} catch(PDOException $e) {
echo $e->getMessage();
exit;
}
PDO is the Best way to stop SQL Injection in PHP
Just one simple solution use parameters for fields like username and password so that SQL command string is separably sent from the parameters and then attacker will only get blank responses.When parameters are used SQL command string is parsed and compiled separately from the parameters. Using prepared statements is the best solution.
I've always done the simple connection of mysql_connect, mysql_pconnect:
$db = mysql_pconnect('*host*', '*user*', '*pass*');
if (!$db) {
echo("<strong>Error:</strong> Could not connect to the database!");
exit;
}
mysql_select_db('*database*');
While using this I've always used the simple method to escape any data before making a query, whether that be INSERT, SELECT, UPDATE or DELETE by using mysql_real_escape_string
$name = $_POST['name'];
$name = mysql_real_escape_string($name);
$sql = mysql_query("SELECT * FROM `users` WHERE (`name` = '$name')") or die(mysql_error());
Now I understand this is safe, to an extent!
It escapes dangerous characters; however, it is still vulnerable to other attacks which can contain safe characters but may be harmful to either displaying data or in some cases, modifying or deleting data maliciously.
So, I searched a little bit and found out about PDO, MySQLi and prepared statements. Yes, I may be late to the game but I've read many, many tutorials (tizag, W3C, blogs, Google searches) out there and not a single one has mentioned these. It seems very strange as to why, as just escaping user input really isn't secure and not good practice to say the least. Yes, I'm aware you could use Regex to tackle it, but still, I'm pretty sure that's not enough?
It is to my understanding that using PDO/prepared statements is a much safer way to store and retrieve data from a database when the variables are given by user input. The only trouble is, the switch over (especially after being very stuck in my ways/habits of previous coding) is a little difficult.
Right now I understand that to connect to my database using PDO I would use
$hostname = '*host*';
$username = '*user*';
$password = '*pass*';
$database = '*database*'
$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);
if ($dbh) {
echo 'Connected to database';
} else {
echo 'Could not connect to database';
}
Now, function names are different so no longer will my mysql_query, mysql_fetch_array, mysql_num_rows etc work. So I'm having to read/remember a load of new ones, but this is where I'm getting confused.
If I wanted to insert data from say a sign up/registration form, how would I go about doing this, but mainly how would I go about it securely? I assume this is where prepared statements come in, but by using them does this eliminate the need to use something like mysql_real_escape_string? I know that mysql_real_escape_string requires you to be connected to a database via mysql_connect/mysql_pconnect so now we aren't using either won't this function just produce an error?
I've seen different ways to approach the PDO method too, for example, I've seen :variable and ? as what I think are known as place holders (sorry if that is wrong).
But I think this is roughly the idea of what should be done to fetch a user from a database
$user_id = $_GET['id']; // For example from a URL query string
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
But then I'm stuck on a couple things, if the variable wasn't a number and was a string of text, you have to given a length after PDO:PARAM_STR if I'm not mistaken. But how can you give a set length if you're not sure on the value given from user in-putted data, it can vary each time? Either way, as far as I know to display the data you then do
$stmt->execute();
$result = $stmt->fetchAll();
// Either
foreach($result as $row) {
echo $row['user_id'].'<br />';
echo $row['user_name'].'<br />';
echo $row['user_email'];
}
// Or
foreach($result as $row) {
$user_id = $row['user_id'];
$user_name = $row['user_name'];
$user_email = $row['user_email'];
}
echo("".$user_id."<br />".$user_name."<br />".$user_email."");
Now, is this all safe?
If I am right, would inserting data be the same for example:
$username = $_POST['username'];
$email = $_POST['email'];
$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
VALUES (:username, :email)");
$stmt->bindParam(':username, $username, PDO::PARAM_STR, ?_LENGTH_?);
$stmt->bindParam(':email, $email, PDO::PARAM_STR, ?_LENGTH_?);
$stmt->execute();
Would that work, and is that safe too? If it is right what value would I put in for the ?_LENGTH_?? Have I got this all completely wrong?
UPDATE
The replies I've had so far have been extremely helpful, can't thank you guys enough! Everyone has got a +1 for opening my eyes up to something a little different. It's difficult to choose the top answer, but I think Col. Shrapnel deserves it as everything is pretty much covered, even going into other arrays with custom libraries which I wasn't aware of!
But thanks to all of you:)
Thanks for the interesting question. Here you go:
It escapes dangerous characters,
Your concept is utterly wrong.
In fact "dangerous characters" is a myth, there are none.
And mysql_real_escape_string escaping but merely a string delimiters. From this definition you can conclude it's limitations - it works only for strings.
however, it is still vulnerable to other attacks which can contain safe characters but may be harmful to either displaying data or in some cases, modifying or deleting data maliciously.
You're mixing here everything.
Speaking of database,
for the strings it is NOT vulnerable. As long as your strings being quoted and escaped, they cannot "modify or delete data maliciously".*
for the other data typedata - yes, it's useless. But not because it is somewhat "unsafe" but just because of improper use.
As for the displaying data, I suppose it is offtopic in the PDO related question, as PDO has nothing to do with displaying data either.
escaping user input
^^^ Another delusion to be noted!
a user input has absolutely nothing to do with escaping. As you can learn from the former definition, you have to escape strings, not whatever "user input". So, again:
you have escape strings, no matter of their source
it is useless to escape other types of data, no matter of the source.
Got the point?
Now, I hope you understand the limitations of escaping as well as the "dangerous characters" misconception.
It is to my understanding that using PDO/prepared statements is a much safer
Not really.
In fact, there are four different query parts which we can add to it dynamically:
a string
a number
an identifier
a syntax keyword.
so, you can see that escaping covers only one issue. (but of course, if you treat numbers as strings (putting them in quotes), when applicable, you can make them safe as well)
while prepared statements cover - ugh - whole 2 isues! A big deal ;-)
For the other 2 issues see my earlier answer, In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?
Now, function names are different so no longer will my mysql_query, mysql_fetch_array, mysql_num_rows etc work.
That is another, grave delusion of PHP users, a natural disaster, a catastrophe:
Even when utilizing old mysql driver, one should never use bare API functions in their code! One have to put them in some library function for the everyday usage! (Not as a some magic rite but just to make the code shorter, less repetitive, error-proof, more consistent and readable).
The same goes for the PDO as well!
Now on with your question again.
but by using them does this eliminate the need to use something like mysql_real_escape_string?
YES.
But I think this is roughly the idea of what should be done to fetch a user from a database
Not to fetch, but to add a whatever data to the query!
you have to given a length after PDO:PARAM_STR if I'm not mistaken
You can, but you don't have to.
Now, is this all safe?
In terms of database safety there are just no weak spots in this code. Nothing to secure here.
for the displaying security - just search this site for the XSS keyword.
Hope I shed some light on the matter.
BTW, for the long inserts you can make some use of the function I wrote someday, Insert/update helper function using PDO
However, I am not using prepared statements at the moment, as I prefer my home-brewed placeholders over them, utilizing a library I mentioned above. So, to counter the code posted by the riha below, it would be as short as these 2 lines:
$sql = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i';
$data = $db->getRow($sql,$_GET['name'],'admin',1);
But of course you can have the same code using prepared statements as well.
* (yes I am aware of the Schiflett's scaring tales)
I never bother with bindParam() or param types or lengths.
I just pass an array of parameter values to execute(), like this:
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->execute( array(':user_id' => $user_id) );
$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
VALUES (:username, :email)");
$stmt->execute( array(':username'=>$username, ':email'=>$email) );
This is just as effective, and easier to code.
You may also be interested in my presentation SQL Injection Myths and Fallacies, or my book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.
Yes, :something is a named placeholder in PDO, ? is an anonymous placeholder. They allow you to either bind values one by one or all at once.
So, basically that makes four options to provide your query with values.
One by one with bindValue()
This binds a concrete value to your placeholder as soon as you call it. You may even bind hard coded strings like bindValue(':something', 'foo') if desired.
Providing a parameter type is optional (but suggested). However, since the default is PDO::PARAM_STR, you only need to specify it when it is not a string. Also, PDO will take care of the length here - there is no length parameter.
$sql = '
SELECT *
FROM `users`
WHERE
`name` LIKE :name
AND `type` = :type
AND `active` = :active
';
$stm = $db->prepare($sql);
$stm->bindValue(':name', $_GET['name']); // PDO::PARAM_STR is the default and can be omitted.
$stm->bindValue(':type', 'admin'); // This is not possible with bindParam().
$stm->bindValue(':active', 1, PDO::PARAM_INT);
$stm->execute();
...
I usually prefer this approach. I find it the cleanest and most flexible.
One by one with bindParam()
A variable is bound to your placeholder that will be read when the query is executed, NOT when bindParam() is called. That may or may not be what you want. It comes in handy when you want to repeatedly execute your query with different values.
$sql = 'SELECT * FROM `users` WHERE `id` = :id';
$stm = $db->prepare($sql);
$id = 0;
$stm->bindParam(':id', $id, PDO::PARAM_INT);
$userids = array(2, 7, 8, 9, 10);
foreach ($userids as $userid) {
$id = $userid;
$stm->execute();
...
}
You only prepare and bind once which safes CPU cycles. :)
All at once with named placeholders
You just drop in an array to execute(). Each key is a named placeholder in your query (see Bill Karwins answer). The order of the array is not important.
On a side note: With this approach you cannot provide PDO with data type hints (PDO::PARAM_INT etc.). AFAIK, PDO tries to guess.
All at once with anonymous placeholders
You also drop in an array to execute(), but it is numerically indexed (has no string keys). The values will replace your anonymous placeholders one by one in the order they appear in your query/array - first array value replaces first placeholder and so forth. See erm410's answer.
As with the array and named placeholders, you cannot provide data type hints.
What they have in common
All of those require you to bind/provide as much values as you have
placeholders. If you bind too many/few, PDO will eat your children.
You don't have to take care about escaping, PDO handles that. Prepared PDO statements are SQL injection safe by design. However, that's not true for exec() and query() - you should generally only use those two for hardcoded queries.
Also be aware that PDO throws exceptions. Those could reveal potentially sensitive information to the user. You should at least put your initial PDO setup in a try/catch block!
If you don't want it to throw Exceptions later on, you can set the error mode to warning.
try {
$db = new PDO(...);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING)
} catch (PDOException $e) {
echo 'Oops, something went wrong with the database connection.';
}
To answer the length question, specifying it is optional unless the param you are binding is an OUT parameter from a stored procedure, so in most cases you can safely omit it.
As far as safety goes, escaping is done behind the scenes when you bind the parameters. This is possible because you had to create a database connection when you created the object. You are also protected from SQL injection attacks since by preparing the statement, you are telling your database the format of the statement before user input can get anywhere near to it. An example:
$id = '1; MALICIOUS second STATEMENT';
mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1
and the executes the
malicious second statement */
$stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a
single statement with
a single parameter */
$stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second
STATEMENT' i.e. returns empty set. */
Thus, in terms of safety, your examples above seem fine.
Finally, I agree that binding parameters individually is tedious and is just as effectively done with an array passed to PDOStatement->execute() (see http://www.php.net/manual/en/pdostatement.execute.php).
<?php
$id = intval($_GET['id']);
$sql = mysql_query("SELECT username FROM users WHERE id = $id");
$row = mysql_fetch_assoc($sql);
$user = htmlspecialchars($row['username']);
?>
<h1>User:<?php echo $user ?></h1>
Can you see any threats in the above code? Do I have to use htmlspecialchars on everything I output? And should i use is_numeric or intval to check so that the get is numeric?
I'm just building a minimal site. I'm just wondering if the above code is vulnerable to sql injection, xss?
Generally speaking mysql_real_escape_string() is preferred but since it's a number, intval() is OK. So yes, it looks OK from a security perspective.
One thing though, on many platforms, ints are limited to 32 bits so if you want to deal in numbers larger than ~2.1 billion then it won't work. Well, it won't work how you expect anyway.
These sorts of security precautions apply to any form of user input including cookies (something many people forget).
I would strongly recommend using PDO and prepared statements. While your statement above looks safe, you're going to have problems as soon as you do more complex queries.
Instead of puzzling over whether a particular query is safe, learn about prepared statements and you won't have to worry. Here is your example, re-written with PDO:
# Make a database connection
$db = new PDO('mysql:dbname=your_db;host=your_db_server', 'username',
'password');
# The placeholder (:id) will be replaced with the actual value
$sql = 'SELECT username FROM users WHERE id=:id';
# Prepare the statement
$stmt = $db->prepare($sql);
# Now replace the placeholder (:id) with the actual value. This
# is called "binding" the value. Note that you don't have to
# convert it or escape it when you do it this way.
$stmt->bindValue(':id', $id);
# Run the query
$stmt->execute();
# Get the results
$row = $stmt->fetch();
# Clean up
$stmt->closeCursor();
# Do your stuff
$user = htmlspecialchars($row['username']);
I've added a lot of comments; it's not as much code as it looks like. When you use bindValue, you never have to worry about SQL injection.
Well,
You are casting the received id to an int ; so no possible SQL injection here.
And the rest of the DB query is "hard-coded", so no problem there either.
If id was a string in DB, you'd have to use mysql_real_escape_string, but for an integer, intval is the right tool :-)
About the output, you are escaping data too (and, as you are outputting HTML, htmlspecialchars is OK) ; so no HTML/JS injection.
So, this short portion of code looks OK to me :-)
As a sidenote, if you are starting developping a new website, it is the moment or never to take a look at either mysqli (instead of mysql), and/or PDO ;-)
It would allow you to use functionnalities provided by recent versions of MySQL, like prepared statements, for instance -- which are a good way to protect yourself from SQL injection !