¿Son seguros los comentarios de código PHP?
Capítulo de seguridad de PHP que filtra los parámetros de entrada del usuario
Regla 1: nunca confíes en datos o entradas externas
Lo que debes saber sobre la seguridad de las aplicaciones web Lo primero que debes saber Lo que me viene a la mente es que no se debe confiar en los datos externos. Los datos externos incluyen cualquier dato que el programador no ingrese directamente en el código PHP. No se puede confiar en ningún dato de cualquier otra fuente (como variables GET, formulario POST, bases de datos, archivos de configuración, variables de sesión o cookies) hasta que se tomen medidas para garantizar la seguridad.
Por ejemplo, los siguientes elementos de datos pueden considerarse seguros porque están configurados en PHP.
Copia el código de la siguiente manera:
$myUsername = 'tmyer';
$arrayUsers = array(' tmyer ', 'tom', 'tommy');define("SALUDO", 'hola' . $myUsername);?>
Sin embargo, los siguientes elementos de datos son defectuosos.
Listado 2. Código inseguro y defectuoso
Copia el código de la siguiente manera:
$myUsername = $ _POST[ 'nombre de usuario']; //¡contaminado!
$arrayUsers = array($myUsername, 'tom', 'tommy'); //¡contaminado!
define("SALUDO", 'hola' . $myUsername); //contaminado >
¿Por qué la primera variable $myUsername es defectuosa? Porque viene directamente del formulario POST. Los usuarios pueden ingresar cualquier cadena en este campo de entrada, incluidos comandos maliciosos para limpiar archivos o ejecutar archivos cargados previamente. Podría preguntar: "¿No se puede evitar este peligro utilizando un script de validación de formularios del lado del cliente (Javascrīpt) que sólo acepta las letras A-Z? Sí, este siempre es un paso beneficioso, pero como veremos más adelante, ¿alguien?" pueden descargar cualquier formulario a su máquina, modificarlo y volver a enviar lo que necesiten.
La solución es simple: el código de desinfección debe ejecutarse en $_POST['nombre de usuario']. Si no hace esto, corre el riesgo de contaminar estos objetos en cualquier otro momento que use $myUsername (como en una matriz o constante).
Una forma sencilla de desinfectar la entrada del usuario es utilizar expresiones regulares para procesarla. En este ejemplo, sólo se espera que se acepten cartas. También podría ser una buena idea limitar la cadena a un número específico de caracteres o exigir que todas las letras estén en minúsculas.
Listado 3. Hacer que la entrada del usuario sea segura
Copie el código de la siguiente manera:
$myUsername = cleanInput( $ _POST['nombre de usuario']); //¡limpiar!
$arrayUsers = array($minombre de usuario, 'tom', 'tommy'); //¡limpiar!
define( " SALUDO", 'hola' . $miNombredeUsuario); //¡limpiar!
función cleanInput($input){
$clean = strtolower($input);
$clean = preg_replace(”/[^a-z]/”, “”, $clean);$clean = substr($clean,0,12);
return $clean; p>
}
Regla 2: Deshabilite las configuraciones de PHP que dificultan la implementación de la seguridad Ahora que sabe que no puede confiar en la entrada del usuario, también debe saber que no debe confiar en la forma PHP está configurado en la máquina. Por ejemplo, asegúrese de que Register_globals esté deshabilitado. Si Register_globals está habilitado, es posible hacer cosas descuidadas como usar una variable $ para reemplazar una cadena GET o POST con el mismo nombre. Al deshabilitar esta configuración, PHP lo obliga a hacer referencia a las variables correctas en el espacio de nombres correcto. Para utilizar variables de un formulario POST, se debe citar $_POST['variable']. De esta manera no confundirá esta variable en particular con una cookie, sesión o variable GET.
Regla 3: si no puedes entenderlo, no puedes protegerlo
Algunos desarrolladores usan una sintaxis extraña u organizan las declaraciones de manera muy estricta, lo que da como resultado un código corto pero ambiguo. Este enfoque puede ser eficaz, pero si no comprende qué hace el código, no podrá decidir cómo protegerlo.
Por ejemplo, ¿cuál de los siguientes dos fragmentos de código te gusta?
Listado 4. Haga que el código sea fácil de proteger
Copia el código de la siguiente manera:
//código ofuscado
p>$input = (isset($_POST['nombre de usuario']) ? $_POST['nombre de usuario']:”);//código no ofuscado
$input = ” ;
if (isset($_POST['nombre de usuario'])){
$input = $_POST['nombre de usuario'];
}else{
$input = ”;
}
En el segundo fragmento de código más claro, es fácil ver que $input está defectuoso y necesita ser limpiado. Entonces se podrá procesar de forma segura.
Regla 4: “Defensa en profundidad” es la nueva arma mágica
Este tutorial utilizará ejemplos para ilustrar cómo proteger los formularios en línea mientras se procesa el formulario. código PHP del formulario Tome las medidas necesarias. Del mismo modo, incluso si utiliza expresiones regulares PHP para garantizar que las variables GET sean completamente numéricas, aún puede tomar medidas para garantizar que las consultas SQL utilicen entradas de usuario escapadas. La profundidad no es solo una buena idea, lo que garantiza que no te metas en problemas graves.
Ahora que se han analizado las reglas básicas, veamos la primera amenaza: los ataques de inyección SQL. p>
Prevención de ataques de inyección SQL.
En un ataque de inyección SQL, el usuario agrega información a una consulta de base de datos manipulando un formulario o una cadena de consulta GET. Por ejemplo, considere una base de datos de inicio de sesión simple.
Cada registro en esta base de datos tiene un campo de nombre de usuario y un campo de contraseña. Cree un formulario de inicio de sesión para permitir que los usuarios inicien sesión.
Listado 5. Formulario de inicio de sesión simple
Copia el código de la siguiente manera:
Este formulario acepta un nombre de usuario y contraseña, y envía la entrada del usuario a un archivo llamado verificar.php. En este archivo, PHP procesa los datos del formulario de inicio de sesión de la siguiente manera:
Listado 6. Código de procesamiento de formulario PHP inseguro
Copia el código de la siguiente manera:
$ok = 0;
$nombre de usuario = $_POST['usuario'];
$pw = $_POST[' pw'] ;
$sql = “seleccione recuento (*) como ctr de usuarios donde nombre de usuario ='".$nombre de usuario."' y contraseña ='". $pw."' límite 1″; mysql_query($sql);
mientras ($data = mysql_fetch_object($resultado)){if ($data->ctr == 1){
//están bien para ingresar a la aplicación!
$okay = 1;
}
}
if ($okay){
$_SESSION['loginokay'] = true;
header("index.php");
}else{
header ("iniciar sesión .php");
}
Este código se ve bien, ¿verdad? Cientos (si no miles) de sitios PHP/MySQL en todo el mundo utilizan códigos como este. ¿Qué tiene de malo? Bueno, recuerde que "no se puede confiar en la entrada del usuario". Aquí no se escapa ninguna información del usuario, lo que deja la aplicación vulnerable. En concreto, es posible cualquier tipo de ataque de inyección SQL.
Por ejemplo, si el usuario ingresa foo como nombre de usuario y ' o '1′='1 como contraseña, la siguiente cadena en realidad se pasa a PHP, que luego pasa la consulta a MySQL: p>
p>
Copia el código de la siguiente manera:
$sql = “select count(*) as ctr from usuarios donde nombre de usuario=' foo' y contraseña=” o '1′='1′ límite 1″;?>
Esta consulta siempre devuelve un recuento de 1, por lo que PHP permitirá el acceso inyectando SQL malicioso al final. de la cadena de contraseña, un hacker puede pretender ser un usuario legítimo.
La solución a este problema es utilizar la función mysql_real_escape_string() incorporada de PHP como contenedor para cualquier entrada del usuario. caracteres en la cadena, lo que hace imposible pasar caracteres especiales como apóstrofes en la cadena y permite que MySQL opere con caracteres especiales
Listado 7. Código de procesamiento de formularios PHP seguro
$okay = 0;
$username = $_POST['user'] ;
$pw = $_POST['pw'];
$sql = "seleccione recuento(*) como ctr de los usuarios donde nombre de usuario='".mysql_real_escape_string($nombre de usuario) ."' y contraseña='". mysql_real_escape_string($pw)."' límite 1";$resultado = mysql_query($sql);
mientras ($datos = mysql_fetch_object($resultado)){if ($datos- >ctr == 1){
//¡Están bien para ingresar a la aplicación!
$okay = 1;
}
}
if ($okay){
$_SESSION['loginokay'] = true;
header(”index.php ");
}else{
header("login.php");
}
Utilice mysql_real_escape_string() como contenedor de entrada del usuario, puede evitar cualquier inyección SQL maliciosa en la entrada del usuario. Si un usuario intenta pasar una contraseña mal formada mediante inyección SQL, se pasará la siguiente consulta a la base de datos:
seleccione count(*) como ctr de los usuarios donde nombre de usuario='foo' y contraseña='\ ' o \ '1\'=\'1′ limit 1″ No hay nada en la base de datos que coincida con dicha contraseña. Con solo dar un simple paso se cerró un gran agujero en la aplicación web. La lección aprendida aquí es: Entrada del usuario. Las consultas SQL siempre deben tener carácter de escape.
Sin embargo, hay varios agujeros de seguridad que deben bloquearse
Evitar la manipulación de las variables GET
en lo anterior. sección, impidió que los usuarios iniciaran sesión con contraseñas mal formadas. Si es inteligente, debe aplicar las técnicas que aprendió para garantizar que todas las entradas del usuario para las declaraciones SQL estén en formato de escape.
Sin embargo, el usuario ahora ha iniciado sesión. en forma segura.
El hecho de que un usuario tenga una contraseña válida no significa que seguirá las reglas: existen muchas oportunidades para que cause daño. Por ejemplo, una aplicación podría permitir a los usuarios ver contenido especial. Todos los enlaces apuntan a ubicaciones como template.php?pid=33 o template.php?pid=321. La parte de la URL después del signo de interrogación se denomina cadena de consulta. Debido a que la cadena de consulta se coloca directamente en la URL, también se denomina cadena de consulta GET.
En PHP, si Register_globals está deshabilitado, se puede acceder a esta cadena con $_GET['pid']. En la página template.php, puede hacer algo similar al Listado 8.
Listado 8. Ejemplo template.php
Copia el código de la siguiente manera:
$pid = $_GET[ ' pid'];
//creamos un objeto de una clase ficticia Page$obj = new Page;
$content = $obj->fetchPage($pid);
p>//¿Y ahora tenemos un montón de PHP que muestra la página?>
¿Hay algún problema aquí? En primer lugar, se confía implícitamente en que la variable GET pid del navegador es segura. ¿Qué pasará? La mayoría de los usuarios no son lo suficientemente inteligentes como para elaborar ataques semánticos. Sin embargo, si detectan pid=33 en el campo de ubicación URL del navegador, podrían empezar a causar problemas. Si ingresan otro número, probablemente esté bien, pero si ingresan algo más, como un comando SQL o el nombre de un archivo (como /etc/passwd), o algo más, hasta un truco, como escribir 3000. Valor de los personajes, ¿qué pasa?
En este caso, recuerda la regla básica: no confiar en la entrada del usuario.
Los desarrolladores de aplicaciones saben que los identificadores personales (PID) aceptados por template.php deben ser numéricos, por lo que pueden usar la función is_numeric() de PHP para garantizar que no se acepten PID no numéricos, de la siguiente manera:
Listado 9. Utilice is_numeric() para limitar el código de copia de la variable GET. El código es el siguiente:
$pid = $_GET['pid']; p>
if (is_numeric($pid)){
//creamos un objeto de una clase ficticia Page$obj = new Page;
$content = $obj ->fetchPage($pid );
//y ahora tenemos un montón de PHP que muestra la página}else{
//no pasó la prueba is_numeric() , ¡haz algo más!
}
Este método parece ser válido, pero las siguientes entradas pueden pasar fácilmente la verificación is_numeric():
100 ( válido)
100 (válido)
} p>
100.1 (no debe tener decimales)
+0123.45e6 (notación científica - mala)
0xff33669f (hexadecimal - ¡Peligro! ¡Peligro! ) Entonces, ¿qué deberían hacer los desarrolladores de PHP preocupados por la seguridad? Años de experiencia han demostrado que la mejor práctica es utilizar expresiones regulares para garantizar que toda la variable GET esté formada por números, como se muestra a continuación:
Listado 10. Uso de expresiones regulares para restringir variables GET
Copia el código de la siguiente manera:
$pid = $_GET['pid'];
if (strlen($pid) ){
if (!ereg("^[0-9]+$",$pid)){
//hacer algo apropiado, como cerrar sesión o enviarles devuélvalos a la página de inicio}
}else{
//$pid vacío, así que envíelos de regreso a la página de inicio}
//creamos un objeto de una página de clase ficticia, que ahora está //moderadamente protegida contra la entrada malvada del usuario$obj = nueva página;
$content = $obj->fetchPage($pid);
/ /¿Y ahora tenemos un montón de PHP que muestra la página?>
Todo lo que necesitamos hacer es usar strlen() para verificar si la longitud de la variable es distinta de cero, si es así; utilice una expresión regular de todos los dígitos para asegurarse de que los elementos de datos sean válidos. Si el PID contiene letras, barras, puntos o algo parecido a hexadecimal, entonces esta rutina lo captura y bloquea la página de la actividad del usuario.
Si miras detrás de escena de la clase Page, verás que los desarrolladores de PHP preocupados por la seguridad han escapado de la entrada del usuario $pid, protegiendo así el método fetchPage(), como se muestra a continuación:
Listado 11 Escapa del método fetchPage()
Copie el código de la siguiente manera:
clase Página{
función fetchPage(. $pid){
$sql = “seleccione pid,title,desc,kw,content,status de la página donde pid='".mysql_real_escape_string($pid)."'";}
}
Quizás te preguntes: "Dado que nos hemos asegurado de que el PID sea un número, ¿por qué necesitamos escapar de él?" Porque no sabemos en cuántos contextos y situaciones diferentes se encuentra. se utilizará en el método fetchPage(). Se debe brindar protección en todos los lugares donde se llame a este método, y escapar en el método encarna el significado de defensa en profundidad.
¿Qué sucede si el usuario intenta ingresar un valor muy largo, como hasta 1000 caracteres, e intenta lanzar un ataque de desbordamiento del búfer? La siguiente sección analiza esto con más detalle, pero por ahora puede agregar otra verificación para asegurarse de que el PID de entrada tenga la longitud correcta. Sabes que la longitud máxima del campo pid de la base de datos es de 5 dígitos, por lo que puedes agregar la siguiente verificación.
Listado 12. Utilice expresiones regulares y comprobaciones de longitud para limitar las variables GET. Copie el código de la siguiente manera:
$pid = $_GET[. ' pid'];
if (strlen($pid)){
if (!ereg("^[0-9]+$",$pid) && strlen( $ pid) > 5){//haga algo apropiado, como tal vez cerrar sesión o enviarlos de regreso a la página de inicio}
} else {
//vaciar $pid, entonces enviarlos de vuelta a la página de inicio}
//creamos un objeto de una clase ficticia Página, que ahora//está aún más protegida contra la entrada malvada del usuario$obj = nueva página;
$content = $obj->fetchPage($pid);
//¿y ahora tenemos un montón de PHP que muestra la página?>
Ahora, nadie puede Introducir un valor de 5.000 dígitos en una aplicación de base de datos, al menos no cuando se trata de cadenas GET. ¡Imagínese a los piratas informáticos rechinar los dientes cuando se sienten frustrados en sus intentos de acceder a su aplicación! Y como el informe de errores está desactivado, a los piratas informáticos les resulta más difícil realizar reconocimientos.
Ataque de desbordamiento de búfer
Un ataque de desbordamiento de búfer intenta desbordar un búfer de asignación de memoria en una aplicación PHP (o, más precisamente, en Apache o el sistema operativo subyacente). Tenga en cuenta que puede estar escribiendo su aplicación web en un lenguaje de alto nivel como PHP, pero en última instancia estará llamando a C (en el caso de Apache). Como la mayoría de los lenguajes de bajo nivel, C tiene reglas estrictas para la asignación de memoria.
Los ataques de desbordamiento de búfer envían una gran cantidad de datos al búfer, lo que hace que parte de los datos se desborden hacia los búferes de memoria adyacentes, destruyendo así el búfer o reescribiendo la lógica. Esto puede provocar una denegación de servicio, datos corruptos o ejecutar código malicioso en el servidor remoto.
La única forma de evitar ataques de desbordamiento del búfer es comprobar la longitud de todas las entradas del usuario. Por ejemplo, si hay un elemento de formulario que requiere el nombre del usuario, agregue un atributo maxlength con un valor de 40 en este campo y verifíquelo usando substr() en el backend. El Listado 13 ofrece un breve ejemplo del formulario y el código PHP.