Conocimientos básicos de seguridad del sitio.

Hola Habr!

La seguridad es un asunto serio. Y a menudo los problemas en esta área surgen inesperadamente y tienen consecuencias extremadamente desagradables. Por lo tanto, el conocimiento en este tema es extremadamente importante para cada desarrollador web.

Haré una reserva de inmediato, estoy lejos de ser un profesional, pero me esfuerzo por lograrlo. Por lo tanto, estaré encantado de criticar, pero solo objetivo. Este material es para principiantes que desean aumentar su profesionalismo y valor como especialistas.

Y, sin embargo, muestro la implementación de código más simple posible. Sé sobre excepciones, sé sobre ORM, sobre la protección proporcionada en los marcos. Mi objetivo es mostrarlo claramente para que todos lo entiendan.

Y así, es hora de terminar con la introducción y comenzar a practicar.

El camino desde la implementación de un novato hasta cualquier resultado sensato


No estoy acostumbrado a trabajar con la teoría. Mi alma anhela practicar. Por lo tanto, al hablar sobre el tema de la seguridad, consideraremos casi todos los tipos de ataques desde un punto de vista práctico: cómo implementar y cómo protegerse. Sí, puedes decir que enseñar piratería no es bueno, pero, sin saber cómo ocurre el ataque, no podemos construir una defensa competente.

Xss


De acuerdo, el primer tipo de ataque es XSS. Sí, el buen viejo XSS del que todos han oído hablar. XSS (Cross Site Scripting) es un tipo de ataque dirigido a los visitantes del sitio. Cómo sucede esto: a través del campo de entrada, el atacante escribe código malicioso que ingresa a la base de datos y hace su trabajo. Por lo general, de esta forma, los usuarios roban cookies, lo que les permite iniciar sesión en sus cuentas sin una contraseña e iniciar sesión.

Estamos implementando un ejemplo más inofensivo.

Nuestro desarrollador hizo un formulario simple para agregar comentarios:

Archivo index.php
<?php $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]; $pdo = new PDO("mysql:host=localhost;dbname=".$db,$user,$pass,$opt); $pdo->exec("SET CHARSET utf8"); $query = $pdo->prepare("SELECT * FROM `comments`"); $query->execute(); $comments = $query->fetchAll(); if ($_POST) { $username = trim($_POST['name']); $comment = trim($_POST['comment']); $query = $pdo->prepare("INSERT INTO `comments` (`username`,`message`) VALUES ('$username', '$comment')"); $query->execute(); if ($query) { echo ' !'; header("Location: index.php"); } else { echo ' !'; } } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>XSS</title> </head> <body> <form method="POST" class="addComment"> <input type="text" name="name" placeholder="Username"> <textarea name="comment"></textarea> <input type="submit" value=" "> </form> <div class="h2"></div> <div class="comments"> <?php if ($comments): foreach ($comments as $comment):?> <div class="comment"> <div class="comment_username"><?php echo $comment['username'];?></div> <div lass="comment_comment"><?php echo $comment['message'];?></div> </div> <?php endforeach;?> <?php else:?> <div class="no_comments"> </div> <?php endif;?> </div> </body> </html> 

El código es muy simple y no necesita explicación.

Hay un intruso, John. John se aburrió y tropezó con el sitio de nuestro desarrollador.
John escribe el siguiente mensaje en el formulario:

 <script>document.body.style.backgroundColor = "#000";</script> 

Y ahora todos los usuarios del sitio tienen un fondo negro. John está satisfecho y el desarrollador adquirió experiencia y amonestación.

Que ha pasado

John agregó un comentario con código JavaScript. Al enviar datos a una página, un comentario de texto se convierte en código html. El código html, al ver el uso de la etiqueta del script, lo agregó al marcado y el intérprete ya ejecutó el código JavaScript. Es decir, John acaba de agregar su pieza de código js al código del sitio existente.

¿Cómo lo arreglaremos?

Para corregir este malentendido, se creó la función htmlspecialcars. La esencia de su trabajo es que reemplaza caracteres como comillas y corchetes con caracteres especiales. Por ejemplo, el carácter "<" será reemplazado por su código de carácter correspondiente. Con esta función, procesamos los datos del formulario y ahora el código js de John ya no puede dañar nuestro sitio. Por supuesto, si este es el único formulario en el sitio.

Los cambios en el código se verán así:

Archivo index.php
 <?php if ($_POST) { $username = htmlspecialchars(trim($_POST['name'])); $comment = htmlspecialchars(trim($_POST['comment'])); /// } 


Inyección SQL


Otro de los tipos de ataques más comunes, que ya han comenzado a ser olvidados. Lo olvidan porque hay consultas y marcos preparados.

Hablaremos sobre las solicitudes preparadas.

Cuál es la esencia del ataque: el atacante ingresa parte de la consulta SQL en el campo de entrada y envía el formulario. Durante la ejecución de la consulta, los datos recibidos se agregan a la base de datos. Pero como el código contiene el código, al agregar registros, modifica la lógica de nuestro script.

Bien, simula una situación. Nuestro desarrollador ha protegido el formulario de los ataques XSS. Y John continúa usando su conocimiento, señalando los defectos del desafortunado desarrollador.

Es decir, continuaremos trabajando con la misma forma para agregar comentarios.
Hagamos algunos cambios:

1) Premoderación de comentarios.

La conclusión es que solo los comentarios aprobados por el moderador se mostrarán en la página. Implementamos la moderación previa en su forma más simple para no distraernos de la parte principal del artículo.

Para implementar la idea, agregue el campo "is_moderate" a la tabla con comentarios, que tomará dos valores: "1" (mostrar el comentario) o "0" (no mostrar). Por defecto, por supuesto, "0".

2) Cambiar la solicitud.

Esto es por claridad. Deje que la solicitud para agregar comentarios se vea así:

 "INSERT INTO `comments` SET `username`='$username', `message`='$comment'" 

Ahora el código del formulario es el siguiente:

Archivo index.php
 <?php $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]; $pdo = new PDO("mysql:host=localhost;dbname=".$db,$user,$pass,$opt); $pdo->exec("SET CHARSET utf8"); $query = $pdo->prepare("SELECT * FROM `comments` WHERE `is_moderate`='1'"); $query->execute(); $comments = $query->fetchAll(); if ($_POST) { $username = htmlspecialchars(trim($_POST['name'])); $comment = htmlspecialchars(trim($_POST['comment'])); $query = $pdo->prepare("INSERT INTO `comments` SET `username`='$username', `message`='$comment'"); $query->execute(); if ($query) { echo ' !'; } else { echo ' !'; } } ?> 


De acuerdo, John inicia sesión en el sitio y, al ver que los comentarios comenzaron a ser moderados, decidió burlarse del desarrollador. Además, la forma de los ataques XSS ahora se refleja con éxito y John ya ha sido privado de la oportunidad de divertirse. Deja un comentario de este tipo: "LOL ', is_moderate =' 1" y omite la moderación.

Por qué

Cuando sustituye el comentario de John en nuestra consulta, las comillas se rompen. Es decir, como se mencionó anteriormente, John pudo ejecutar código SQL arbitrario.

Al ejecutar una solicitud con el comentario de John, la solicitud es la siguiente:

 "INSERT INTO `comments` SET `username`='John', `message`='LOL', `is_moderate`='1'" 

Además, la inyección SQL se puede implementar no solo al enviar un formulario. Esto también puede suceder al recibir registros por su identificador, procesar el formulario de búsqueda y otras situaciones no tan obvias.

¿Cómo arreglarlo?

El método para resolver el problema se conoce desde hace mucho tiempo: consultas preparadas. Las solicitudes preparadas son solicitudes que se someten a un procesamiento especial antes de ser ejecutadas. El procesamiento consiste en escapar de comillas adicionales. Debes haber oído hablar de tal característica. En PHP, se implementa así: "\ '".

La solución más popular es PDO. PDO es una interfaz para trabajar con una base de datos. Lo que tiene una interfaz bastante conveniente. Solo úsalo sabiamente.

PDO proporciona la capacidad de usar máscaras y marcadores de posición para implementar consultas preparadas.

Entonces nuestra solicitud al usar máscaras se verá así:

Archivo index.php
 <?php $query = $pdo->prepare("INSERT INTO `comments` SET `username`=:username, `message`=:comment"); $params = ['username' => $username,'comment' => $comment]; $query->execute($params); 


Y cuando se usan marcadores de posición como este:

Archivo index.php
 <?php $query = $pdo->prepare("INSERT INTO `comments` SET `username`=?, `message`=?"); $params = [$username,$comment]; $query->execute($params); 


Ahora el ataque de John ya no es relevante. Al menos para este formulario.

Por cierto, nuestra moderación, incluso en esta forma, ya protege contra otro tipo de ataque: el SPAM. Todos escuchamos de él. El SPAM es el envío de cualquier mensaje en el que, con la ayuda del conocimiento de ingeniería social, los atacantes llevan a cabo sus ataques. Ahora el único que será atacado es el moderador. Y luego, si no es tan estúpido, eliminará la basura de la base de datos o, si es flojo, rechazará la publicación y eso es todo.

Ataque CSRF


CSRF: falsificación de solicitudes entre sitios. Es peligroso porque pocas personas lo saben. Aunque hacer esto es bastante simple.

Cómo sucede: un atacante de otro sitio falsifica un formulario y obliga a la víctima a seguirlo. Es decir, se envía una solicitud POST. Por lo tanto, se falsifica una solicitud HTTP y se realiza una acción maliciosa en el sitio de la víctima.

Por ejemplo, un atacante podría enviarle a su amigo una carta en VK en su nombre, pero no lo sabrá.

Suena un poco confuso. Propongo considerarlo en la práctica.

Nuestro desarrollador hizo el formulario, está bien hecho. Ella ya sabe cómo defenderse contra XSS, inyección SQL y mantiene la presión de Spam. Se ve así:

El archivo index.php en el sitio del desarrollador
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CSRF</title> </head> <body> <form action="action.php" method="POST"> <input type="text" name="username"> <textarea name="message"></textarea> <input type="submit" value=" "> </form> </body> </html> 


Pero John no es tan simple. Recibe el código del formulario (simplemente del código fuente del sitio en el navegador) y agrega el formulario a su sitio.

El archivo index.php de John
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CSRF</title> <style> form input[type=submit]{ padding: 15px; font-size: 20px; color: #fff; background: #f00; cursor: pointer; } </style> </head> <body> <form action="localhost/note/action.php" method="POST"> <input type="hidden" name="username" value="lol"> <input type="hidden" name="message" value="  !   !"> <input type="submit" value=" !"> </form> </body> </html> 


Tenga en cuenta que en el sitio web de John, el lugar donde se procesa el formulario es un archivo del sitio web de nuestro desarrollador. Entonces, cualquier usuario que haga clic en el botón no enviará buenos comentarios.

Este es un ataque CSRF. En la versión más simple, por supuesto.

El desarrollador vuelve a tener problemas ...

¿Cómo arreglar una vulnerabilidad?

En un momento, el desarrollador buscará en Google qué es csrf y la lógica de protección será la siguiente: para la protección, debe crear un token csrf (un conjunto de letras y números) y colgarlo en el formulario. También es necesario arreglar el mismo token para el usuario (por ejemplo, a través de una sesión). Y luego, al procesar el formulario, compare estos tokens. Si coinciden, podemos agregar un comentario.

Implementamos esto:

El archivo index.php en el sitio del desarrollador
 <?php session_start(); $token = ''; if (function_exists('mcrypt_create_iv')) { $token = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); } else { $token = bin2hex(openssl_random_pseudo_bytes(32)); } $_SESSION['token'] = $token; ?> ... <form action="action.php" method="POST"> <input type="text" name="username"> <textarea name="message"></textarea> <input type="hidden" name="csrf_token" value="<?php echo $token;?>"> <input type="submit" value=" "> </form> 


Archivo action.php
 <?php session_start(); if ($_POST) { if ($_SESSION['token'] == $_POST['csrf_token']) { echo ' !'; } else { echo '!'; } } 


Fuerza bruta y contraseñas de Publick


Quizás el tipo de ataque más famoso. Lo escuchó casi todas las primeras películas sobre hackers y cosas por el estilo.

Cuál es el punto: hay un formulario de autorización en el panel de administración del sitio. Necesitamos un nombre de usuario y contraseña que John no conoce. Pero tiene un archivo con nombres de usuario y contraseñas populares. Y él corre alegremente para probarlos en nuestro sitio.

¿A qué se puede oponer un desarrollador? Por ejemplo, una restricción en el número de intentos de autorización en un cierto período de tiempo.

Deje que la versión inicial del formulario de autorización se vea así:

Archivo index.php
 <?php $opt = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]; $pdo = new PDO("mysql:host=localhost;dbname=".$db,$user,$pass,$opt); $pdo->exec("SET CHARSET utf8"); if (isset($_POST['autoriz'])) { $username = htmlspecialchars(trim($_POST['login'])); $password = htmlspecialchars(trim($_POST['password'])); $query = $pdo->prepare("SELECT * FROM `users` WHERE `username`=:username AND `password`=:password"); $query->execute(['username' => $username,'password' => $password]); $find_user = $query->fetchAll(); if ($find_user) { echo ' !'; } else { echo '  !'; } } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Brute Force  Public Passwords</title> </head> <body> <form method="POST"> <input type="text" name="login" placeholder="Login"> <input type="password" name="password" placeholder="Password"> <input type="submit" value="" name="autorize"> </form> </body> </html> 


Cómo solucionarlo: la opción más simple, como ya se mencionó, es limitar el número de intentos de autorización por período de tiempo.

Para hacer esto, cuando intentemos iniciar sesión, agregaremos el valor actual del tiempo al usuario en las cookies. Y ahora, cuando intente iniciar sesión, veremos que el usuario puede iniciar sesión no más de 1 vez en 5 segundos. Además, si la contraseña o el inicio de sesión no se ingresan correctamente, aumentaremos el tiempo de espera hasta el siguiente intento en 5 segundos.

Archivo index.php
 <?php $count_next_minit = $_COOKIE['count_try'] ? $_COOKIE['count_try'] : 1; $seconds_to_new_try = 5; $opt = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]; $pdo = new PDO("mysql:host=localhost;dbname=".$db,$user,$pass,$opt); $pdo->exec("SET CHARSET utf8"); if (isset($_POST['autorize'])) { if ($_COOKIE['last_try']) { if ($_COOKIE['last_try'] < time() - $seconds_to_new_try * $count_next_minit) { $username = htmlspecialchars(trim($_POST['login'])); $password = htmlspecialchars(trim($_POST['password'])); $query = $pdo->prepare("SELECT * FROM `users` WHERE `username`=:username AND `password`=:password"); $query->execute(['username'=>$username,'password'=>$password]); $find_user = $query->fetchAll(); setcookie('last_try', time(), time() + 3600); if ($_COOKIE['count_try']) { $old_value = (int)$_COOKIE['count_try']; setcookie('count_try', $old_value + 1, time() + 3600); } else { setcookie('count_try', 1, time() + 3600); } if ($find_user) { var_dump(' !'); } else { var_dump('  !'); } }else{ var_dump('   !    ' . $seconds_to_new_try * $count_next_minit . ' '); } }else{ setcookie('last_try', time(), time() + 3600); } } ?> 


Traza


Backtrace es una forma de atacar a través de mensajes de error del sistema. Esto es tanto MySQL como PHP.

Por ejemplo, John ingresó una URL incorrecta y recibió un error diciendo que no hay registro en la base de datos con dicha identificación (si el registro se recibe a través de la identificación de la barra de direcciones - site.ru/article?id=12). Incluso hay los llamados "dorks": patrones específicos de direcciones de sitios web, al ingresar, el usuario ve errores. Y esto abre la posibilidad de que John use el bot para revisar esta lista de direcciones y tratar de encontrar esta vulnerabilidad en su sitio.

¿Cómo arreglarlo? En este caso, lo haremos sin ejemplos, porque el problema se resuelve simplemente cerrando la salida de error. Por lo general, esto se implementa mediante el alojamiento, proporcionándole en el panel de administración para escribir registros en un archivo separado, pero no será superfluo limitar la salida de errores usted mismo.

Esto se puede hacer usando la función error_reporting ().

Entre los argumentos que se necesitan están: E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_ALL. Los nombres hablan por sí mismos.

Por ejemplo, si usa error_reporting (E_NOTICE), todos los errores estarán ocultos, excepto los errores del tipo Aviso (advertencias, por ejemplo, que no hay datos en la matriz $ _POST).
Para deshabilitar la salida de todos los errores (que realmente necesitamos), debe usar esta función de la siguiente manera: error_reporting (0)

Errores lógicos


Los errores lógicos son algunos de los peores. Porque estos son errores por descuido. Aparecen inesperadamente y, a veces, ni siquiera sabemos dónde está la raíz del problema. Estos son errores en la lógica del sitio.

Bueno, por ejemplo, puede olvidarse de verificar la presencia de datos de autorización en la sesión y las cookies para una de las páginas del panel de administración. Entonces abrieron el acceso a esta página para cualquier usuario.

En este caso, solo una cosa te salvará: al escribir el código del programa, piensa en cómo puedes hackearlo.

DDOS


DOS es un tipo de ataque a una técnica, en particular a una computadora. El ataque tiene como objetivo desactivar la máquina debido a una sobrecarga. DDOS difiere solo en que un mayor número de computadoras están involucradas en el ataque.

Es decir, John llama a sus amigos y juntos comienzan a enviar solicitudes al sitio. ¿Qué tiene que ver la botnet? Una botnet es una gran cantidad de computadoras infectadas, la peculiaridad es que un atacante puede controlar su trabajo hasta cierto punto (iniciar procesos, etc.). Envían tantas solicitudes que el servidor no puede soportar la carga y, en el mejor de los casos, comienza a funcionar muy lentamente o se niega por un período de tiempo no especificado.

La protección contra tales ataques la proporcionan los propios hosts o servicios especiales como Cloudflare.

Cómo funciona la protección: el servicio Cloudflare le proporciona sus propios servidores DNS a través de los cuales pasará el tráfico. Allí se filtra, pasando a través de algoritmos conocidos solo por los propietarios y desarrolladores del servicio. Y después de eso, el usuario llega a su sitio. Sí, por supuesto, hay operación del servidor, generación de páginas y todo lo demás, pero no estamos hablando de eso ahora.
Además, hablando de DDOS, uno no puede dejar de mencionar el bloqueo de la dirección IP que también proporciona Cloudflare.

Sí, todo esto no dará una garantía de protección del 100%, pero a veces aumentará las posibilidades de que su sitio permanezca a flote en el momento del ataque.

MITM


Man In The Middle es un tipo de ataque cuando un atacante intercepta tus paquetes y los engaña. Todos escuchamos que los datos se transmiten a través de la red en paquetes. Entonces, cuando se utiliza el protocolo http, los datos se transmiten en la forma habitual, no encriptada.

Por ejemplo, le escribes "hola" a un amigo y él recibe "envíame dinero, aquí está la billetera".

Fue para resolver este problema que se creó el protocolo https. Al usarlo, los datos se cifrarán y John no podrá hacer nada con el tráfico recibido.
Y para obtener el protocolo https para el sitio, debe obtener un certificado SSL. Bueno, o TLS. En general, TLS es esencialmente un receptor SSL, porque se basa en SSL 3.0. No hay diferencias significativas en su trabajo.

El certificado SSL proporciona el mismo Cloudflare, y de forma gratuita.

Puerta trasera


Un poco más de teoría. Para este tipo de ataque se puede implementar de muchas maneras y solo necesita captar la esencia. Backdoor es un tipo de ataque encubierto en el que el script hace algo en segundo plano. Muy a menudo, estos son complementos o temas de WordPress descargados de un torrent. El complemento / tema en sí funcionará de manera bastante adecuada, pero una parte del script agregada al código del complemento / tema hará algo en secreto. El mismo SPAM, por ejemplo. Esta es la razón de todas las advertencias sobre la indeseabilidad de descargar archivos desde un torrent.

En conclusión


Sí, por supuesto, este no es todo el rango de ataques. Pero este conocimiento ya aumentará la seguridad de sus proyectos.

Source: https://habr.com/ru/post/451420/


All Articles