Foros del Web » Programando para Internet » PHP »

[Aporte] Seguridad básica en PHP

Estas en el tema de [Aporte] Seguridad básica en PHP en el foro de PHP en Foros del Web. Cita: Muy Importante Primero que nada, aclarar que no hay sistema 100% seguro y que esto es sólo una guía para tratar de evitar ataques ...
  #1 (permalink)  
Antiguo 06/09/2012, 05:42
Avatar de Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 15 años, 7 meses
Puntos: 2237
[Aporte] Seguridad básica en PHP

Cita:
Muy Importante


Primero que nada, aclarar que no hay sistema 100% seguro y que esto es sólo una guía para tratar de evitar ataques y cualquier comentario, sugerencia o añadidura será bienvenida.

Por favor, no copies y pegues el código esperando que funcione, todo lo hice en el bloc de notas y, seguramente, habrá muchos errores; analiza, entiende y adapta lo que creas que te pueda servir.

(Estas notas se repiten al final del aporte)
En FDW -> PHP se ha tratado muchas veces este tema, constantemente hay usuarios (nuevos o asiduos) que tienen dudas sobre cómo hacer sus scripts más seguros, por eso voy a plantear los problemas más comunes al respecto.

La regla más importante al desarrollar en cualquier lenguaje es: No confiar en los usuarios, porque los hay inexpertos y/o malintencionados que pueden lograr que las cosas no salgan como las tenemos previstas.

Primera Parte -> La directiva register_globals

A pesar de que desde PHP 4.2.0 y posteriores esta directiva se encuentra desactivada, todavía he encontrado algunos servidores que la tienen activa, pero no es lo peor, ya que al solicitar que la desactiven, me dicen que es una configuración por defecto y así se queda para todos los sitios, pudiendo incluir un php.ini en cada carpeta donde desee desactivarla.

Tener activa register_globals no es una vulnerabilidad, más bien un "riesgo calculado" que debemos evitar, porqué?

Hay muchos usuarios que no inicializan correctamente sus variables y, peor aún, tampoco verifican el origen de las mismas, entonces, supongamos que tenemos lo siguiente:

Código PHP:
Ver original
  1. <?php
  2. if($logueado) {
  3.     // Incluir script para usuarios
  4. } else {
  5.     // Incluir script para iniciar sesión
  6. }
  7.  
  8. /* se recomienda omitir el uso de ?> para evitar problemas relacionados
  9.     con encabezados tocaremos luego ese tema
  10.     En su lugar colocaremos una línea como la siguiente, sólo por referencia */
  11.  
  12. // Fin de archivo
Si no inicializamos nuestras variables y hacemos una correcta verificación de las mismas, al tener register_globals en on, un usuario podría ingresar a script.php?logueado=1, obteniendo acceso como usuario.

Cómo evitamos este problema?
- Lo primero es desactivar register_globals
- Lo segundo es inicializar correctamente nuestras variables

Código PHP:
Ver original
  1. $logueado = false;
  2. if(isset($_SESSION['logueado']) && $_SESSION['logueado'] == true) {
  3.     // Aquí debemos hacer otras validaciones
  4.     // que trataremos posteriormente
  5. }

Otro ejemplo:

Código PHP:
Ver original
  1. <?php
  2. include "$ruta/script.php";
  3.  
  4. // Fin de archivo
Con eso nos exponemos a que un usuario malintencionado logre tener acceso a scripts locales o remotos, lo que implicaría riesgos de robo de información o ataques a visitantes de nuestra web.

Cómo evitamos este problema?
- Nunca vamos a incluir un archivo que provenga de una variable externa
- En su lugar, creamos un arreglo con los archivos que podrán incluirse, un ejemplo de esto lo pueden ver en: http://www.forosdelweb.com/f18/aport...ulares-681437/

Qué hacer si register_globals está activa y no la podemos desactivar?
- Usamos al principio de nuestro script este código que encontré en: http://kohanaframework.org/3.1/guide/api/Kohana_Core

Código PHP:
Ver original
  1. function globals() {
  2.     if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS'])) {
  3.         // Prevent malicious GLOBALS overload attack
  4.         echo "Global variable overload attack detected! Request aborted.\n";
  5.  
  6.         // Exit with an error status
  7.         exit(1);
  8.     }
  9.  
  10.     // Get the variable names of all globals
  11.     $global_variables = array_keys($GLOBALS);
  12.  
  13.     // Remove the standard global variables from the list
  14.     $global_variables = array_diff($global_variables, array(
  15.         '_COOKIE',
  16.         '_ENV',
  17.         '_GET',
  18.         '_FILES',
  19.         '_POST',
  20.         '_REQUEST',
  21.         '_SERVER',
  22.         '_SESSION',
  23.         'GLOBALS',
  24.     ));
  25.  
  26.     foreach ($global_variables as $name) {
  27.         // Unset the global variable, effectively disabling register_globals
  28.         unset($GLOBALS[$name]);
  29.     }
  30. }

Hay varias cosas más sobre register_globals, pero si la desactivamos nos evitaremos muchos dolores de cabeza.

Segunda Parte -> Nuestras variables

Siempre tenemos que inicializar las variables antes de usarlas y también validarlas si pueden provenir de fuentes manipulables ($_GET, $_POST, $_COOKIE, etc.). Vamos directamente con ejemplos:

Código PHP:
Ver original
  1. // Siempre asignamos un valor por defecto que
  2. // nos permitirá tener mejor control
  3. $usuario = '';
  4. $id = 0;
  5. $numerico = 0;
  6.  
  7. if(isset($_GET['usuario'])) {
  8.     // Los datos de variables externas, por defecto son string
  9.     // Aplicamos las funciones que convengan para "sanear" y evitar ataques
  10.     $usuario = htmlentities($usuario);
  11. }
  12.  
  13. if(isset($_GET['id'])) {
  14.     // Forzamos el tipo para obtener un entero, es lo que esperamos
  15.     $id = (int) $_GET['id'];
  16. }
  17.  
  18. if(isset($_POST['numerico'])) {
  19.     // Si la variable puede ser entero, flotante, doble, etc.
  20.     // podemos asegurarnos de que sea numérico
  21.     // simplemente multiplicando por 1
  22.     $numerico = $_POST['numerico'] * 1;
  23. }
  24.  
  25. if($usuario != '') {
  26.     // Aquí debemos verificar que lo que se tecleó sea correcto
  27.     // comparando con nuestra fuente de datos (archivo, constante, variable, BDD, etc.)
  28. }
  29.  
  30. if($id > 0) {
  31.     // Ejecutamos las acciones necesarias para verificar la ID
  32.     // que, puede ser de un usuario, noticia, evento, etc.
  33. }
  34.  
  35. if($numerico > 0) {
  36.     // Ejecutamos las operaciones necesarias
  37. }

Dirán que soy maniático, pero evitaremos el uso de $_REQUEST, porque incluye variables provenientes de $_GET, $_POST y $_COOKIE y es preferible saber el origen de cada una de ellas; por ejemplo, si esperamos obtener 'usuario' desde $_POST y está en $_GET o $_COOKIE, tal vez se trata de un intento de ataque.

Por supuesto, habrá ocasiones en que alguna variable pueda aparecer en $_GET o $_POST, dependiendo de la acción que se vaya a realizar, pero deberíamos tener if's anidados para obtenerlas, ejemplo:

Código PHP:
Ver original
  1. // Acciones válidas
  2. $acciones = array('', 'modificar', 'ver'); // Faltarían 'agregar' y 'eliminar'
  3.  
  4. // Podemos inicializar la variable con asignación ternaria
  5. // es lo mismo que el ejemplo anterior, pero en una sóla línea
  6. $accion = (isset($_GET['accion'])) ? $_GET['accion'] : '';
  7.  
  8. // Esta la dejamos pendiente, hasta saber qué vamos a hacer
  9. $id = 0;
  10.  
  11. // Verificamos si la acción es correcta
  12. if( ! in_array($accion, $acciones)) {
  13.     // La acción seleccionada no es válida
  14.     // Podemos enviar un mensaje de error (ideal)
  15.     // O establecer la acción por defecto
  16.     $accion = '';
  17. }
  18.  
  19. if($accion == '') {
  20.     // No necesitamos ID
  21.     // Tal vez sólo mostraremos un listado de productos
  22. } else if($accion == 'ver') {
  23.     // ID debería estar en $_GET
  24.     $id = (isset($_GET['id'])) ? (int) $_GET['id'] : 0;
  25.     if($id == 0) {
  26.         die('No sabemos qué es lo que vamos a buscar');
  27.     }
  28.     // Lees de base de datos, archivo, etc.
  29.     // Muestras el elemento (producto, fotografía, noticia, etc.)
  30. } else if($accion == 'modificar') {
  31.     // ID debería estar en $_POST (?)
  32.     $id = (isset($_POST['id'])) ? (int) $_POST['id'] : 0;
  33.     if($id == 0) {
  34.         die('No sabemos qué es lo que vamos a buscar');
  35.     }
  36.     // Lees de base de datos, archivo, etc.
  37.     // Muestras el formulario para modificar el elemento
  38. }

Tercera Parte -> Ataques XSS

Si no saneamos todas las variables manipulables por el usuario, corremos el riesgo de que incluyan códigos que permiten atacar el sitio y a nuestros visitantes.

Para esto, usaremos al principio de nuestros scripts la función compartida por GatorV en: http://www.forosdelweb.com/f18/aport...ar-xss-948577/

Código PHP:
Ver original
  1. // Función recursiva que permite limpiar variables y/o arreglos
  2. function limpiaVariables($data) {
  3.     if(is_array($data)) {
  4.         foreach($data as $key => $val) {
  5.             $data[$key] = limpiarVariables($val);
  6.         }
  7.     } else {
  8.         $data = filterXSS($data);
  9.     }
  10.     return $data;
  11. }
  12.  
  13. // Limpiamos las entradas manipulabes por el usuario
  14.  
  15. $_GET = limpiarVariables($_GET);
  16. $_POST = limpiarVariables($_POST);
  17. $_COOKIE = limpiarVariables($_COOKIE);

Adicionalmente, también podemos convertir caracteres especiales a sus respectivas entidades html usando:

Código PHP:
Ver original
  1. // Convertimos caracteres como <, >, &, " a sus entidades HTML
  2. $variable = htmlentities($variable);

O bien, si tenemos un campo donde el usuario puede introducir código HTML, podemos sanearlo usando alguna de las opciones mencionadas en: http://www.forosdelweb.com/f18/aport...-html-1007806/

-- Continuará --
__________________
- León, Guanajuato
- GV-Foto

Última edición por Triby; 06/09/2012 a las 05:48
  #2 (permalink)  
Antiguo 06/09/2012, 05:42
Avatar de Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 15 años, 7 meses
Puntos: 2237
Respuesta: [Aporte] Seguridad básica en PHP

Cuarta Parte -> Subiendo archivos

Hace algún tiempo, un usuario creó un tema para debatir sobre los riesgos que hay al subir imágenes al servidor y la posibilidad de ejecutar código PHP malicioso por ese medio: http://www.forosdelweb.com/f18/ataqu...agenes-964823/

Pasaremos este tema por alto, ya que la solución es sencilla y la vimos en la primera parte: "Jamás incluir un archivo sin saber de qué se trata"; aparte, si validamos correctamente la subida, no nos tenemos que preocupar por estas cosas raras.

Lo primero que tenemos que hacer al subir archivos a nuestro servidor es asegurarnos de que se trata de una extensión permitida, después, de ser posible, verificamos que el contenido corresponda a esa extensión y, finalmente, nunca lo vamos a usar en una sentencia include o require.

Por supuesto, puede haber proyectos en los que tengamos que subir archivos o scripts ejecutables (php, js, html, etc.) y nunca debemos dejarlos accesibles desde URL, forzándolos para descarga cuando sean solicitados.

Cita:
Iniciado por Sugerido por Abimaelrc
Usando la librería Fileinfo podemos obtener más información de los archivos y, en base a eso, determinar las acciones a realizar.
Código PHP:
Ver original
  1. // Si vamos a subir imágenes, tenemos que saber cuáles permitir
  2. $extensiones = array('jpg', 'jpeg', 'gif', 'png', 'bmp');
  3.  
  4. // Verificamos si existe el campo de archivo de nuestro formulario
  5. if( ! isset($_FILES['imagen'])) {
  6.     // Ups!, no hay archivo
  7.     die('No modifiques mi formulario, por favor.');
  8. }
  9.  
  10. // Ahora revisamos que realmente se subió un archivo
  11. if($_FILES['imagen']['error'] == 4) {
  12.     // El error 4 corresponde a campo de archivo vacío
  13.     die('Selecciona un archivo de tu PC para subir.');
  14. }
  15. if($_FILES['imagen']['error'] == 0) {
  16.     // El error 0 quiere decir que se subió el archivo correctamente
  17.     // Obtenemos la extensión, en minúsculas para poder comparar
  18.     // Si no sabes que hacen strtolower(), end() o alguna otra función... consulta el manual de PHP
  19.     $extension = strtolower(end(explode('.', $_FILE['imagen']['name'])));
  20.  
  21.     // verificamos que sea una extensión permitida
  22.     if( ! in_array($extension, $extensiones)) {
  23.         die('Sólo se permite subir archivos con estas extensiones: ' . implode(', ', $extensiones);
  24.     }
  25.  
  26.     // Ahora, con la librería GD verificamos la imagen (o podrías usar imagick)
  27.     if($extension == 'jpg' || $extension == 'jpeg') {
  28.         $imagen = createimagefromjpeg($_FILE['imagen']['tmp_name']);
  29.     } else if($extension == 'gif') {
  30.         $imagen = createimagefromgif($_FILE['imagen']['tmp_name']);
  31.     }
  32.     // Haz lo mismo para png, bmp o cualquier otro archivo de imagen
  33.     // Consulta el manual de PHP -> GD para saber más
  34.  
  35.     // Ahora verificamos que el archivo sea realmente una imagen
  36.     $datos = getimagesize($imagen);
  37.  
  38.     // Si la función falla, devolverá falso y, aunque podría ser una imagen real que no se reconoció
  39.     // correctamente, mejor consideramos que se trata de un intento de ataque :borracho:
  40.     if($datos === false) {
  41.         // Nótese el triple signo igual, para comparar valor y tipo
  42.         die('No se pudo subir la imagen, intenta con otro formato.');
  43.     }
  44.  
  45.     // Ok, aquí movemos la imagen a su destino final con move_uploaded_file()
  46.     // Y, si corresponde, actualizamos base de datos
  47.     // Ojo, hay que revisar si se va a sobreescribir un archivo
  48.     // y crear la lógica para renombrar en caso de ser necesario
  49. } else {
  50.     // Más info sobre códigos de error subiendo
  51.     // archivos en el manual de PHP
  52.     die('Hubo un error subiendo la imagen.');
  53. }

Como mencionaba, si vamos a subir archivos que puedan ejecutarse, los guardamos en una ruta no disponible desde URL y forzamos a que sean descargados: http://www.forosdelweb.com/f18/forza...2/#post2303479

Quinta Parte -> Acceso a datos importantes

Hace algunos ayeres, se tenía la costumbre de guardar la configuración del sitio en archivos con extensiones diferentes de php, tal vez con la finalidad de identificarlos fácilmente, pero esa práctica es un error, ya que los archivos con extensiones no registradas como aplicación podrían mostrarse como archivos de texto o quedar disponibles para descarga.

Entre otras extensiones, solía usarse: inc, dat, cfg, txt, etc.; a PHP no le importa la extensión del archivo, si tiene código PHP válido, lo ejecutará.

De hecho, todavía existen aplicaciones prefabricadas que incurren en esta mala práctica y por ahora no recuerdo alguna.

Supongamos que tenemos un script para guardar la configuración del sitio, llamado config.inc

Código PHP:
Ver original
  1. <?php
  2. $dbhost = 'localhost';
  3. $dbuser = 'usuario';
  4. $dbpass = 'contraseña';
  5.  
  6. $db = mysqli_connect($dbhost, $dbuser, $dbpass);
  7.  
  8. // Fin de archivo

Si un usuario malintencionado coloca en la barra de direcciones del navegador sitio.com/config.inc, obtendrá información que le dará acceso a la base de datos.

Cómo soluciono este problema?
- Registra la extensión inc como ejecutable de PHP (no recomendado)
- Modifica tu .htaccess para evitar que los archivos .inc sean accesibles desde URL (no recomendado)
- NUNCA uses otras extensiones para tus scripts PHP!!!
- En tu servidor de producción debes desactivar todos los mensajes de error, ya que estos podrían revelar información de estos archivos. Si siempre usas la extensión php, codificas correctamente y validas todas tus variables, nunca verás mensajes de error, excepto cuando haya problemas en el servidor.

Otra medida que podemos tomar es evitar acceso por URL a scripts que están pensados exclusivamente para ser incluidos, como el del ejemplo anterior o uno donde tenemos funciones comunes para todo el sitio, etc.

Sólo tenemos que definir una constante (de preferencia) o una variable para saber si el ingreso a los scripts es válido

Código PHP:
Ver original
  1. <?php
  2. // index.php - Nuestra página principal
  3.  
  4. // Establecemos la constante
  5. define('MI_PROYECTO', 1);
  6.  
  7. // Cargar info y establecer conexión a base de datos
  8. include 'config.php';

Código PHP:
Ver original
  1. <?php
  2. // config.php y cualquier otro script que no deba accederse por URL
  3.  
  4. if( ! defined('MI_PROYECTO')) {
  5.     die('No tienes acceso a esta carpeta o archivo.');
  6. }

Igualmente, debes evitar que se liste el contenido de tus carpetas, ya sea creando un index.php vacío en cada carpeta o modificando el .htaccess en la raíz del sitio agregando la siguiente línea:

Código APACHE:
Ver original
  1. Options -Indexes

Cita:
Iniciado por Sugerido por Abimaelrc
También es recomendable que archivos importantes estén en una ruta no accesible desde web, por ejemplo:

misitio.com está hospedado en un sistema linux donde la carpeta de usuario es /home/misitio y la carpeta pública (web) es /home/misitio/public_html, entonces, podemos crear una carpeta en el mismo nivel de public_html para almacenar estos archivos, que serían accesibles por script (PHP) pero no directamente desde un navegador, ejemplo: /home/misitio/config

Sexta Parte -> Funciones peligrosas

Directivas include, include_once, require y require_once - Se usan para incluir e interpretar scripts PHP.

Por si todavía no ha quedado claro, nunca se deben incluir archivos que no sabemos su procedencia o mediante variables que no fueron verificadas apropiadamente; así mismo, debemos evitar incluir archivos de otros sitios (include 'http://www.ejemplo.com/script.php';), a menos que sean de fuentes confiables.

eval() - El manual de PHP para esta función hace la siguiente advertencia:

Cita:
Precaución

El constructor de lenguaje eval() es muy peligroso porque permite la ejecución de código de PHP arbitrario. Su uso está totalmente desaconsejado. Si se ha verificado cuidadosamente que no existe otra opción que usar este constructor, se ha de poner especial atención en no pasar ninguna información proporcionada por el usuario a esta función sin haberla validado apropiadamente con anterioridad.

Enlace: http://www.php.net/eval

exec(), system() y shell_exec() - Ejecutar comandos.

Básicamente, shell_exec() permite ejecutar comandos directamente relacionados con el sistema operativo, como obtener listado de archivos y carpetas, mientras que exec() y system() permiten ejecutar cualquier comando.

Más info en: http://www.php.net/exec

No voy a abarcar mucho sobre este tema porque es muy extenso, me voy a limitar a decir que no se deben usar a menos que sea estrictamente necesario y sepas lo que haces. En mi caso, sólo he usado exec() porque debo convertir videos a diferentes formatos mediante ffmpeg y nunca se debe incluir información manipulable por el usuario.

Seguramente hay otras directivas y funciones que comprometen la seguridad del sitio o del servidor mismo al ser usadas sin buena planeación, agrega un comentario si sabes (o tienes duda) de alguna.

-- Continuará --
__________________
- León, Guanajuato
- GV-Foto

Última edición por Triby; 13/12/2012 a las 17:29
  #3 (permalink)  
Antiguo 06/09/2012, 05:43
Avatar de Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 15 años, 7 meses
Puntos: 2237
Respuesta: [Aporte] Seguridad básica en PHP

Séptima Parte -> Inyección SQL

Cita:
NOTA IMPORTANTE:


El uso de la librería mysql está desaconsejado, por lo que nos basaremos exclusivamente en mysqli y PDO.

Más info: http://www.forosdelweb.com/f18/anunc...ecada-1008145/

Si usas un motor de base de datos diferente de MySQL, revisa el manual de PHP para la librería correspondiente, donde encontrarás las funciones adecuadas para evitar ataques.
Por seguridad y para evitar errores, los datos a incluir en una consulta SQL deben ser "escapados" o "preparados".

Veamos la sintáxis de algunas consultas comunes:

Código MySQL:
Ver original
  1. SELECT * FROM tabla WHERE cadena = 'Este valor es una cadena';
  2. INSERT INTO tabla SET cadena = 'Este valor es una cadena';
  3. INSERT INTO tabla (cadena) VALUES('Este valor es una cadena');
  4. UPDATE tabla SET cadena = 'Este valor es una cadena' WHERE id = 5;

Supongamos que 'Este valor es una cadena' lo enviamos por medio de la variable $cadena y que podemos obtener el valor a través de un formulario o de otra consulta a la base de datos, lo mismo para el número 5 usado en id.

Si los valores son como en el ejemplo, no tendremos problemas para ejecutar la consulta, pero, qué pasa si tenemos algo como:

Código PHP:
Ver original
  1. $cadena "Esta cadena contiene ' comilla simple";
  2. $id = 'número';

Las consultas quedarían así:

Código MySQL:
Ver original
  1. SELECT * FROM tabla WHERE cadena = 'Esta cadena contiene ' comilla simple';
  2. INSERT INTO tabla SET cadena = 'Esta cadena contiene ' comilla simple';
  3. INSERT INTO tabla (cadena) VALUES('Esta cadena contiene ' comilla simple');
  4. UPDATE tabla SET cadena = 'Este valor es una cadena' WHERE id = 'numero';

Este es el menos malo de los escenarios, porque al tratar de ejecutar la consulta sólo obtendremos un error de sintáxis porque:
- La comilla simple hace que termine el valor de cadena y lo que viene después es "basura" en la consulta
- 'número' no se puede asignar a id porque se trata de un campo numérico

Lo de la variable $id se puede solucionar fácilmente como vimos antes, forzando a que sea entero y validando, pero, si tenemos esto:

Código PHP:
Ver original
  1. $cadena = "cualquier cosa'; INSERT INTO usuarios (usuario, password, rol) VALUES ('Triby', 'contraseña', 'admin'); #";

La primera consulta UPDATE quedaría así (los saltos de línea son para verlo mejor):

Código MySQL:
Ver original
  1. UPDATE tabla SET cadena = 'cualquier cosa';
  2. INSERT INTO usuarios (usuario, password) VALUES ('Triby', 'contraseña');
  3. # WHERE id = 5

1- Se ejecuta UPDATE estableciendo el valor de cadena en 'cualquier cosa' para todos los registros de la tabla, la comilla simple corta el contenido del campo.
2- El punto y coma le indica al motor de base de datos que finalizó la primera consulta y continúa otra.
3- Se ejecuta INSERT INTO, registrando al usuario, obteniendo permisos de administrador.
4- El punto y coma separa la siguiente consulta que, por el signo de número (#) es un comentario y no se ejecuta.

Algunas soluciones posibles con mysqli:

NOTA: Mysqli por procedimientos también está considerado obsoleta, hay que usar orientado a objetos o PDO.

Código PHP:
Ver original
  1. // Recordar que previamente se deben limpiar todas las entradas
  2. // Para evitar ataques XSS y caracteres no permitidos
  3. $cadena = $_POST['cadena'];
  4.  
  5. // Usando mysqli estructurado
  6. // addslashes() no es suficiente, mejor usamos la función nativa de la librería
  7. $cadena = mysqli_real_escape_string($cadena);
  8. $sql = "SELECT * FROM tabla WHERE cadena = '$cadena'";
  9. $resultados = mysqli_query($sql);
  10.  
  11. // Usando mysqli orientado a objetos
  12. $mysqli = new mysqli('localhost', 'usuario', 'contraseña', 'base_de_datos');
  13. $cadena = $mysqli->real_escape_string($cadena);
  14. $sql = "SELECT * FROM tabla WHERE cadena = '$cadena'";
  15. $resultados = $mysqli->query($sql);
  16.  
  17. // Usando mysqli con consultas preparadas y parámetros enlazados
  18. $mysqli = new mysqli('localhost', 'usuario', 'contraseña', 'base_de_datos');
  19. $sql = "SELECT * FROM tabla WHERE cadena = ?";
  20. $stmt = $mysqli->prepare($sql);
  21. // Agregamos el valor de $cadena como string a la consulta preparada
  22. $stmt->bind_param('s', $cadena);
  23. $stmt->execute();

Hay otras opciones con mysqli y si requieres más info, ingresa en: http://www.php.net/manual/es/book.mysqli.php

Algunas soluciones posibles con PDO:

Código PHP:
Ver original
  1. // Recordar que previamente se deben limpiar todas las entradas
  2. // Para evitar ataques XSS y caracteres no permitidos
  3. $cadena = $_POST['cadena'];
  4.  
  5. // Info para conectar a MySQL
  6. $dbhost = 'mysql:dbname=pruebas;host=localhost';
  7. $dbuser = 'usuario';
  8. $dbpass = 'contraseña';
  9.  
  10. // Establecer conexión y seleccionar base de datos
  11. $db = new PDO($dbhost, $dbuser, $dbpass);
  12.  
  13. // Parámetros con nombre
  14. $sql = "SELECT * FROM tabla WHERE cadena = :cadena";
  15. $sth = $db->prepare($sql);
  16. $sth->execute(array(':cadena' => $cadena));
  17.  
  18. // Parámetros con signo de interrogación
  19. $sql = "SELECT * FROM tabla WHERE cadena = ?";
  20. $sth = $db->prepare($sql);
  21. $sth->execute(array($cadena));
  22.  
  23. // Enlazando parámetros
  24. $sql = "SELECT * FROM tabla WHERE cadena = :cadena";
  25. $sth = $db->prepare($sql);
  26. $sth->bindParam(':cadena', $cadena, PDO::PARAM_STR, 50);
  27. $sth->execute();

Como habrás podido notar, PDO con parámetros enlazados es la mejor opción, ya que te permite especificar el tipo y longitud del dato que se agregará a la consulta.

Hay otras opciones con PDO y si requieres más info, ingresa en: http://www.php.net/manual/es/book.pdo.php

Nota: Usando parámetros con nombre o con signo de interrogación, PDO trata todos los campos como cadenas.

-- Continuará --
__________________
- León, Guanajuato
- GV-Foto

Última edición por Triby; 06/02/2013 a las 13:02
  #4 (permalink)  
Antiguo 06/09/2012, 05:43
Avatar de Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 15 años, 7 meses
Puntos: 2237
Respuesta: [Aporte] Seguridad básica en PHP

Octava Parte -> Sesiones

La capacidad de PHP para manejar sesiones es una de las características que le han dado mayor auge, facilitando el conservar variables entre diferentes páginas de nuestro sitio y, como casi siempre, una validación precaria o nula es la causa de riesgos de seguridad.

Primero unas observaciones y recomendaciones:

1- Siempre debes tener en cuenta que antes de session_start(); no debes haber enviado otras salidas al navegador, esto incluye enviar encabezados, crear cookies, o mostrar cualquier caracter como salto de línea, espacio en blanco, letras y, mucho menos, código HTML.

2- Si vas a usar variables de sesión en tu sitio, asegúrate de tener siempre session_start(); al principio de tus scripts, no importa en qué momento las usarás o si hay secciones de tu sitio donde no se usarán. Esto te ayudará para que en cualquier momento puedas hacer modificaciones e incluir variables de sesión sin tener que revisar si ya incluiste la línea o no, aparte de evitarte problemas de encabezados.

3- Es recomendable "agrupar" tus variables de sesión, por ejemplo:

Código PHP:
Ver original
  1.  
  2. // Sin agrupar (no recomendable):
  3. $_SESSION['nombre'] = 'Triby';
  4. $_SESSION['rol'] = 'admin';
  5. $_SESSION['email'] = '[email protected]';
  6. $_SESSION['producto1'] = 'Disco duro SATA 520GB';
  7. $_SESSION['precio1'] = 850;
  8. $_SESSION['producto2'] = 'Pantalla LCD 19"';
  9. $_SESSION['precio2'] = 990;
  10.  
  11. // Agrupado (muy recomendable)
  12. $_SESSION['usuario'] = array(
  13.     'nombre' => 'Triby',
  14.     'rol' => 'admin',
  15.     'email' => '[email protected]'
  16. );
  17. $_SESSION['carrito'] = array(
  18.     array('Disco duro SATA 520GB', 850),
  19.     array('Pantalla LCD 19"', 990)
  20. );

Si tienes tus variables de sesión agrupadas en arreglos podrás manipularlas fácilmente y tus scripts serán más legibles.

4- Para eliminar variables de sesión sólo necesitas:

Código PHP:
Ver original
  1. // Borrar todos los datos de sesión
  2. // El manual de PHP recomienda eliminar también la cookie de sesión, tú eliges si lo haces
  3. // Opción 1
  4.  
  5. // Opción 2
  6. $_SESSION = array();
  7.  
  8. // Opción 3
  9. unset($_SESSION);
  10.  
  11. // Eliminar una variable específica
  12. unset($_SESSION['usuario']);
  13.  
  14. // También puedes eliminar solo un elemento dentro de 'usuario'
  15. unset($_SESSION['usuario']['rol']);

5- No crees variables de sesión indiscriminadamente, piensa el uso que les darás y si te serán útiles en otras páginas o tienes más alternativas. Esto no es algo muy crítico, de hecho, el único problema sería que entre scripts incluidos, constantes, variables, sesiones, consultas a base de datos, etc., satures la memoria asignada a PHP y el script se detenga.

Bien, ahora veamos los problemas de seguridad que, actualmente, es muy raro que se puedan manipular las sesiones, pero depende mucho de tus hábitos de programación.

Crear ID de sesión "personalizado" (Session fixation)

Esto sólo es posible por mala validación de scripts y propagar el id de sesión por URL. Supongamos que tenemos script.php:

Código PHP:
Ver original
  1.  
  2. if (!isset($_SESSION['visits'])) {
  3.     $_SESSION['visitas'] = 1;
  4. } else {
  5.     $_SESSION['visitas']++;
  6. }
  7.  
  8. echo $_SESSION['visitas'];

La primera vez que abres la página mostrará 1 y, conforme actualices o vuelvas a entrar, ese valor se irá incrementando. Supongamos que un usuario ingresa a script.php?PHPSESSID=ABC123 se creará una sesión con la id proporcionada y, si ingresa desde otro navegador, incluso desde otro equipo usando ese mismo parámetro, no verá el 1 que se espera, sino que continuará la suma donde se quedó en el navegador que usó al crear la id.

La mejor forma de evitar este problema es configurar PHP para que guarde la ID de sesión en cookies, que también pueden ser robadas, pero orientaremos nuestros esfuerzos a defender una "fuente más confiable" y tendremos URL's "sin basura".

Robo de sesión

Generalmente, los atacantes buscarán obtener una ID de sesión de otro usuario que les permita acceso a nuestro sitio, tanto mejor si el usuario en cuestión tiene permisos administrativos.

Cuando la ID de sesión se propaga por URL es mucho más fácil "robarla" ya que está a la vista. Guardar la id de sesión en cookies limita a los atacantes, ya que deben tener acceso al equipo para obtenerla, lo que puede ocurrir si el usario trabaja en equipos públicos o los comparte con otras personas y no tiene la precaución de cerrar su sesión (logout) después de haber navegado en el sitio.

Ya sea que el atacante pueda crear una sesión o robar la de otro usuario, la seguridad de nuestro sitio se verá comprometida sólo por una mala programación y/o que la cuenta tenga permisos suficientes para tener acceso a secciones administrativas.

En www.phpsec.org (de donde tomé parte de info para el aporte) sugieren "proteger" la sesión en base a $_SERVER['HTTP_USER_AGENT'], pero la información enviada por el navegador es tan manipulable que no creo conveniente mencionarlo, en la siguiente parte veremos qué podemos hacer al respecto.

Por cierto, hasta ahora, la forma más común de robo de cuentas no es en base a sesiones, sino a robo de correos mediante páginas que simulan ser de un servicio como hotmail; quién no ha "recibido" una postal de gusanito.com donde te piden correo y contraseña de hotmail?

Agrego punto muy importante:
Cita:
Iniciado por webankenovi Ver Mensaje
Configurar correctamente las directivas de las sessiones

ini_set('session.hash_function','whirlpool'); // md5 es el algoritmo por defecto

para no propagar el id por url

ini_set('session.use_trans_sid',0);
ini_set('session.use_cookies',1);
ini_set('session.use_only_cookies',1);

solo sera accesible la cookie desde http y no sera accesible para lenguajes de script

ini_set('session.cookie_secure',0);
ini_set('session.cookie_httponly',1);

-- Continuará --
__________________
- León, Guanajuato
- GV-Foto

Última edición por Triby; 13/12/2012 a las 17:39
  #5 (permalink)  
Antiguo 06/09/2012, 05:44
Avatar de Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 15 años, 7 meses
Puntos: 2237
Respuesta: [Aporte] Seguridad básica en PHP

Novena Parte -> Manejo de usuarios

Para tratar de proteger las sesiones es conveniente que en cada cambio de estado (login y logout) generemos una nueva id de sesión y eliminemos las variables que no serán necesarias:

Código PHP:
Ver original
  1.  
  2. // Ingreso de usuario
  3. function login($id, $usuario, $correo, $rol) {
  4.     // Se supone que ya verificamos que los datos son correctos
  5.     // Creamos una nueva id de sesión y eliminamos la anterior (true)
  6.  
  7.     // Creamos sólo las variables que necesitaremos
  8.     $_SESSION = array(
  9.         'usuario' => array(
  10.             'id' => $id,
  11.             'nombre' => $usuario,
  12.             'correo' => $correo,
  13.             'rol' => $rol
  14.         )
  15.     );
  16.     // Redirigimos a página de usuario
  17.     header('Location: bienvenido.php');
  18.     exit;
  19. }
  20.  
  21. // Finalizar sesión
  22. function logout() {
  23.     // Creamos una nueva id de sesión y eliminamos la anterior (true)
  24.  
  25.     // Eliminamos información de sesión
  26.     $_SESSION = array();
  27.  
  28.     // Redirigimos a página principal
  29.     header('Location: index.php');
  30.     exit;
  31. }

Aunque el uso de md5() no es tan aconsejable, podemos darle un poco más de confiabilidad agregando información y, de paso, nos ayudará tratando de hacer más segura nuestra sesión:
Cita:
Iniciado por IMPORTANTE
Es recomendable ver la sugerencia de Abimaelrc para reforzar la seguridad en este punto: http://www.forosdelweb.com/f18/aport...8/#post4330821
Código PHP:
Ver original
  1.  
  2. // Un pequeño agregado para usar en md5()
  3. DEFINE('SALT_MD5', 'AbC%"$%"');
  4.  
  5. // El usuario ya inició sesión?
  6. if(esUsuario()) {
  7.     // Ok, es una sesión válida
  8.     // Aquí el código para usuarios con sesión iniciada
  9. } else {
  10.     // No ha iniciado sesión
  11.     // Aquí el código para "invitados"
  12. }
  13.  
  14. // Obtener cadena de comprobación
  15. function strMd5($cadena) {
  16.     return md5(SALT_MD5 . $cadena . SALT_MD5);
  17. }
  18.  
  19. // Ingreso de usuario
  20. function login($id, $usuario, $correo, $rol) {
  21.  
  22.     // Creamos sólo las variables que necesitaremos
  23.     $_SESSION = array(
  24.         'usuario' => array(
  25.             'id' => $id,
  26.             'nombre' => $usuario,
  27.             'correo' => $correo,
  28.             'rol' => $rol,
  29.             'comprobacion' => strMd5("$id|$usuario|$correo|{$_SERVER['REMOTE_ADDR']}");
  30.         )
  31.     );
  32.     // Redirigimos a página de usuario
  33.     header('Location: bienvenido.php');
  34.     exit;
  35. }
  36.  
  37. // Finalizar sesión
  38. function logout() {
  39.     // Creamos una nueva id de sesión y eliminamos la anterior (true)
  40.  
  41.     // Eliminamos información de sesión
  42.     $_SESSION = array();
  43.  
  44.     // Redirigimos a página principal
  45.     header('Location: index.php');
  46.     exit;
  47. }
  48.  
  49. // Comprobar si es una sesión válida
  50. // Devuelve verdadero sólo si el usuario inició sesión y la cadena de comprobación es correcta
  51. // Si el usuario cambia de IP, entonces finalizará su sesión
  52. function esUsuario() {
  53.     if(isset($_SESSION['usuario'])) {
  54.         $cadena = "{$_SESSION['usuario']['id']}|{$_SESSION['usuario']['nombre']}|{$_SESSION['usuario']['correo']}|{$_SERVER['REMOTE_ADDR']}";
  55.         if(strMd5($cadena) == $_SESSION['usuario']['comprobacion']) {
  56.             // Sesión válida
  57.             return true;
  58.         }
  59.         // Sesión no válida, posiblemente es un ataque
  60.         // Cerramos sesión
  61.         logout();
  62.     }
  63.     return false;
  64. }

Nota 1: La dirección IP es tan poco confiable como la información del navegador, sin embargo, los datos añadidos en la cadena de comprobación harán que un atacante se tenga que esforzar más para obtener acceso al sitio.

Nota 2: Sí, lo sé, puede resultar molesto que la sesión caduque automáticamente cuando encendemos la computadora y regresamos al sitio, todo porque la IP cambió, entonces, esta estrategia podría ser usada sólo para usuarios con permisos administrativos.

Hay que tener en cuenta que la sesión podría cerrarse aún cuando el usuario tenga abierta la página, pero esté inactivo durante mucho tiempo; igualmente, debería haber la posibilidad de guardar los datos del usuario para mantener la conexión activa entre cada visita. En estos casos, debemos hacer uso de cookies, teniendo cuidado de guardar sólo información que nos permita obtener id de usuario y comparar con otros datos para saber que se trata de una sesión válida.

Más o menos así quedaría el script al final:


Código PHP:
Ver original
  1.  
  2. // Un pequeño agregado para usar en md5()
  3. DEFINE('SALT_MD5', 'AbC%"$%"');
  4.  
  5. // El usuario ya inició sesión?
  6. if(esUsuario()) {
  7.     // Ok, es una sesión válida
  8.     // Aquí el código para usuarios con sesión iniciada
  9. } else {
  10.     // No ha iniciado sesión
  11.     // Aquí el código para "invitados"
  12. }
  13.  
  14. // Obtener cadena de comprobación
  15. function strMd5($cadena) {
  16.     return md5(SALT_MD5 . $cadena . SALT_MD5);
  17. }
  18.  
  19. // Ingreso de usuario
  20. function login($id, $usuario, $correo, $rol) {
  21.  
  22.     // Creamos sólo las variables que necesitaremos
  23.     $_SESSION = array(
  24.         'usuario' => array(
  25.             'id' => $id,
  26.             'nombre' => $usuario,
  27.             'correo' => $correo,
  28.             'rol' => $rol,
  29.             'comprobacion' => strMd5("$id|$usuario|$correo|{$_SERVER['REMOTE_ADDR']}")
  30.         )
  31.     );
  32.     // Creamos cookie 'usuario' con id y cadena de comprobación
  33.     // Expira en un mes y está disponible en la raiz del sitio y todas las subcarpetas
  34.     setcookie('usuario', "$id|{$_SESSION['usuario']['comprobacion']}", time() + (86400 * 30), '/');
  35.  
  36.     // Redirigimos a página de usuario
  37.     header('Location: bienvenido.php');
  38.     exit;
  39. }
  40.  
  41. // Finalizar sesión
  42. function logout() {
  43.     // Creamos una nueva id de sesión y eliminamos la anterior (true)
  44.  
  45.     // Eliminamos información de sesión
  46.     $_SESSION = array();
  47.  
  48.     // Borramos info de cookie
  49.     setcookie('usuario', 'sin sesión', time() + 3600, '/');
  50.  
  51.     // Redirigimos a página principal
  52.     header('Location: index.php');
  53.     exit;
  54. }
  55.  
  56. // Comprobar si es una sesión válida
  57. // Devuelve verdadero sólo si el usuario inició sesión y la cadena de comprobación es correcta
  58. // Si el usuario cambia de IP, entonces finalizará su sesión
  59. function esUsuario() {
  60.     // Si no existe la variable de sesión
  61.     if( ! isset($_SESSION['usuario'])) {
  62.         // Entonces buscamos en cookie
  63.         $cookie = (isset($_COOKIE['usuario'])) ? $_COOKIE['usuario'] : 'sin sesión';
  64.         if($cookie != 'sin sesión' && $cookie != '') {
  65.             // Separamos ud y cadena de comprobación
  66.             $tmp = explode('|', $cookie);
  67.  
  68.             // Sólo será válida si son dos elementos
  69.             if(count($tmp) == 2) {
  70.                 $id = (int) $tmp[0];
  71.                 $sql = "SELECT id, nombre, correo, rol FROM usuarios WHERE id = $id";
  72.                 // Leemos la consulta con nuestra librería preferida
  73.  
  74.                 // Si los datos son válidos, creamos variables de sesión
  75.                 $_SESSION['usuario'] = array(
  76.                     'id' => $id,
  77.                     'nombre' => $usuario,
  78.                     'correo' => $correo,
  79.                     'email' => $email,
  80.                     'rol' => $rol,
  81.                     'comprobacion' => $tmp[1] // La cadena obtenida de cookie
  82.                 );
  83.             }
  84.         }
  85.     }
  86.  
  87.     if(isset($_SESSION['usuario'])) {
  88.         $cadena = "{$_SESSION['usuario']['id']}|{$_SESSION['usuario']['nombre']}|{$_SESSION['usuario']['correo']}|{$_SERVER['REMOTE_ADDR']}";
  89.         if(strMd5($cadena) == $_SESSION['usuario']['comprobacion']) {
  90.             // Sesión válida, renovamos cookie para mantenerlo conectado
  91.             setcookie('usuario', "$id|{$_SESSION['usuario']['comprobacion']}", time() + (86400 * 30), '/');
  92.  
  93.             return true;
  94.         }
  95.         // Sesión no válida, posiblemente es un ataque
  96.         // Cerramos sesión
  97.         logout();
  98.     }
  99.     // No hay sesión válida, nos aseguramos de que la cookie siga sin datos
  100.     setcookie('usuario', 'sin sesión', time() + 3600, '/');
  101.     return false;
  102. }

Últimas recomendaciones:

- Nunca guardar las contraseñas directamente en la base de datos, es recomendable codificarla con md5(), sha1() o cualquier otro método que no permita decodificar y agregando algunos caracteres como en el ejemplo anterior, que usamos la constante SALT_MD5.
- Si en el ejemplo anterior cambiamos la cadena de comprobación quitando el correo y agregando la contraseña codificada nos facilitaría comprobar si la cookie de usario es realmente válida.
- Por favor, no copies y pegues el código esperando que funcione, todo lo hice en el bloc de notas y, seguramente, habrá muchos errores; analiza, entiende y adapta lo que creas que te pueda servir.
- Cualquier duda será respondida en este mismo tema.

Bueno, creo que es todo, sólo falta aclarar que no hay sistema 100% seguro y que esto es sólo una guía para tratar de evitar ataques y cualquier comentario, sugerencia o añadidura será bienvenida.
__________________
- León, Guanajuato
- GV-Foto

Última edición por Triby; 13/12/2012 a las 17:31
  #6 (permalink)  
Antiguo 13/12/2012, 15:09
Avatar de abimaelrc
Colaborador
 
Fecha de Ingreso: mayo-2009
Ubicación: En el planeta de Puerto Rico
Mensajes: 14.734
Antigüedad: 14 años, 10 meses
Puntos: 1517
Respuesta: [Aporte] Seguridad básica en PHP

En la cuarta parte, también es útil usar Fileinfo.

En la quinta parte, lo mencionas pero encuentro que no muy claro (bueno al menos lo vi así), sería bueno indicar que el proyecto esté fuera del directorio que es público. Podrías también dar ejemplo para que se entienda completamente.

En la novena parte, el problema de md5 es que es tan rápido (porque de eso se trata, que sea lo más rápido igual sha1) que los que tratan de robar la contraseña con fuerza bruta lo logran con tarjetas de gráficos o cosas similares que corren miles (creo que hay algunas que pueden correr millones de veces) y con 1 o 2 días hackear la contraseña. Por eso se recomienda el uso de crypt (yo uso esa con CRYPT_BLOWFISH)

Código PHP:
Ver original
  1. echo crypt('rasmuslerdorf', '$2a$07$usesomesillystringforsalt$');

el $2a$07 indica que tipo de algoritmo y velocidad con que va a correr el algoritmo. Si los dos dígitos despues del último $ son mayores más lento es el algoritmo, al menos a mi no me funcionó despues de 11, se tardó mucho. Al final la idea es que se tome un tiempo para evitar este tipo de ataques.

PD: Si vas a continuar el tema, borro esto y luego lo coloco al final, para no interrumpir lo que estás haciendo, muy bueno
__________________
Verifica antes de preguntar.
Los verdaderos amigos se hieren con la verdad, para no perderlos con la mentira. - Eugenio Maria de Hostos

Última edición por abimaelrc; 13/12/2012 a las 15:38
  #7 (permalink)  
Antiguo 13/12/2012, 17:21
Avatar de Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 15 años, 7 meses
Puntos: 2237
Respuesta: [Aporte] Seguridad básica en PHP

Abimael, gracias por tus comentarios, el tema es un tanto "antiguo" y no tengo pensado extenderlo, aparte de las sugerencias que se vayan dando y, tal vez, sólo editar para agregar en cada sección lo que corresponda.

Lo del MD5, ya lo había mencionado, no es tan recomendable, pero es útil cuando no tienes disponibles otros métodos/librerías.

De cualquier forma, ya están agregadas tus sugerencias en cada sección, esperamos que pronto haya más comentarios para ir mejorando el tema.

Gracias.
__________________
- León, Guanajuato
- GV-Foto

Última edición por Triby; 13/12/2012 a las 17:32
  #8 (permalink)  
Antiguo 15/06/2014, 08:05
Avatar de Italico76  
Fecha de Ingreso: abril-2007
Mensajes: 3.303
Antigüedad: 16 años, 11 meses
Puntos: 292
Respuesta: [Aporte] Seguridad básica en PHP

Si no es "intrusivo" agregaré para completar que un mal diseño de como se hacen las peticiones para borrar / crear / modificar recursos puede hacer extremandamente facil a un usuario por ejemplo borrar toda una tabla de nuestra base de datos:


- Debe separarse lo que es borrar / crear / modificar recursos (generalmente usando POST) de lo que es consultarlos (por GET)

- No deben enviarse IDs numericas consecutivas.


Quizas lo mejor es usar GET para recuperar recursos y POST para modificar / eliminar / crearlos pero enviando en cada caso el ID de una forma distinta (en el caso de por GET haciendo un HASH complicado que lo convierta en una cadena alfanumerica compleja o usando "urls amigables") asi si veo una "ID" en un GET no la puedo usar en POST y viceversa.
__________________
Salu2!

Última edición por Italico76; 15/06/2014 a las 08:10
  #9 (permalink)  
Antiguo 11/09/2014, 07:56
 
Fecha de Ingreso: septiembre-2014
Mensajes: 3
Antigüedad: 9 años, 6 meses
Puntos: 0
Respuesta: [Aporte] Seguridad básica en PHP

Hola a todos!! en primer lugar, gracias por todo el conocimiento que compartís con todo el mundo.

En segundo lugar deciros que aunque este tema lleva mucho tiempo parado es de lo mejor que e encontrado por Internet.

Soy un programador neo-nato y aunque e conseguido lograr un sistema de sesiones "seguro" me encuentro el siguiente problema con session_regenerate_id(true);

Todas mis paginas cuentan con este sistema de protección de la sesión pero el problema es cuando clickeas dos veces seguidas rápidamente en un enlace... al parecer genera 2 sesiones y se pierden todos los valores asignados.

He encontrado alguna cosa en ingles pero no acabo de enterarme.. sabriais si existe alguna manera de corregir este error?? espero que sí y que todos mis problemas se deban a mi inexperiencia.

Muchas gracias de antemano

Agur!.
  #10 (permalink)  
Antiguo 22/09/2014, 20:20
 
Fecha de Ingreso: agosto-2013
Ubicación: Talca
Mensajes: 40
Antigüedad: 10 años, 7 meses
Puntos: 1
Respuesta: [Aporte] Seguridad básica en PHP

Muy buenos tips, gracias por todo lo aportado.
Cambiare la estructura de una aplicación que hice, con los consejos aquí dichos.
Saludos desde Chile.

Etiquetas: formulario, html, seguridad, variables, usuarios
Atención: Estás leyendo un tema que no tiene actividad desde hace más de 6 MESES, te recomendamos abrir un Nuevo tema en lugar de responder al actual.
Respuesta

SíEste tema le ha gustado a 20 personas




La zona horaria es GMT -6. Ahora son las 18:41.