Is there a way to demonstrate SQL injection with mysqli? - php

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

Related

Problem with SQL query with variable that calls by php

I wrote a SQL query for checking name in php, but it does not work.
I have no assumptions how to fix it, but I assume it's just mistake in syntax.
$username = $_POST["username"];
$nameCheckQuery = "SELECT username FROM users WHERE username '" . $username . "';";
$nameCheck = mysqli_query($db, $nameCheckQuery) or die("2: Name check query failed");
I receive error log on query.
The reason it's failing is likely due to you missing a = after username.
This code is open to SQL injection and you should use prepared statements.
The most basic of a prepared statement looks something like this:
$stmt = $db->prepare("SELECT * FROM users WHERE username = ?");
$username = $_POST['username'];
$stmt->bind_param('s', $username);
$result = $stmt->execute();
The main problem of your query is that you forget to insert = next to WHERE username.
You have to write:
$nameCheckQuery = "SELECT username FROM users WHERE username ='" . $username . "';";
Right now it works but......
The query you are using is not preventing a SQL INJECTION attack (one of the most used attack against database).
Please take a look at the ways you can connect to the database:
use PDO (it works with 12 database type);
use MSQLI (it works only with MYSQL database and you are using it);
In other word, if you are planning that you will move your application in another database type please consider to use PDO, instead.
Using PDO preventing SQL injection you have to prepare the SQL statement like this:
$stmt = $pdo->prepare("SELECT username FROM users WHERE username = ?");
$stmt->execute([$_POST['username']]);
$arr = $stmt->fetch();
For Starter, please use this escape string:
$username = $mysqli->real_escape_string($_POST["username"]);
Simply do it like this and don't get confused with quotes.
You can still print php variables inside single quote like this.
$nameCheckQuery = "SELECT username FROM users WHERE username = '$username'";
or to edit your code, this is how you can achieve it.
$nameCheckQuery = "SELECT username FROM users WHERE username ='" . $username."'";
Just to answer your question, it is Vulnerable to Sql Injection.
Reasons why Sql Injection occurs:
SQL Injection occurs when an attacker is able to send their own instructions to your database and the database executes those instructions. This occurs when a PHP developer has taken input from a website visitor and passed it to the database without checking to see if it contains anything malicious or bothering to clean out any malicious code.
SQL Injection can allow an attacker to access all of your website data. They can also create new data in your database which may include links to malicious or spam websites. An attacker may also be able to use SQL Injection to create a new administrative level user account which they can then use to sign-into your website and gain full access.
SQLi is a serious vulnerability because it is easy to exploit and often grants full access immediately.
This is how you can achieve it, which provides detailed functionality.
https://stackoverflow.com/a/60496/6662773

Prevent URL Injections [duplicate]

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.

Why is INSERT INTO followed by SELECT LAST_INSERT_ID() not outputting anything?

The PHP code I have inserts the HTML form data from the previous page into the database and in the same SQL statement return the PostID back from the inserted data. The PostID column is AUTO_INCREMENTING. I have been researching this problem for a week or two now and have found no significant solutions.
<?php
include("dbconnect.php");
mysql_select_db("astral_database", $con);
session_start();
$username = $_SESSION['username'];
$forumtext = $_POST["forumtext"];
$forumsubject = $_POST["forumsubject"];
$postquery = 'INSERT INTO Forums (Creator, Subject, Content) VALUES ("$username", "$forumsubject", "$forumtext"); SELECT LAST_INSERT_ID()';
$result = mysql_query($postquery, $con);
if (!$con) {
echo "<b>If you are seeing this, please send the information below to astraldevgroup#gmail.com</b><br>Error (331: dbconnect experienced fatal errors while attempting to connect)";
die();
}
if ($username == null) {
echo "<b>If you are seeing this, please send the information below to astraldevgroup#gmail.com</b><br>Error (332: Username was not specified while attempting to send request)";
die();
}
if ($result != null) {
echo "last id: " . $result;
$fhandle = fopen("recentposts.txt", "r+");
$contents = file_get_contents("recentposts.txt");
fwrite($fhandle, json_encode(array("postid" => $result, "creator" => $username, "subject" => $forumsubject, "activity" => time())) . "\n" . $contents);
fclose($fhandle);
mysql_close($con);
header("location: http://astraldevgroup.com/forums");
die();
} else {
die("<b>If you are seeing this, please send the information below to astraldevgroup#gmail.com</b><br>Error (330: Unhandled exception occured while posting forum to website.)<br>");
echo mysql_error();
}
mysql_close($con);
?>
First off, the mysql_query doesn't return anything from the SELECT statement. I haven't found anything that will properly run both the SELECT statement and the INSERT statement in the same query. If I try running them in two different statements, it still doesn't return anything. I tried running the following statement in the SQL console and it ran perfectly fine without errors.
INSERT INTO Forums (Creator, Subject, Content) VALUES ("Admin", "Test forum 15", "This is a forum that should give me the post id."); SELECT LAST_INSERT_ID();
The mysql_query function does not run multiple statements
Reference: http://php.net/manual/en/function.mysql-query.php
mysql_query() sends a unique query (multiple queries are not supported) to the currently active database on the server ...
That's one reason your call to mysql_query isn't returning a resultset.
The most obvious workaround is to not try to run the SELECT in the same query. You could use a call to the mysql_insert_id instead.
Reference: PHP: mysql_insert_id http://php.net/manual/en/function.mysql-insert-id.php
Answers to some of questions you didn't ask:
Yes, your example code is vulnerable to SQL Injection.
Yes, the mysql_ interface has been deprecated for a long time.
Yes, you should being using either PDO or mysqli interfaces instead of the deprecated mysql_ functions.
FOLLOWUP
Re-visiting my answer, looking again at the question, and the example code.
I previously indicated that the code was vulnerable to SQL Injection, because potentially unsafe values are included in the SQL text. And that's what it looked like on a quick review.
But looking at it again, that isn't strictly true, because variable substitution isn't really happening, because the string literal is enclosed in single quotes. Consider what the output from:
$foo = "bar";
echo '$foo';
echo '"$foo"';
Then consider what is assigned to $postquery by this line of code:
$postquery = 'INSERT ... VALUES ("$username", "$forumsubject", "$forumtext")';
Fixing that so that $username is considered to be a reference to a variable, rather than literal characters (to get the value assigned to $username variable incorporated into the SQL text) that would introduce the SQL Injection vulnerability.
Prepared statements with bind placeholders are really not that hard.
$result will never be null. It's either a result handle, or a boolean false. Since you're testing for the wrong value, you'll never see the false that mysql_query() returned to tell you that the query failed.
As others have pointed out, you can NOT issue multiple queries in a single query() call - it's a cheap basic defense against one form of SQL injection attacks in the PHP mysql driver. However, the rest of your code IS vulnerable other forms of injection attacks, so... better start reading: http://bobby-tables.com
Plus, on the logic side, why are you testing for a null username AFTER you try to insert that very same username into the DB? You should be testing/validating those values BEFORE you run the query.

Secure against SQL Injection - PDO, mysqli [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Best way to prevent SQL Injection in PHP
I just found that my website is vunerable.
Since it's connected to a DB and have functions like: Register, Change Password, Notices, etc... and SUPOSING it's fully vulnerable.
What should I look for into the code in order to start making it safe?
I mean, I did some researches and everywhere, everyone says different things about security.
"Use PDO."
"Use mysql_real_escape_string."
"Use addslashes."
What exactly should I look for??
"$_POST" and "$_GET" variables??
"$_SESSION" variables?
SQL querys?
$sql = "select * from user";
$sql = "update user set user="new_user_name";
$sql = "insert into user (user) values ('userid')";
What should I do in each case?
Please, help me to know what and where I must go.
Thank you.
Following are the points to be considered for making safe php application.
USE PDO or mysqli
Never trust any inputs. Consider every variable viz $_POST, $_GET, $_COOKIE, $_SESSION, $_SERVER as if they were tainted. Use appropriate filtering measure for these variables.
To avoid XSS attack use php’s builtin functions htmlentities,
strip_tags, etc while inserting the user input data into the
database.
Disable Register Globals in PHP.INI
Disable “allow_url_fopen” in PHP.INI
Don’t allow user to input more data than required. Validate input to
allow max number of characters. Also validate each field for
relevant datatypes.
Disable error reporting after Development period. It might give
information about database that’ll be useful to hackers.
Use one time token while posting a form. If token exist and matches
the form post is valid otherwise invalid.
Use parametrized database queries
Use stored procedures
You can google for each point for more details.
HOpe this helps
What you should look for: Any data send from the client/user. Sanitize/escape this data.
PDO can sanitize queries (using PDO::prepare) and supports multiple SQL systems.
For MySQL, use MySQLi. mysqli_real_escape_string is the function to use for sanitizing data if you are using MySQL.
None of the SQL queries you provided are actually vulnerable to SQL injection.
SQL injection vulnerabilities happen because SQL input is not properly escaped.
For example:
$sql = "select * from users where user_id =" . $_GET['user_id'];
Consider if I passed in the following:
http://some_server.com/some_page.php?user_id=123%20or%201=1
The query when executed would end up being:
select * from users where user_id = 123 or 1=1
To fix this, use parameterized queries:
$query = "select * from users where user_id = ?"
When you bind the user_id value to the query, the data access layer will escape the input string properly and the following would be executed:
select * from users where user_id = '123 or 1=1' which would not return any rows, preventing the injection
If using PHP and the mysql extension:
$sql = "select * from users where user_id = '" . mysql_real_escape_string($_GET['user_id']) . "'";
Keep in mind you need to escape ALL input that is going into a SQL query:
$sql = "select id_column from some_table where id = 1";
$stmt = mysqli_query($conn, $sql);
if($stmt === false) die(mysqli_error($conn) . "\n");
while($row = mysqli_fetch_assoc($conn, $stmt) {
$sql = "update some_other_table set some_value = 'new value' where some_column = '" . mysqli_real_escape_string($conn, $row['id_column']) . "'";
....
}
This is because values you select from the database might include characters that are not safe for execution in a SQL statement, like the name "O'Hara" or example.
}
I've been using PDO.
An example for that in your case:
<?php
$stmt = $dbh->prepare("insert into user (user) values (?)");
$stmt->bindParam(1, $name);
$name = 'ValueHere';
$stmt->execute();
?>

Replacing mysql_* functions with PDO and prepared statements

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

Categories