Can PHP's PDO be limited to a single query? - php

PHP's PDO allows multiple querys to be executed at once, either via the query() method or as a prepared statement. Both of the following examples work:
// Two SQL queries
$query = "SELECT * FROM table; DROP table;"
// Execute via query()
$pdo->query($query);
// Execute via prepared statement
$stmt = $pdo->prepare($query);
$stmt->execute();
Is there any way to limit PDO to a single query at a time, much like the mysql_query() function is?

This is a more up-to-date answer to this question.
The old way of preventing multi query execution was to disable emulated prepares, however this was only applicable to the PDO::prepare() method. In newer versions of PHP (>= 5.5.21 and >= 5.6.5), a new constant has been introduced to disable this multi query execution in both PDO::prepare() and PDO::query(). (Constants aren't usually added in patch versions, but this was done due to the severity of a Drupal SQL injection attack brought about by this capability).
The new constant is PDO::MYSQL_ATTR_MULTI_STATEMENTS and must be set on object creation (as the fourth argument to the PDO constructor) - setting it on a pre-existing object with PDO::setAttribute() will not work.
$pdo = new PDO('mysql:host=_;dbname=_', '', '', [PDO::MYSQL_ATTR_MULTI_STATEMENTS => false]);

Mmm, there's a way of achieving this by disabling the emulation of prepared statements in PDO to make it use the native mysql API instead (multi-querying is not supported in server-side prepared statements):
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
However, one of the drawbacks of this option is that the query cache is lost.

Related

Is it possible to use (with PHP) prepared statements that have been previously declared in the mysql CLI?

In the mysql CLI I have prepared a statement like this:
PREPARE registrarUser FROM 'INSERT INTO Users (Users_name,Email,pass) values (?,?,?)'
In my database the prepared statements have to be done this way,instead of using a php method like this::
$conn->prepare("INSERT INTO Users (Users_name,Email,pass) VALUES (?, ?, ?)");
So I can't use the prepared statement or bind arguments.
I have tried this query which mimics the required statements in mysql CLI
$query = sprintf('
SET #Users_name = "%s";
SET #Email= "%s";
SET #pass = "%s";
EXECUTE registrarUser USING #Users_name, #Email, #pass;',$Users_name,$Email,$pass);
But it returns the following syntax error:
Errormessage: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SET #Email= "eds#gmail.com"; SET #pass = "Thinkshap2"; EXECUTE registrar' at line 2
Does anyone know if there is a way to do it?
Thank you very much in advance;
No, it's not possible. Prepared statements have the session scope. Whenever you open a new connection in PHP, you open a new MySQL session. You can use PREPARE and EXECUTE in PHP, but both operations have to be done using the same session.
In other words, statements created with PREPARE do not persist on the database server. They only exist for the lifetime of the current session.
The reason why you are getting a syntax error in PHP is because you have concatenated multiple SQL statements together. You can't do that by default in PHP due to security considerations. Execute each one separately. For example, this works:
$stmt = $mysqli->query("PREPARE registrarUser FROM 'SELECT ?'");
$stmt = $mysqli->query("EXECUTE registrarUser USING 32");
Warning. Using PREPARE and EXECUTE from PHP defeats the main purpose of prepared statements usage in PHP. The main advantage is that you can separate variables from SQL syntax. You can't do that with PREPARE and EXECUTE. This is why both PDO and mysqli have prepared statements. Use mysqli::prepare() and mysqli_stmt::execute()
It's not possible.
From the MySQL manual
The scope of a prepared statement is the session within which it is created...
A prepared statement created in one session is not available to other sessions.

PDO not filter SQL Injection

I have an Application with PHP 5.3.29 and MySQL 5.6.35.
I used SQLQUERY to execute SQL instrucctions, then change to PDO with prepared Statements to avoid SQL-i, but whe i test my app with ZAP 2.6.0, i can confirm that the SQL-I still happens, despite the use of "PDO" and "prepare".
I activated the general log at MySQL and looked for all statements that were executed.
My code is:
function cerrar_sesion($usuario) {
$pdo = new
PDO("mysql:"."host=".DB_SERVIDOR.";"."dbname=".DB_BASEDATOS,DB_USUARIO, DB_CLAVE);
$query = $pdo->prepare('UPDATE ADMIN_USUARIO SET USERID=\' \' WHERE C_USUARIO= :usuario');
$query->bindParam(':usuario',$usuario,PDO::PARAM_INT);
$query->execute();
$pdo = null;
.........
}
Checking the DB log i see the parameter "C_USUARIO" changed, the following 3 lines were extracted from MySQL Log:
227726 Query UPDATE ADMIN_USUARIO SET USERID=' ' WHERE C_USUARIO= '54/2' 227730 Query UPDATE ADMIN_USUARIO SET USERID=' ' WHERE C_USUARIO= '108/2' 227732 Query UPDATE ADMIN_USUARIO SET USERID=' ' WHERE C_USUARIO= '108/2'
Note the values for C_USUARIO should't have "/2", that was injected by ZAP
I expected PDO to prevent the injection, but this wasn't the case, how can i do this using PDO?
Please help me, i´ll apreciate it.
By default, PDO "emulates" prepared statements, by interpolating the bound variables into your SQL query string, and then executing that SQL directly, without using parameters.
PDO does apply correct escaping as it interpolates your variables in the query, so it is safe with respect to SQL injection.
If you want real parameterized queries, disable emulation:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
See http://php.net/manual/en/pdo.setattribute.php for more information.
If you disable emulation, your MySQL query log will show the PREPARE and EXECUTE as separate steps. But MySQL will also log the full query including parameter values. This is also safe, it's just a convenience that MySQL does for the sake of logging, because it's useful to show the query with values. See example in my answer to https://stackoverflow.com/a/210693/20860.

How to prevent SQL injection in Codeigniter [duplicate]

Hi all I need to use Prepared Statements in my site. I tried use this
$sql = "SELECT * FROM tbl_user WHERE uid=:id and activation_key=:key";
$query = $this->db->query(
$sql,
array( ':id' => $uid ,':key' => $activation_key)
);
but this is not working. When I change :id and :key to ? its working.
CodeIgniter does not support Prepared Statements. If you look at the sourcecode for CI's Database class, you will see that they resolve bindings simply by replacing the question marks with the data from the passed array:
https://github.com/EllisLab/CodeIgniter/blob/develop/system/database/DB_driver.php#L874
They only support Query Binding with unnamed placeholders. See http://ellislab.com/codeigniter/user-guide/database/queries.html
Query Bindings
Bindings enable you to simplify your query syntax by letting the system put the queries together for you. Consider the following example:
$sql = "SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?";
$this->db->query($sql, array(3, 'live', 'Rick'));
The question marks in the query are automatically replaced with the values in the array in the second parameter of the query function.
and http://ellislab.com/forums/viewthread/105112/#528915
Even though CI doesn’t support prepared statements, it does support Query Bindings. With prepared statements you have to call some type of prepare() function and then some type of execute() function. With query bindings, you only have to call one function and it basically does the same thing. Because of this, I like query bindings better than prepared statements.
On a sidenote, changing ? to :foo is merely changing from unnamed to named bindings (which CI apparently does not support either). Just because you use either or doesn't mean you are preparing the statements.
I came across this question as I faced a similar issue. The answer is correct that CI doesn't support prepared statements. However it doesn't mean that you can't use prepared statements!
In the following example I am using PDO as my connection class but the following code will work:
$q = $this->db->conn_id->prepare('SELECT * FROM tbl_user WHERE uid=? and activation_key=?');
$q->execute(array($param1,$param2));
print_r($q->fetchAll());
Note the conn_id is the PDO object against which you can run your prepared statements.
What this won't allow however is for you to get the query string which the native CI functions allow. You will need something like Get Last Executed Query in PHP PDO for that.
Further more however this doesn't stop you using the Query Builder to build your statements which you can then use in the PDO prepare. For example -
$db->where('uid = ?',null,false);
$db->where('activation_key = ?',null,false);
$q = $this->db->conn_id->prepare($db->get_compiled_select('tbl_user'));
Would build the query and would allow you to see the basic query if you output $db->get_compiled_select('tbl_user');
As #site80443 points out, CI4 now supports prepared statements and this can be found here:
https://codeigniter.com/user_guide/database/queries.html?highlight=prepared#prepared-queries

PHP Postgres PDO driver does not support prepared statement?

Am I losing my mind, or does the Postgres PDO driver just not support prepared statements, but instead emulates them client side?
The following code returns NO ERROR for the prepare() call, even though it should. Instead, it returns the applicable error when execute() is called.
Edit: Since according to Daniel Vérité I'm wrong, I added his suggested code. I still get the error. My code now looks like the below, with Daniel's line added.
<?php
$pdo = new PDO("pgsql:host=myhost;dbname=mydatabase");
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // as suggested by Daniel
$sth = $pdo->prepare('COMPLETE GARBAGE');
echo "[prepare] errorInfo = " . print_r($sth->errorInfo(), true);
$sth->execute();
echo "[execute] errorInfo = " . print_r($sth->errorInfo(), true);
PHP version 5.3.15, PHP Postgres client version 9.1.4, Postgres server version 9.2.1.
See http://www.php.net/manual/en/pdo.prepare.php
Note:
Emulated prepared statements does not communicate with the database
server so PDO::prepare() does not check the statement.
(in fact real prepared statements are not sent immediately anyway, see answer to Q2 below)
Anyway you may issue:
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
to get real prepared statements implemented with the SQL PREPARE command.
See http://www.php.net/manual/en/pdo.setattribute.php for more.
On further discussion and tests, two questions arise:
Q1. Why does pdo::getAttribute(PDO::ATTR_EMULATE_PREPARES) yield an error?
Indeed setAttribute doesn't error out but getAttribute(PDO::ATTR_EMULATE_PREPARES) says:
'SQLSTATE[IM001]: Driver does not support this function: driver does
not support that attribute'
Looking at the documentation for pdo::getAttribute, it says The constants that apply to database connections are as follows, and a number of constants follow from PDO::ATTR_AUTOCOMMIT to PDO::ATTR_TIMEOUT, and it's remarkable that PDO::ATTR_EMULATE_PREPARES is not in them. So strictly speaking, we should not expect getAttribute(PDO::ATTR_EMULATE_PREPARES) to work, anyway.
Now looking at the source code to be sure, it appears that the pdo_pgsql driver provides a pdo_pgsql_get_attribute function that has a switch statement on:
PDO_ATTR_CLIENT_VERSION
PDO_ATTR_SERVER_VERSION
PDO_ATTR_CONNECTION_STATUS
PDO_ATTR_SERVER_INFO
and that's it. No trace of PDO_ATTR_EMULATE_PREPARES which is why ultimately this error appears.
On the other hand, the function pdo_pgsql_set_attr has a switch statement on:
PDO_ATTR_EMULATE_PREPARES
PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT
which confirms that this attribute is actually taken into account when set.
So PDO is just inconsistent with getAttribute that doesn't match setAttribute.
Q2 - When prepare emulation is false, why doesn't a bogus statement immediately raise an error when prepared?
Consider this piece of code in pgsql_statement.c:
if (!S->is_prepared) {
stmt_retry:
/* we deferred the prepare until now, because we didn't
* know anything about the parameter types; now we do */
S->result = PQprepare(H->server, S->stmt_name, S->query,
stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0,
S->param_types);
It shows that PQprepare is used (so that's a "real" prepared statement), but also that the statement is not immediately sent to the server. That's why the dbo::prepare("bogus statement") won't return false: it's actually not sent to the server for lack of parameter types.

What is the PDO equivalent of function mysql_real_escape_string?

I am modifying my code from using mysql_* to PDO. In my code I had mysql_real_escape_string(). What is the equivalent of this in PDO?
Well No, there is none!
Technically there is PDO::quote() but it is rarely ever used and is not the equivalent of mysql_real_escape_string()
That's right! If you are already using PDO the proper way as documented using prepared statements, then it will protect you from MySQL injection.
# Example:
Below is an example of a safe database query using prepared statements (pdo)
try {
// first connect to database with the PDO object.
$db = new \PDO("mysql:host=localhost;dbname=xxx;charset=utf8", "xxx", "xxx", [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
} catch(\PDOException $e){
// if connection fails, show PDO error.
echo "Error connecting to mysql: " . $e->getMessage();
}
And, now assuming the connection is established, you can execute your query like this.
if($_POST && isset($_POST['color'])){
// preparing a statement
$stmt = $db->prepare("SELECT id, name, color FROM Cars WHERE color = ?");
// execute/run the statement.
$stmt->execute(array($_POST['color']));
// fetch the result.
$cars = $stmt->fetchAll(\PDO::FETCH_ASSOC);
var_dump($cars);
}
Now, as you can probably tell, I haven't used anything to escape/sanitize the value of $_POST["color"]. And this code is secure from myql-injection thanks to PDO and the power of prepared statements.
It is worth noting that you should pass a charset=utf8 as attribute, in your DSN as seen above, for security reasons, and always enable
PDO to show errors in the form of exceptions.
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
so errors from you database queries won't reveal sensitive data like your directory structure, database username etc.
Last but not least, there are moments when you should not trust PDO 100%, and will be bound to take some extra measures to prevent sql injection, one of those cases is, if you are using an outdated versions of mysql [ mysql =< 5.3.6 ] as described in this answer
But, using prepared statements as shown above will always be safer, than using any of the functions that start with mysql_
Good reads
PDO Tutorial for MySQL Developers
There is none*! The object of PDO is that you don’t have to escape anything; you just send it as data. For example:
$query = $link->prepare('SELECT * FROM users WHERE username = :name LIMIT 1;');
$query->execute([':name' => $username]); # No need to escape it!
As opposed to:
$safe_username = mysql_real_escape_string($username);
mysql_query("SELECT * FROM users WHERE username = '$safe_username' LIMIT 1;");
* Well, there is one, as Michael Berkowski said! But there are better ways.
$v = '"'.mysql_real_escape_string($v).'"';
is the equivalent of $v = $this->db->quote($v);
be sure you have a PDO instance in $this->db so you can call the pdo method quote()
There is no need of mysql_real_escape_string in PDO.
PDO itself adjust special character in mysql query ,you only need to pass anonymous parameter and bind it run time.like this
Suppose you have user table with attribute name,email and password and you have to insert into this use prepare statement like this
you can pass name as => $name="Rajes'h ";
it should execute there is no need of equivalent of mysql_real_escape_string
$stmt="INSERT into user(name,email,password) VALUES(:name,:email,:password)";
try{
$pstmt=$dbh->prepare($stmt);//$dbh database handler for executing mysql query
$pstmt->bindParam(':name',$name,PDO::PARAM_STR);
$pstmt->bindParam(':email',$email,PDO::PARAM_STR);
$pstmt->bindParam(':password',$password,PDO::PARAM_STR);
$status=$pstmt->execute();
if($status){
//next line of code
}
}catch(PDOException $pdo){
echo $pdo->getMessage();
}
The simplest solution I've found for porting to PDO is the replacement for mysql_real_escape_string() given at https://www.php.net/manual/en/mysqli.real-escape-string.php#121402. This is by no means perfect, but it gets legacy code running with PDO quickly.
#samayo pointed out that PDO::quote() is similar but not equivalent to mysql_real_escape_string(), and I thought it might be preferred to a self-maintained escape function, but because quote() adds quotes around the string it is not a drop in replacement for mysql_real_escape_string(); using it would require more extensive changes.
In response to a lot of people's comments on here, but I can't comment directly yet (not reached 50 points), there ARE ACTUALLY needs to use the $dbh->quote($value) EVEN when using PDO and they are perfectly justifiable reasons...
If you are looping through many records building a "BULK INSERT" command, (I usually restart on 1000 records) due to exploiting InnoDb tables in MySQL/Maria Db. Creating individual insert commands using prepared statements is neat, but highly inefficient when doing bulk tasks!
PDO can't yet deal with dynamic IN(...) structures, so when you are building a list of IN strings from a list of user variables, YOU WILL NEED TO $dbh->quote($value) each value in the list!
So yes, there is a need for $dbh->quote($value) when using PDO and is probably WHY the command is available in the first place.
PS, you still don't need to put quotes around the command, the $dbh->quote($value) command also does that for you.
Out.
If to answer the original question, then this is the PDO equivalent for mysql_real_escape_string:
function my_real_escape_string($value, $connection) {
/*
// this fails on: value="hello'";
return trim ($connection->quote($value), "'");
*/
return substr($connection->quote($value), 1, -1);
}
btw, the mysqli equivalent is:
function my_real_escape_string($value, $connection) {
return mysqli_real_escape_string($connection, $value);
}

Categories