Ver Mensaje Individual
  #1 (permalink)  
Antiguo 06/09/2012, 05:42
Avatar de Triby
Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 15 años, 8 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