Security when creating a PDO query function

Asked

Viewed 1,808 times

10

I am now migrating to PDO because of security, but I don’t know if I am using PDO correctly.

I am creating these functions to make faster the use of PDO, but I do not know if this form is safe. It’s the same thing that I’m using the function-free form?

<?php 
    function Conecta(){
try{
    $conexao=new PDO("mysql:host=localhost;dbname=sistemaescolar",'root','');
}
catch(PDOException $erro){
    $erro->getMessage();
}   
return  $conexao;
}

function ExecutaConsulta($conexao,$sql){
  $valor=func_get_args();
  $valores=array_slice($valor,2);
  $preparado=$conexao->prepare($sql);
  $preparado->execute($valores);
  $executar=$preparado->fetch();

  return $executar;

}
$conecta=Conecta();


$sql="SELECT * FROM se_professores WHERE id=?";
$resultado=ExecutaConsulta($conecta,$sql,1);
echo $resultado['nome'];

?>
  • Everything is up to date with your code, functions are useful for reusing code, vc n will create 5 connections in 5 files? a function solves ;)

  • Thanks for the answer. And no, I won’t create 5 connections in 5 files, I’m replacing mysql... So I’m putting this in the connection file that goes through all parts of the site, just mixed it up there to get better

  • The only thing missing is to make Binding to make queries safer etc. You can refer to: https://github.com/Leonardo-Souza/MinPDO

  • 1

    Blz. I’ll read about it

  • I think it’s a good question of code review and can serve as a basis for a canonical response and possible target of future duplicates. What do you think of the edition I made, @rray?

  • got better, clearer.

  • Migrating from which lib to PDO? And which DB to use?

  • Of the mysql absoleto

  • 1

    It would probably be better mysqli then, if what it seeks is security and efficiency (mainly because mysqli does not simulate Binding by default, as the PDO does, which is "fake" if it does not modify the default behavior). Anyway, whether with one or the other, the important thing is the correct escape from things before using in queries. Just so you understand the difference, a real Binding sends the query separate from the values, which eliminates any possibility of injection. A simulation depends on exhaust from the customer’s side. Also, only native bind is efficient for sequential operations.

  • @Bacco, could you elaborate a complete answer for me? because I’ve heard about this about the PDO, but it wasn’t entirely clear. And I’m deepening more recently. These questions that you pointed out in your comment would be of great interest I think not only for me

  • @Localhost this already has on the site in some corner, about the differences of PDO and mysqli_ - I confess that the answers are not explaining so well this part, but it is something that already has on the site.

  • Okay, I’ll look then :)

  • 1

    Follow the link. There is an answer that I even gave 50 bonus points, because the author remembered to explain this. http://answall.com/questions/8302/70

Show 8 more comments

3 answers

3


Code analysis

Its implementation is correct, because by passing the values of a query as an array to the function Pdostatement::execute, it will prepare the received parameters treating all as PDO::PARAM_STR, that is, all received parameters will be treated as a string and will not risk suffering an Sql Injection, unless the values are concatenated earlier in your query.

In the example below, I use an array as a third parameter, but the reason will be explained later.

 // Forma vulnerável
 $nome = $_POST['nome'];
 $valor = $_POST['valor'];
 $query = 'INSERT INTO REGISTRY (name, value) VALUES (' . $nome . ', ' . $valor . ')';
 $resultado0 = executaConsulta($conecta, $query);

 // 1º Forma correta
 $query = 'INSERT INTO REGISTRY (name, value) VALUES (:name, :value)';
 $valores = array(
      ':name' => $_POST['nome']
      ':value' => $_POST['value']
 );
 $resultado1 = executaConsulta($conecta, $query, $valores);

 // 2º Forma correta
 $query = 'INSERT INTO REGISTRY (name, value) VALUES (?, ?)';
 $valores = array(
      $_POST['nome']
      $_POST['value']
 );
 $resultado2 = executaConsulta($conecta, $query, $valores);

 // 3º Forma correta
 // A função filter_input é utilizada para acessar variáveis globais de forma mais segura.
 // Existem outras funções deste tipo. http://php.net/manual/en/book.filter.php
 $query = 'INSERT INTO REGISTRY (name, value) VALUES (:name, :value)';
 $valores = array(
      ':name' => filter_input( INPUT_POST, 'nome', FILTER_SANITIZE_STRING );
      ':value' => filter_input( INPUT_POST, 'value', FILTER_SANITIZE_STRING );
 );
 $resultado3 = executaConsulta($conecta, $query, $valores);

About prepared query or Prepared statements

Some databases allow to use a resource of reuse of queries. It would be something like preparing a text where only a few words change, and each time you use this text, you need to inform only what the new words are.

However, not all databases support this feature (today is no longer the case with Mysql), and as the PDO aims to be a practical interface for using multiple database drivers, they had to implement a way that this feature could be used when needed. To solve this problem, the resource is simulated by the function.

In the documentation there are these examples:

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);

// Inserindo uma linha
$name = 'one';
$value = 1;
$stmt->execute();

// Inserindo outra linha com diferentes valores
$name = 'two';
$value = 2;
$stmt->execute();

Forcing PDO to use native prepared queries

To force PDO to always use the native prepared queries feature, add right after connecting:

$conexao->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

The PDO has treatment for if an error occurs, the emulated version is used, but there are some disadvantages in using the native feature in some versions

  • Do not take advantage of cache resulting in loss of performance;
  • Cannot be performed by some types of queries like "SHOW TABLES";
  • Do not communicate well with the size of columns in other queries that use "SHOW" returning distorted results.
  • Calling several times procedurespode cause connection loss.

Some additional information on https://stackoverflow.com/questions/10113562/pdo-mysql-use-pdoattr-emulate-prepares-or-not

Binding of values and variables

In PDO there are two methods that allow you to associate parameters to your queries, they are the Pdostatement::bindParam and Pdostatement::bindValue.

The bindParam associates the name of a variable to the query parameter, that is, if the value of a variable is changed and the method execute is called again, the query will be executed with the new value.

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name) VALUES (:name)");
$stmt->bindParam(':name', $name);

// Inserindo uma linha
$name = 'one';
$stmt->execute();

// Inserindo outra linha com diferentes valores
$name = 'two';
$stmt->execute();

Already the bindValue assigns a specific value to a parameter of your query, ie to change the value of the parameter you will have to call the method bindValue again.

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name) VALUES (:name)");

// Inserindo uma linha
$stmt->bindValue(':name', 'one');
$stmt->execute();

// Inserindo outra linha com diferentes valores
$stmt->bindValue(':name', 'two');
$stmt->execute();

In both methods it is possible to specify which type should be used to treat the query parameter value. O bindParam besides the type it is also possible to indicate other options such as the maximum value size.

Suggestions for improvements

Improvement in code style

I recommend giving a read on the attempt that the PHP community has made to standardize some things, the PSR-2 is a guide on how to better format your code. Improving this template is a good point to start observing what your code can improve and will help you find both security holes and some bugs.

Organization of parameters

When using the parameters "magic" using the function func_get_args() you will have problems documenting, because the signature of your function does not make it explicit that it is possible to pass more arguments, this will leave other developers confused with your code.

In the presented code, there is no need for the function to receive infinite parameters, because the only thing that the function code performs is to obtain an array of everything that has been passed, removing what is already known.

To make the signature clearer, you can set a third parameter with a default value, in the case of an empty array, because the function execute PDO expects an array.

// Versão reescrita
function executaConsulta($conexao, $sql, $valores = array()){
    $preparado = $conexao->prepare($sql);
    $preparado->execute($valores);
    $resultado = $preparado->fetchAll();

    return $resultado;
}

In the function I also changed the function Dostatement::fetch() by function Dostatement::fetchAll(), so its function will always return all the result obtained in an array. Working with array is much easier than with PDO objects.

Use of the function

When using the function, you will make it clearer to use keys instead of the wildcard character (?) as well as avoid having to pass the same value twice, if used in two places of your query. See the two possibilities of use:

// 1º forma
$sql = "SELECT * FROM se_professores WHERE id = ?";
$resultado1 = executaConsulta($conecta, $sql, array(1));

foreach ($resultado1 as $linha) {
    echo $linha['nome'];
}

// 2º forma
$sql = "SELECT * FROM se_professores WHERE id = :id";
$valores = array(':id' => 1);
$resultado2 = executaConsulta($conecta, $sql, $valores);

foreach ($resultado1 as $linha) {
    echo $linha['nome'];
}

3

Look it all depends on how ExecutaConsulta will be used. If as in your example:

$sql="SELECT * FROM se_professores WHERE id=?";
$resultado=ExecutaConsulta($conecta,$sql,1);

there is no obvious security problem, however nothing in your code prevents calls of this type:

$userId = "1; DROP TABLE se_professores"; // user input com evil SQL Injection
$sql="SELECT * FROM se_professores WHERE $userId";
$resultado=ExecutaConsulta($conecta,$sql);

So the answer is that there is nothing wrong with its function and at the same time it does not entail a "safe use" of PDO.

Another thing, maybe you’re just using for testing the new PDO("mysql:host=localhost;dbname=sistemaescolar",'root',''); but anyway it’s always good to remember, never use the root user of the database in production and never drop a user without password in the production database.

  • Yes, it is only for testing, only local server, on my online server has user and password yes.

  • I understood what you meant, but would there be problem in this use? Executaquery($connects,"SELECT * FROM se_turmas WHERE id=?",$idTurma); Because, I am not passing any variable in sql, never step... Because the example of an sql injection, would be in the case of passage of variables in the proper sql correct?

  • As I said in the reply (maybe it was not clear, I will see if edit) if you always use the way your example is no problem at all, what I wanted to make clear is that its Run query function does not prevent someone from doing a query in such a way that it is vulnerable to attacks, as in the case of my Intel SQL exeplo.

  • Blz, I get it now

3

The best (and most recommended) way is the correct use of PDO. Although it is a little more difficult to implement, it ensures greater security when executing the query. That’s because the process is done in two parts.

1) We created the query template. This template is nothing more than a template of what the query will be. Unlike the other methods, you will not enter the variable itself at this time. It will only indicate where the variable will be placed. In case, it will be right after the "...name =".

$var = $pdo->prepare('SELECT * FROM tabela where nome = :nome');

2) Now we indicate which value will occupy the space indicated by ":name":

$var->bindValue(':nome', 'Nome da pessoa');

At the end the code will look something like this:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=crud', 'root', '');
$var = $pdo->prepare('SELECT * FROM tabela where nome = :nome');
$var->bindValue(':nome', 'Nome da pessoa');
$run = $var->execute();
$result = $var->fetchAll(PDO::FETCH_ASSOC);
var_dump($result);
?>

There is also another way. A faster way (but not the most indicated) is to remove special characters from the variables that will be used in the query. Without using ";" (semicolon) and "'" (single or double quotes) and other special characters it is practically impossible to execute sql Injection.

Take the example:

$password = preg_replace('/[^[:alnum:]_]/', '',$senha);

If you want more information:> http://www.devmedia.com.br/evitando-sql-injection-em-aplicacoes-php/27804

Browser other questions tagged

You are not signed in. Login or sign up in order to post.