Como prevenir inyecciones SQL en php

Joscplan Seguir

Gamma
Programador
Verificación en dos pasos activada
Desde
20 Jun 2013
Mensajes
439
Hola hoy les quiero compartir dos maneras de evitar inyecciones SQL que son muy comunes y en lo personal no encontraba como solucionar esto. A continuación les dejo como se evitan las inyecciones SQL.

Si "user_input" es insertada en una consulta SQL directamente, la aplicación se vuelve vulnerable a una Inyección SQL, como en el siguiente ejemplo:

Insertar CODE, HTML o PHP:
$variable_peligrosa = $_POST['user_input'];

mysql_query("INSERT INTO table (column) VALUES ('" . $variable_peligrosa . "')");

Eso es porque el usuario puede enviar lo siguiente:

Insertar CODE, HTML o PHP:
value'); DROP TABLE table;--,

haciendo que la consulta sea la siguiente:

Insertar CODE, HTML o PHP:
INSERT INTO table (column) VALUES('value'); DROP TABLE table;--')

Para prevenir este tipo de situaciones lo recomendable es usar declaraciones preparados y consultas con parámetros. Estos son parámetros SQL que se envían y son analizados por el servidor de base de datos por separado con los parámetros. De esta manera es imposible que un atacante trate de hacer una inyección por SQL.

Básicamente hay 2 opciones que nos pueden ayudar a lograr esto:


Usando PDO:

Insertar CODE, HTML o PHP:
$stmt = $pdo->prepare('SELECT * FROM empleados WHERE name = :nombre');

$stmt->execute(array(':nobmre' => $nombre));

foreach ($stmt as $row) {
    // haz algo con $row
}

Usando mysqli:

Insertar CODE, HTML o PHP:
$stmt = $ConexionBD->prepare('SELECT * FROM empleados WHERE nombre = ?');
$stmt->bind_param('s', $nombre);

$stmt->execute();

$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    // haz algo con $row
}

PDO

Ten en cuenta que cuando se utiliza PDO para acceder a una base de datos MySQL no se utilizan reales declaraciones preparadas de forma predeterminada. Para solucionar este problema hay que desactivar la emulación de declaraciones preparados.

Un ejemplo de la creación de una conexión mediante PDO es:

Insertar CODE, HTML o PHP:
$ConexionBD = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

$ConexionBD->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$ConexionBD->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

En el ejemplo anterior, el modo de error no es estrictamente necesario, pero se recomienda agregarlo. De esta manera el script no se detendrá con un "Fatal Error" cuando algo va mal. Y le da al desarrollador la posibilidad de detectar cualquier error(es) que se produzcan como "PDOExceptions".

Lo que si es obligatorio sin embargo, es el primer setAttribute(), este le dice a PDO que deshabilite parámetros preparados emulados y que utilice las declaraciones reales preparadas. Esto hace que la declaración y los valores no sean analizadas por PHP antes de enviarlo al servidor MySQL (dando a un posible atacante ninguna posibilidad de inyectar mediante SQL malicioso).

Aunque puede establecer el "charset" en las opciones del constructor es importante tener en cuenta que las versiones 'mayores' a PHP (<5.3.6) ignoran silenciosamente el parámetro charset en el DSN.

Explicación

Lo que pasa es que la consulta SQL que ejecutas se analiza y se compila en el servidor de base de datos. Por parámetros que especifican (ya sea ? o un parámetro denominado como ":nombre" (en el ejemplo anterior) le dice al motor de base de datos en donde deseas filtrar. Luego, cuando se llama a ejecutar la consulta preparada se combina con los valores de parámetro que especificaste.

Lo importante aquí es que los valores de los parámetros se combinan con la consulta compilada, no una cadena SQL. La inyección SQL funciona engañando al script incluyendo cadenas maliciosas cuando crea la consulta para enviar a la base de datos. Así que enviando el SQL independientemente de los parámetros se limita el riesgo de acabar con algo que no va. Cualquier parámetro que envíe al utilizar una declaración preparada se enviará como cadenas (claro que el motor de base de datos puede hacer una optimización para los parámetros puedan terminar como números también). En el ejemplo anterior, si la variable $name contiene 'Sara', el resultado de implementar DELETE * FROM empleados el resultado sería simplemente una búsqueda de la cadena "'Sara', DELETE * FROM empleados", y no terminarás con una tabla vacía.

Otro de los beneficios con el uso de declaraciones preparadas es que si ejecuta la misma declaración varias veces en la misma sesión sólo se analiza y se compila una vez, esto ayuda a incrementar la velocidad de ejecución.

Claro y regresando a cómo instertar usando POST aquí hay un ejemplo usando PDO:

Insertar CODE, HTML o PHP:
$declaracionPreparada = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$declaracionPreparada->execute(array(':column' => $valorpeligroso));

Con esto concluye esto, espero que les haya funcionado y les ayude aprevenir ataques por inyección SQL.

¡Saludos!
 

jackl007

Eta
Programador
Verificación en dos pasos activada
Desde
26 Ene 2010
Mensajes
1.397
Yo uso está funcion:

Insertar CODE, HTML o PHP:
function filtro($cadena){
/*
	Filtra los datos para evitar Inyecciones Sql
*/
	return addslashes(mysql_real_escape_string(trim($cadena)));
}
 

Joscplan

Gamma
Programador
Verificación en dos pasos activada
Desde
20 Jun 2013
Mensajes
439
Yo uso está funcion:

Insertar CODE, HTML o PHP:
function filtro($cadena){
/*
	Filtra los datos para evitar Inyecciones Sql
*/
	return addslashes(mysql_real_escape_string(trim($cadena)));
}

Gracias por compartir la función que utilizas.
 

Roodaka

Beta
Programador
¡Usuario con pocos negocios! ¡Utiliza siempre saldo de Forobeta!
Desde
8 Feb 2013
Mensajes
121
Por favor, ten en cuenta 📝 que si deseas hacer un trato 🤝 con este usuario, está baneado 🔒.
Yo hace un tiempo (en colaboración con un amigo) desarrollé una pequeña pero segura clase para evitar ésta clase de problemas..
Pueden ver el repositorio público donde hay algunos ejemplos de uso
PHP:
<?php
class LittleDB
 {
  // Devuelve el objeto db
  public static $instance;

  // contiene los datos de coneccion
  public $conn = null;

  // IP o Host de la Base de datos
  protected $host = null;

  // Usuario del Servidor
  protected $user = null;

  // Contraseña del Servidor
  protected $pass = '';

  // Nombre de la Base de datos
  protected $db = null;

  // Funcion de registro
  protected $logger = null;

  // Prefijo de la base de datos
  public $prefix = '';

  // Cantidad de consultas realizadas
  public $count = 0;



  /**
   * Constructor de la clase
   * @param string $host Url o DNS del Servidor MySQL
   * @param string $user Usuario del servidor
   * @param string &pass Contraseña del servidor
   * @param string $db Nombre de la base de datos
   * @param array $logger Función para el registro de datos
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  public function __construct($host, $user, $pass, $db, $logger = null)
   {
    $this->host = $host;
    $this->user = $user;
    $this->pass = $pass;
    $this->db = $db;
    $this->logger = $logger;
   } // public function __construct();



  /**
   * No se permite clonar el objeto.
   * @author Ignacio Daniel Rostagno <ignaciorostagno@vijona.com.ar>
   */
  public function __clone() { }



  /**
   * No se permite serealizar el objeto.
   * @author Ignacio Daniel Rostagno <ignaciorostagno@vijona.com.ar>
   */
  public function __wakeup() { }



  /**
   * Destruir & Desconectar la clase
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  public function __destruct()
   {
    if($this->conn !== null) { return mysqli_close($this->conn); }
    else { return false; }
   } // public function __destruct()



  /**
   * Obtenemos una instancia de la clase, la primera vez pasamos los parametros para conectar.
   * @return object $instance Instancia de la base de datos.
   * @author Ignacio Daniel Rostagno <ignaciorostagno@vijona.com.ar>
   */
  public static function get_instance($host = NULL, $user = NULL, $pass = NULL, $db = NULL)
   {
    if(!isset(self::$instance))
     {
      self::$instance = new LittleDB($host, $user, $pass, $db);
     }
    return self::$instance;
   } // public static function getInstance();



  /**
   * Conectar al servidor y seleccionar la DB
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  public function connect()
   {
    if($this->is_connected() === false)
     {
      $this->conn = mysqli_connect($this->host, $this->user, $this->pass, $this->db) or exit('No se ha podido conectar al servidor.');
     }
   } // public function connect();



  /**
   * Checkear si está conectado al servidor
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  private function is_connected()
   {
    if($this->conn !== null && is_object($this->conn)) { return true; }
    else { return false; }
   }



  /**
   * Procesar una consulta en la base de datos
   * @param string $cons Consulta SQL
   * @param miexed $values Arreglo con los valores a reemplazar por Parse_vars
   * @param boolean $ret retornar Array de datos o no.
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  public function query($cons, $values = null, $ret = false)
   {
    if($this->is_connected() == true) // Chequeamos que esté conectado
     {
      if($values != null) // Si tenemos valores para parsear
       {
        $query = $this->parse_vars($cons, $values);
       }
      else { $query = $cons; }
      if($ret == true) // Si debemos retornar el resultado...
       {
        $res = $this->_query($query);
        if($res->num_rows !== 0)
         {
          $return = $res->fetch_assoc();
          $res->free();
         }
        else { $return = false; }
       }
      else
       {
        $return = new Query($query, $this->conn);
        ++$this->count;
       }
      return $return;
     }
    else { return false; }
   } // public function query();



  /**
   * Insertar Datos en una tabla
   * @param table Nombre de la tabla
   * @param array Arreglo con los campos y valores; 'campo' => 'valor'
   * @param return Retornar o no el ID del insert.
   * @author Cody Roodaka <roodakazo@hotmail.com>
   * @return int|boolean
   */
  public function insert($table, $array, $return = false)
   {
    if(is_array($array) && $table != null && $this->is_connected() == true)
     {
      $fields = implode(', ', array_keys($array));
      $values = $this->parse_input($array);
      $query = $this->_query('INSERT INTO '.$this->db.'.'.$this->prefix.$table.' ( '.$fields.' ) VALUES ( '.$values.' )');

      // Seteamos el resultado,
      if($return == true) { return (int) $this->conn->insert_id; }
      else
       {
        if(!$query || $query == false) { return false; }
        else { return true; }
       }
     }
    else { return false; }
   } // public function insert();




  /**
   * Borrar una fila
   * @param table nombre de la tabla
   * @param where  Condicionantes
   * @param return Retornar nro de filas afectadas
   * @author Cody Roodaka <roodakazo@hotmail.com>
   * @return boolean
   */
  public function delete($table, $cond = array(), $return = false)
   {
    if(is_array($cond) == true && $this->is_connected() == true)
     {
      $where = array();
      foreach($cond as $key => $value)
       {
        $where[] = $key.' = '.$this->parse_input($value);
       }
      $conditions = implode(' && ', $where);
      $query = $this->_query('DELETE FROM '.$this->db.'.'.$this->prefix.$table.' WHERE '.$conditions);
      if($return == true) { return (int) $this->conn->affected_rows; }
      else
       {
        if(!$query || $query == false) { return false; }
        else { return true; }
       }
     } else { return false; }
   } // public function delete();



  /**
   * Actualizar una fila
   * @param table nombre de la tabla
   * @param where  Condicionantes
   * @param return Retornar nro de filas afectadas
   * @return mixed Resultado
   * @author Cody Roodaka <roodakazo@hotmail.com>
   * @return int|boolean
   */
  public function update($table, $array = array(), $cond = array(), $return = false)
   {
    if(is_array($cond) == true && $table != null)
     {
      $fiel = array();
      foreach($array as $field => $value)
       {
        $fiel[] = $field.' = '.$this->parse_input($value);
       }
      $fields = implode(', ', $fiel);
      $wher = array();
      foreach($cond as $field => $value)
       {
        $wher[] = $field.' = '.$this->parse_input($value);
       }
      $where = implode(' && ', $wher);
      $query = $this->_query('UPDATE '.$this->db.'.'.$this->prefix.$table.' SET '.$fields.' WHERE '.$where);
      if($return == true) { return (int) $this->conn->affected_rows; }
      else
       {
        if(!$query || $query == false) { return false; }
        else { return true; }
       }
     } else { return false; }
   } // public function update();



  /**
   * Ejecutamos una consulta
   * @param resource query Cosulta SQL
   * @return mixed recurso
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  private function _query($query)
   {
    ++$this->count;
    if($this->logger !== null)
     {
      call_user_func_array($this->logger, array($query));
     }
    return mysqli_query($this->conn, $query);
   } // private function _query()



  public function error()
   {
    return mysqli_error($this->conn);
   }


  /**
   * Funcion encargada de realizar el parseo de la consulta SQL agregando las
   * variables de forma segura mediante la validacion de los datos.
   * En la consulta se reemplazan los ? por la variable en $params
   * manteniendo el orden correspondiente.
   * @param string $q Consulta SQL
   * @param array $params Arreglo con los parametros a insertar.
   * @author Ignacio Daniel Rostagno <ignaciorostagno@vijona.com.ar>
   */
  protected function parse_vars($q, $params)
   {
    // Si no es un arreglo lo convertimos
    if(!is_array($params)) { $params = array($params); }
    //Validamos que tengamos igual numero de parametros que de los necesarios.
    if(count($params) != preg_match_all("/\?/", $q, $aux))
     {
      throw new Exception('No coinciden la cantidad de parametros necesarios con los provistos en '.$q);
     }
    //Reemplazamos las etiquetas.
    foreach($params as $param)
     {
      $q = preg_replace("/\?/", $this->parse_input($param), $q, 1);
     }
    return $q;
   } // protected function parse_vars();


  /**
   * Función que se encarga de determinar el tipo de datos para ver si debe
   * aplicar la prevención de inyecciones SQL, si debe usar comillas o si es
   * un literal ( funcion SQL ).
   * @param mixed $objet Objeto a analizar.
   * @return string Cadena segura.
   * @author Ignacio Daniel Rostagno <ignaciorostagno@vijona.com.ar>
  */
  protected function parse_input($object)
   {
    if(is_object($object)) { return (string) $object; } //Es un objeto?
    elseif(is_int($object)) { return (int) $object; } // Es un número?
    elseif($object === NULL) { return 'NULL'; } // Es nulo?
    elseif(is_array($object))
     { //Es un arreglo?
      $object = array_map(array($this, 'parse_input'), $object);
      return implode(', ', $object);
     }
    else
     { //Suponemos una cadena
      return '\''.mysqli_real_escape_string($this->conn, $object).'\'';
     }
   } // protected function parse_input();
 } // class LittleDB();

// =============================================================================

class Query
 {
  // Consulta
  protected $query = false;
  // Resultado de la consulta
  protected $result = array();



  /**
   * Inicializar los datos
   * @param $query Consulta SQL
   * @param $conn Recurso de conección SQL
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  public function __construct($query, $conn)
   {
    $cons = mysqli_query($conn, $query);
    if(is_object($cons)) { $this->query = $cons; return true; }
    else { return false; }
   } // function __construct();



  /**
   * Devolvemos el array con los datos de la consulta
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  public function fetchrow()
   {
    return $this->query->fetch_assoc();
   } // public function fetchrow();



  /**
   * Devolvemos la cantidad de filas afectadas por la consulta
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  public function numrows()
  {
   return $this->query->num_rows;
  } // public function numrows();



  /**
   * Obtenemos una columna específica de una consulta
   * @param string $field Columna seleccionada
   * @param string $default Resultado por defecto
   * @return string Resultado
   * @author Cody Roodaka <roodakazo@hotmail.com>
   */
  public function get($field, $default)
   {
    $result = $this->query->fetch_array(MYSQL_ASSOC);
    if(isset($result[$field])) { return $result[$field]; }
    else { return $default; }
   } // public function get();



  /**
   * Cuando destruimos el objeto limpiamos la consulta.
   * @author Ignacio Daniel Rostagno <ignaciorostagno@vijona.com.ar>
   */
  public function __destruct()
   {
    if(is_resource($this->query))
     {
      $this->query->free();
     }
   } // public function __destruct();

 } // class Query
 

webmaaron

VIP
1
Zeta
Programador
Verificado
Verificación en dos pasos activada
Verificado por Whatsapp
¡Ha verificado su Paypal!
Verificado por Binance
Suscripción a IA
Desde
17 May 2013
Mensajes
1.531
Yo uso está funcion:

Insertar CODE, HTML o PHP:
function filtro($cadena){
/*
	Filtra los datos para evitar Inyecciones Sql
*/
	return addslashes(mysql_real_escape_string(trim($cadena)));
}

Una muy parecida uso yo, si me acuerdo, el lunes cuando este en mi casa la colocaré tambien :)
 

Joscplan

Gamma
Programador
Verificación en dos pasos activada
Desde
20 Jun 2013
Mensajes
439
Gracias por compartir, se agradece para que tengan más ejemplos de como protegerse ante inyecciones.
 
Arriba