
La empresa Variti desarrolla protección contra bots y ataques DDoS, además de realizar pruebas de estrés y carga. En la conferencia HighLoad++ 2018, hablamos sobre cómo proteger los recursos de diferentes tipos de ataques. En resumen: aíslen partes del sistema, utilicen servicios en la nube y CDN, y mantengan actualizaciones regulares. Pero sin empresas especializadas en protección, no podrán hacerlo por sí solos 🙂
Antes de leer el texto, pueden revisar los breves puntos clave .
Y si no les gusta leer o simplemente quieren ver el video, la grabación de nuestra presentación está más abajo bajo el spoiler.
Grabación de la presentación

Muchas empresas ya saben cómo realizar pruebas de carga, pero no todas hacen pruebas de estrés. Algunos de nuestros clientes piensan que su sitio es invulnerable porque tienen un sistema de alta carga, el cual protege bien contra ataques. Sin embargo, nosotros mostramos que esto no es del todo cierto.
Por supuesto, antes de realizar las pruebas, obtenemos permiso del cliente, con firma y sello, y con nuestra ayuda, no se puede realizar un ataque DDoS a nadie. Las pruebas se llevan a cabo en el momento elegido por el cliente, cuando la cantidad de visitantes a su recurso es mínima, y los problemas de acceso no afectarán a los clientes. Además, dado que siempre puede surgir algún inconveniente durante las pruebas, mantenemos contacto permanente con el cliente. Esto permite no solo informar sobre los resultados alcanzados, sino también hacer ajustes durante la prueba. Al finalizar la prueba, siempre elaboramos un informe en el que señalamos las desventajas encontradas y damos recomendaciones para solucionar las debilidades del sitio.
Cómo trabajamos
Al realizar las pruebas, emulamos un botnet. Dado que trabajamos con clientes que no se encuentran en nuestras redes, para que la prueba no termine en el primer minuto debido a límites o protecciones activadas, generamos carga no desde una sola IP, sino desde nuestra propia subred. Además, para crear una carga significativa, contamos con nuestro potente servidor de pruebas.
Postulados
Mucho no significa bueno
Cuanto menor sea la carga que podamos llevar al recurso hasta su fallo, mejor. Si logramos que el sitio deje de funcionar con una solicitud por segundo, o incluso con una por minuto, será maravilloso. Porque por ley de Murphy, los usuarios o atacantes caerán accidentalmente en esta vulnerabilidad.
Un fallo parcial es mejor que uno total.
Siempre aconsejamos hacer que los sistemas sean heterogéneos. Además, deben separarse a nivel físico, no solo mediante contenedores. En caso de separación física, incluso si algo falla en el sitio, hay una gran probabilidad de que no deje de funcionar por completo, y los usuarios mantendrán acceso a al menos parte de las funcionalidades.
Una arquitectura correcta es la base de la resiliencia.
La tolerancia a fallos del recurso y su capacidad para resistir ataques y cargas deben ser contempladas en la etapa de diseño, en realidad, en la fase de trazar los primeros diagramas de bloques en el cuaderno. Porque si se introducen errores fatales, es posible corregirlos más adelante, pero resulta muy complicado.
No solo debe ser bueno el código, sino también la configuración.
Muchos piensan que un buen equipo de desarrollo garantiza la resiliencia del servicio. Un buen equipo de desarrollo es, de hecho, necesario, pero también debe haber una buena operación, un buen DevOps. Es decir, se necesitan especialistas que configuren correctamente Linux y la red, que escriban bien las configuraciones en nginx, establezcan límites, etc. De lo contrario, el recurso funcionará bien solo en pruebas, y en producción, en algún momento, todo se romperá.
Diferencias entre pruebas de carga y de estrés.
Las pruebas de carga permiten identificar los límites de funcionamiento del sistema. Las pruebas de estrés están dirigidas a encontrar debilidades en el sistema y se utilizan para romper dicho sistema y observar cómo se comporta durante el fallo de ciertas partes. En este sentido, la naturaleza de la carga suele ser desconocida para el cliente antes del inicio de las pruebas de estrés.
Características distintivas de los ataques L7.
Los tipos de carga se dividen comúnmente en cargas a nivel L7 y L3&4. L7 es carga a nivel de aplicación, y generalmente se refiere solo a HTTP, mientras que nosotros entendemos cualquier carga a nivel del protocolo TCP.
Los ataques L7 tienen características distintivas. Primero, se dirigen directamente a la aplicación, por lo que es poco probable que se puedan mitigar mediante medios de red. Estos ataques utilizan lógica y, gracias a ello, consumen recursos como CPU, memoria, disco, bases de datos y otros recursos de manera muy efectiva incluso con tráfico reducido.
Inundación HTTP
En caso de cualquier ataque, es más fácil generar carga que procesarla, y esto también es cierto para L7. El tráfico del ataque no siempre se puede distinguir fácilmente del tráfico legítimo, y a menudo esto se logra mediante la frecuencia, pero si todo está bien planificado, es imposible determinar por los registros dónde está el ataque y dónde están las solicitudes legítimas.
Como primer ejemplo, consideremos el ataque de inundación HTTP. El gráfico muestra que, por lo general, estos ataques son muy potentes; en el ejemplo a continuación, el número pico de solicitudes superó los 600,000 por minuto.

La inundación HTTP es la forma más sencilla de generar carga. Por lo general, se utiliza alguna herramienta de prueba de carga, como ApacheBench, y se especifican la solicitud y el objetivo. Con este enfoque simple, hay una gran probabilidad de encontrarse con el almacenamiento en caché del servidor, pero es fácil de eludir. Por ejemplo, añadiendo cadenas aleatorias a la solicitud, lo que obligará al servidor a entregar constantemente una página nueva.
Tampoco se debe olvidar el user-agent en el proceso de generación de carga. Muchos user-agent de herramientas de prueba populares son filtrados por los administradores del sistema, y en tal caso, la carga puede no llegar al backend. Se puede mejorar significativamente el resultado insertando en la solicitud un encabezado más o menos válido de un navegador.
A pesar de la simplicidad del ataque, la inundación HTTP también tiene sus desventajas. Primero, se requieren grandes recursos para generar la carga. En segundo lugar, estos ataques son muy fáciles de detectar, especialmente si provienen de una única dirección. Como resultado, las solicitudes comienzan a ser filtradas ya sea por los administradores del sistema o incluso a nivel de proveedor.
Qué buscar
Para reducir la cantidad de solicitudes por segundo sin perder eficiencia, es necesario usar un poco de imaginación e investigar el sitio. Se puede sobrecargar no solo el canal o el servidor, sino también partes específicas de la aplicación, como bases de datos o sistemas de archivos. También se pueden buscar lugares en el sitio que realicen grandes cálculos: calculadoras, páginas de selección de productos y demás. Finalmente, a menudo hay un script php en el sitio que genera una página a partir de cientos de miles de líneas. Tal script también carga significativamente el servidor y puede convertirse en un objetivo de ataque.
Dónde buscar
Cuando examinamos un recurso antes de realizar pruebas, primero miramos, por supuesto, el propio sitio. Buscamos todo tipo de campos de entrada, archivos pesados —en general, todo lo que pueda crear problemas para el recurso y ralentizar su funcionamiento. Aquí son útiles las herramientas de desarrollo simples en Google Chrome y Firefox, que muestran los tiempos de respuesta de la página.
También escaneamos subdominios. Por ejemplo, hay una tienda en línea, abc.com, y tiene un subdominio admin.abc.com. Es probable que esta sea un área de administración con autenticación, pero si se le aplica carga, puede crear problemas para el recurso principal.
El sitio puede tener un subdominio api.abc.com. Seguramente, es un recurso para aplicaciones móviles. La aplicación se puede encontrar en la App Store o Google Play, establecer un punto de acceso especial, descomponer la API y registrar cuentas de prueba. El problema es que mucha gente piensa que todo lo que está protegido por autenticación es invulnerable a ataques de denegación de servicio. Supuestamente, la autenticación es la mejor CAPTCHA, pero no es así. Crear de 10 a 20 cuentas de prueba es fácil, y al hacerlo, obtenemos acceso a funcionalidades complejas y no protegidas.
Desde luego, revisamos la historia, el robots.txt y WebArchive, ViewDNS, buscando versiones antiguas del recurso. A veces, los desarrolladores lanzan, digamos, mail2.yandex.net, pero una versión antigua, mail.yandex.net, permanece. Este mail.yandex.net deja de ser soportado, no se destinan recursos de desarrollo, pero sigue consumiendo la base de datos. Por lo tanto, con la versión antigua, se pueden utilizar eficazmente los recursos del backend y todo lo que está detrás del diseño. Claro, esto no siempre sucede, pero nos encontramos con algo similar bastante a menudo.
Por supuesto, analizamos todos los parámetros de la solicitud, la estructura de las cookies. Puedes, por ejemplo, insertar un valor en un array JSON dentro de las cookies, crear una gran anidación y hacer que el recurso funcione de manera ineficiente por un largo tiempo.
Carga en la búsqueda
Lo primero que se me ocurre al investigar un sitio es cargar la base de datos, ya que casi todos los sitios tienen búsqueda y, desafortunadamente, casi todos están mal protegidos. Por alguna razón, los desarrolladores no le prestan suficiente atención a la búsqueda. Pero hay una recomendación: no debes hacer solicitudes similares, porque puedes encontrarte con la caché, al igual que con un ataque de inundación HTTP.
Hacer solicitudes aleatorias en la base de datos tampoco es siempre efectivo. Es mucho mejor crear una lista de palabras clave relacionadas con la búsqueda. Regresando al ejemplo de una tienda en línea: supongamos que el sitio vende neumáticos para automóviles y permite especificar el radio de las llantas, el tipo de coche y otros parámetros. Así, las combinaciones de palabras relevantes harán que la base de datos trabaje en condiciones mucho más complejas.
Además, debes usar paginación: es mucho más difícil para la búsqueda devolver la penúltima página de resultados que la primera. Es decir, con la paginación puedes diversificar un poco la carga.
En el ejemplo a continuación mostramos la carga en la búsqueda. Se puede ver que desde el primer segundo de la prueba a una velocidad de diez solicitudes por segundo, el sitio colapsó y no respondió.

¿Y si no hay búsqueda?
Si no hay búsqueda, no significa que el sitio no contenga otros campos de entrada vulnerables. Un campo así puede ser la autorización. Actualmente, los desarrolladores disfrutan crear hashes complejos para proteger la base de datos de inicios de sesión de ataques de tablas arcoíris. Esto es bueno, pero tales hashes consumen grandes recursos de CPU. Un gran flujo de falsas autorizaciones provoca la falla del procesador y, como consecuencia, el sitio deja de funcionar.
La presencia en el sitio de diversos formularios para comentarios y retroalimentación es una oportunidad para enviar textos muy grandes o simplemente crear spam masivo. A veces, los sitios aceptan archivos adjuntos, incluso en formato gzip. En este caso, tomamos un archivo de 1 TB, lo comprimimos con gzip hasta unos pocos bytes o kilobytes y lo enviamos al sitio. Luego, se descomprime y se obtiene un efecto muy interesante.
Rest API
Es importante dedicar un poco de atención a servicios populares en la actualidad, como el Rest API. Proteger un Rest API es mucho más complicado que proteger un sitio web convencional. Incluso los métodos más básicos de protección contra ataques de fuerza bruta y otras actividades ilegítimas no funcionan en un Rest API.
Un Rest API es muy fácil de romper, porque se conecta directamente a la base de datos. Además, la caída de un servicio así conlleva consecuencias bastante graves para el negocio. Lo cierto es que un Rest API generalmente está vinculado no solo al sitio principal, sino también a la aplicación móvil y a algunos recursos internos del negocio. Y si todo esto falla, el impacto es mucho mayor que en el caso de que simplemente se caiga un sitio web.
Carga de contenido pesado
Si nos proponen probar alguna aplicación web sencilla, una landing page o un sitio de presentación que no tiene funcionalidades complejas, buscamos contenido pesado. Por ejemplo, grandes imágenes que el servidor entrega, archivos binarios, documentación en pdf; intentamos descargar todo esto. Tales pruebas cargan bien el sistema de archivos y saturan los canales, lo que las hace efectivas. Es decir, incluso si no logras tirar el servidor descargando un archivo grande a bajas velocidades, simplemente saturarás el canal del servidor objetivo y, de ese modo, ocurrirá una denegación de servicio.
En el ejemplo de esta prueba se puede ver que a 30 RPS el sitio dejó de responder o devolvió errores 500 del servidor.

No debemos olvidar la configuración de los servidores. A menudo se observa que una persona compra una máquina virtual, instala Apache, configura todo por defecto, coloca una aplicación php, y a continuación se puede ver el resultado.

Aquí la carga fue a la raíz y alcanzó solo 10 RPS. Esperamos 5 minutos y el servidor se cayó. Al final, no se sabe con certeza por qué se cayó, pero se presume que simplemente se quedó sin memoria y por eso dejó de responder.
Basado en ondas
En los últimos uno o dos años, los ataques de ola se han vuelto bastante populares. Esto se debe a que muchas organizaciones compran diversos equipos para protegerse contra DDoS, los cuales requieren un tiempo determinado para acumular estadísticas antes de comenzar a filtrar el ataque. Es decir, no filtran el ataque durante los primeros 30-40 segundos, ya que están recopilando datos y aprendiendo. Por lo tanto, durante esos 30-40 segundos, se puede lanzar tanto tráfico a la página web que el recurso estará inactivo durante un largo tiempo, hasta que se gestionen todas las solicitudes.
En el caso del ataque a continuación, hubo un intervalo de 10 minutos, después del cual llegó una nueva y modificada oleada de ataque.

Es decir, la protección aprendió y puso en marcha la filtración, pero llegó una nueva oleada de ataque completamente diferente, y la protección comenzó de nuevo a aprender. De hecho, la filtración deja de funcionar, la protección se vuelve ineficaz y la página web se vuelve inaccesible.
Los ataques de ola se caracterizan por tener valores muy altos en el pico, pudiendo alcanzar hasta cien mil o un millón de solicitudes por segundo, en el caso de L7. Si hablamos de L3&4, puede haber cientos de gigabits de tráfico, o, en consecuencia, cientos de mpps, si lo contamos en paquetes.
El problema de estos ataques radica en la sincronización. Los ataques provienen de una botnet y, para crear un pico muy grande de una sola vez, se requiere un alto grado de sincronización. Y esta coordinación no siempre se logra: a veces, el resultado es un pico parabólico que se ve bastante lamentable.
No solo HTTP
Aparte de HTTP a nivel L7, también nos gusta explotar otros protocolos. Por lo general, un sitio web normal, y más aún un hosting común, expone protocolos de correo y MySQL. Los protocolos de correo son menos susceptibles a cargas que las bases de datos, pero también se pueden sobrecargar de manera bastante efectiva, resultando en un CPU sobrecargado en el servidor.
De manera bastante realista, con la vulnerabilidad SSH de 2016 logramos tener éxito. Ahora, esta vulnerabilidad está casi corregida por todos, pero eso no significa que no se pueda aplicar carga a SSH. Se puede. Simplemente se aplica una carga enorme de autorizaciones, y SSH consume casi todo el CPU en el servidor, haciendo que la página web colapse ya con una o dos solicitudes por segundo. Por lo tanto, estas una o dos solicitudes no se pueden distinguir de una carga legítima según los registros.
Siguen siendo relevantes muchas conexiones que abrimos en los servidores. Antes, esto era un problema de Apache, y ahora es un defecto real en nginx, ya que a menudo se configura por defecto. El número de conexiones que nginx puede mantener abiertas está limitado; por lo tanto, al alcanzar este límite, nginx ya no aceptará nuevas conexiones, y como resultado, el sitio web deja de funcionar.
Nuestro clúster de prueba tiene suficiente CPU para atacar el apretón de manos SSL. De hecho, como demuestra la práctica, a veces los botnets también disfrutan hacer esto. Por un lado, es claro que no se puede prescindir de SSL, debido a los resultados de Google, la clasificación y la seguridad. Por otro lado, SSL tiene, lamentablemente, un problema con la CPU.
L3&4
Cuando hablamos de un ataque en los niveles L3&4, generalmente nos referimos a un ataque en el nivel de canal. Este tipo de carga casi siempre es distinguible de la legítima, a menos que se trate de un ataque SYN-flood. El problema de los ataques SYN-flood para las medidas de protección es su gran volumen. La máxima cantidad en L3&4 ha sido de 1.5-2 Tbit/s. Tal tráfico es muy difícil de manejar incluso para grandes empresas, incluyendo Oracle y Google.
SYN y SYN-ACK son paquetes utilizados para establecer una conexión. Por lo tanto, SYN-flood es difícil de distinguir de la carga legítima: no está claro si es un SYN que ha llegado para establecer una conexión o parte de un flood.
UDP-flood
Normalmente, los atacantes no tienen la capacidad que tenemos nosotros, por lo que para organizar ataques se puede utilizar la amplificación. Esto significa que el atacante escanea Internet y encuentra servidores que son vulnerables o que están mal configurados, que, por ejemplo, responden a un único paquete SYN con tres SYN-ACK. Falsificando la dirección de origen desde la dirección del servidor objetivo, se puede aumentar la capacidad, digamos, por tres, y redirigir el tráfico hacia la víctima.

El problema de las amplificaciones radica en su difícil detección. Entre los ejemplos recientes se encuentra el famoso caso del memcached vulnerable. Además, ahora hay muchos dispositivos IoT, cámaras IP que también están configuradas incorrectamente por defecto, así que a menudo los atacantes utilizan estos dispositivos para llevar a cabo ataques.

SYN-flood complicado
El SYN-flood es probablemente el tipo de ataque más interesante desde el punto de vista de un desarrollador. El problema es que a menudo los administradores de sistemas utilizan el bloqueo por IP como forma de protección. Así, no solo los administradores que actúan según scripts sufren, sino que, lamentablemente, también algunas soluciones de seguridad, que se compran a alto costo.
Este método puede resultar desastroso, ya que si los atacantes sustituyen IP, la empresa bloqueará su propia subred. Cuando un cortafuegos bloquee su propio clúster, las interacciones externas colapsarán y el recurso fallará.
Además, lograr bloquear la propia red no es complicado. Si en la oficina del cliente hay una red WI-Fi, o si la operatividad de los recursos se mide mediante varios sistemas de monitorización, tomamos la dirección IP de este sistema de monitorización o del cliente de Wi-Fi de la oficina y la usamos como fuente. Así, el recurso aparentemente está disponible, pero las direcciones IP objetivo están bloqueadas. De este modo, puede quedar bloqueada la red Wi-Fi de la conferencia HighLoad, donde se presenta un nuevo producto de la empresa, lo que conlleva ciertos costos económicos y empresariales.
Durante las pruebas no podemos utilizar amplificación a través de memcached con recursos externos, porque hay acuerdos sobre la entrega de tráfico solo a direcciones IP permitidas. Por lo tanto, utilizamos amplificación a través de SYN y SYN-ACK, cuando por el envío de un SYN, el sistema responde con dos o tres SYN-ACK, y así la ataque se multiplica por dos o tres.
Herramientas
Una de las herramientas principales que utilizamos para la carga a nivel L7 es Yandex-tank. En particular, se utiliza un fantasma como cañón, y hay varios scripts para generar munición y para analizar resultados.
Para analizar el tráfico de red se utiliza Tcpdump, y para analizar el servidor — Nmap. Para crear carga a nivel L3&4 se usa OpenSSL y un poco de magia propia con la biblioteca DPDK. DPDK es una biblioteca de Intel que permite trabajar con la interfaz de red, evitando el stack de Linux, aumentando así la eficiencia. Naturalmente, usamos DPDK no solo a nivel L3&4, sino también a nivel L7, ya que permite crear un flujo de carga muy alto, de varios millones de solicitudes por segundo desde una sola máquina.
También utilizamos ciertos generadores de tráfico y herramientas especiales que desarrollamos para pruebas específicas. Recordando la vulnerabilidad en SSH, el conjunto mencionado anteriormente no puede ser explotado. Si atacamos el protocolo de correo, utilizamos utilidades de correo o simplemente escribimos scripts para ellas.
Conclusiones
Como conclusión, me gustaría decir:
- Además de las pruebas de carga clásicas, es imprescindible realizar también pruebas de estrés. Tenemos un ejemplo real en el que un subcontratista de un socio realizó solo pruebas de carga. Estas mostraron que el recurso soporta la carga normal. Pero luego apareció una carga anormal, y los visitantes del sitio comenzaron a utilizar el recurso de manera diferente, y al final el subcontratista colapsó. Por lo tanto, es importante buscar vulnerabilidades, incluso si ya estás protegido contra ataques DDoS.
- Es necesario aislar unas partes del sistema de otras. Si tienes búsqueda, debes trasladarla a máquinas separadas, es decir, ni siquiera en Docker. Porque si falla la búsqueda o la autorización, al menos algo seguirá funcionando. En el caso de una tienda en línea, los usuarios seguirán encontrando productos en el catálogo, accediendo desde agregadores, comprando, si ya están autorizados, o autenticándose a través de OAuth2.
- No debes menospreciar los diversos servicios en la nube.
- Utiliza CDN no solo para optimizar la latencia de la red, sino también como medio de protección contra ataques de agotamiento de canal y simplemente contra floods en la estática.
- Es necesario utilizar servicios de protección especializados. No podrás protegerte de ataques L3&4 a nivel de canal, porque probablemente no tengas un canal suficiente. También es poco probable que te defienda de ataques L7, ya que pueden ser muy grandes. Además, detectar pequeños ataques es en realidad prerrogativa de servicios especializados y algoritmos específicos.
- Actualízate regularmente. Esto no solo se refiere al núcleo, sino también al demonio SSH, especialmente si están abiertos al exterior. En general, hay que actualizar todo, porque es poco probable que puedas rastrear vulnerabilidades por tu cuenta.
Fuente: habr.com
