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<?php
if($logueado) {
// Incluir script para usuarios
} else {
// Incluir script para iniciar sesión
}
/* se recomienda omitir el uso de ?> para evitar problemas relacionados
con encabezados tocaremos luego ese tema
En su lugar colocaremos una línea como la siguiente, sólo por referencia */
// 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$logueado = false;
if(isset($_SESSION['logueado']) && $_SESSION['logueado'] == true) { // Aquí debemos hacer otras validaciones
// que trataremos posteriormente
}
Otro ejemplo:
Código PHP:
Ver original<?php
include "$ruta/script.php";
// 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 originalfunction globals() {
if (isset($_REQUEST['GLOBALS']) OR
isset($_FILES['GLOBALS'])) { // Prevent malicious GLOBALS overload attack
echo "Global variable overload attack detected! Request aborted.\n";
// Exit with an error status
}
// Get the variable names of all globals
// Remove the standard global variables from the list
'_COOKIE',
'_ENV',
'_GET',
'_FILES',
'_POST',
'_REQUEST',
'_SERVER',
'_SESSION',
'GLOBALS',
));
foreach ($global_variables as $name) {
// Unset the global variable, effectively disabling register_globals
}
}
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// Siempre asignamos un valor por defecto que
// nos permitirá tener mejor control
$usuario = '';
$id = 0;
$numerico = 0;
if(isset($_GET['usuario'])) { // Los datos de variables externas, por defecto son string
// Aplicamos las funciones que convengan para "sanear" y evitar ataques
}
// Forzamos el tipo para obtener un entero, es lo que esperamos
$id = (int) $_GET['id'];
}
if(isset($_POST['numerico'])) { // Si la variable puede ser entero, flotante, doble, etc.
// podemos asegurarnos de que sea numérico
// simplemente multiplicando por 1
$numerico = $_POST['numerico'] * 1;
}
if($usuario != '') {
// Aquí debemos verificar que lo que se tecleó sea correcto
// comparando con nuestra fuente de datos (archivo, constante, variable, BDD, etc.)
}
if($id > 0) {
// Ejecutamos las acciones necesarias para verificar la ID
// que, puede ser de un usuario, noticia, evento, etc.
}
if($numerico > 0) {
// Ejecutamos las operaciones necesarias
}
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// Acciones válidas
$acciones = array('', 'modificar', 'ver'); // Faltarían 'agregar' y 'eliminar'
// Podemos inicializar la variable con asignación ternaria
// es lo mismo que el ejemplo anterior, pero en una sóla línea
$accion = (isset($_GET['accion'])) ?
$_GET['accion'] : '';
// Esta la dejamos pendiente, hasta saber qué vamos a hacer
$id = 0;
// Verificamos si la acción es correcta
// La acción seleccionada no es válida
// Podemos enviar un mensaje de error (ideal)
// O establecer la acción por defecto
$accion = '';
}
if($accion == '') {
// No necesitamos ID
// Tal vez sólo mostraremos un listado de productos
} else if($accion == 'ver') {
// ID debería estar en $_GET
$id = (isset($_GET['id'])) ?
(int
) $_GET['id'] : 0; if($id == 0) {
die('No sabemos qué es lo que vamos a buscar'); }
// Lees de base de datos, archivo, etc.
// Muestras el elemento (producto, fotografía, noticia, etc.)
} else if($accion == 'modificar') {
// ID debería estar en $_POST (?)
$id = (isset($_POST['id'])) ?
(int
) $_POST['id'] : 0; if($id == 0) {
die('No sabemos qué es lo que vamos a buscar'); }
// Lees de base de datos, archivo, etc.
// Muestras el formulario para modificar el elemento
}
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// Función recursiva que permite limpiar variables y/o arreglos
function limpiaVariables($data) {
foreach($data as $key => $val) {
$data[$key] = limpiarVariables($val);
}
} else {
$data = filterXSS($data);
}
return $data;
}
// Limpiamos las entradas manipulabes por el usuario
$_GET = limpiarVariables($_GET);
$_POST = limpiarVariables($_POST);
$_COOKIE = limpiarVariables($_COOKIE);
Adicionalmente, también podemos convertir caracteres especiales a sus respectivas entidades html usando:
Código PHP:
Ver original// Convertimos caracteres como <, >, &, " a sus entidades HTML
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á --