Inyección SQL y XSS: lo que saben los hackers de sombrero blanco sobre la confianza en la entrada del usuario

Los desarrolladores de software tienen mucho en mente. Hay una gran cantidad de preguntas que hacer cuando se trata de crear un sitio web o una aplicación: ¿Qué tecnologías usaremos? ¿Cómo se configurará la arquitectura? ¿Qué funciones necesitamos? ¿Cómo se verá la interfaz de usuario?

Especialmente en un mercado de software donde el envío de nuevas aplicaciones parece más una carrera por la reputación que un proceso bien meditado, una de las preguntas más importantes a menudo cae al final de la columna "Urgente": ¿cómo se asegurará nuestro producto?

Si está utilizando un marco robusto de código abierto para crear su producto (y si hay uno aplicable y disponible, ¿por qué no lo haría?), Es posible que ya se hayan resuelto algunos problemas básicos de seguridad, como los tokens CSRF y el cifrado de contraseñas. tú.

Aún así, los desarrolladores de rápido movimiento estarían bien servidos para repasar su conocimiento de las amenazas y trampas comunes, aunque solo sea para evitar algunos errores vergonzosos de novatos. Por lo general, el punto más débil en la seguridad de su software es usted.

Recientemente, me he interesado más en la seguridad de la información en general y en la práctica de la piratería ética en particular. Un pirata informático ético, a veces llamado pirata informático de "sombrero blanco", ya veces simplemente "pirata informático", es alguien que busca posibles vulnerabilidades de seguridad y las informa de manera responsable (en privado) a los propietarios del proyecto.

Por el contrario, un pirata informático malintencionado o de "sombrero negro", también llamado "cracker", es alguien que explota estas vulnerabilidades para divertirse o para beneficio personal.

Tanto los piratas informáticos de sombrero blanco como los de sombrero negro pueden usar las mismas herramientas y recursos, y generalmente tratan de llegar a lugares donde se supone que no deben estar. Pero los sombreros blancos hacen esto con permiso y con la intención de fortalecer las defensas en lugar de destruirlas. Los sombreros negros son los malos.

Cuando se trata de aprender a encontrar vulnerabilidades de seguridad, no debería sorprenderme que haya estado devorando toda la información que tengo en mis manos. Esta publicación es una destilación de algunas áreas clave que son específicamente útiles para los desarrolladores cuando manejan la entrada del usuario. Estas lecciones se han extraído colectivamente de estos excelentes recursos:

  • Las guías del proyecto de seguridad de aplicaciones web abiertas
  • La lista de reproducción Hacker101 del canal de YouTube de HackerOne
  • Web Hacking 101 de Peter Yaworski
  • Blog de Brute Logic
  • El canal de YouTube Computerphile
  • Videos con Jason Haddix (@jhaddix) y Tom Hudson (@tomnomnom) (dos hackers éticos consumados con metodologías diferentes, pero ambas efectivas)

Es posible que esté familiarizado con el eslogan "¡desinfecte sus entradas!" Sin embargo, como espero que demuestre esta publicación, desarrollar una aplicación con seguridad sólida no es tan sencillo.

Sugiero una frase alternativa: preste atención a sus entradas. Desarrollemos examinando los ataques más comunes que aprovechan las vulnerabilidades en esta área: inyección SQL y scripting entre sitios.

Ataques de inyección SQL

Si aún no está familiarizado con los ataques de inyección SQL (lenguaje de consulta estructurado), o SQLi, aquí hay un excelente video de explicación como si tuviera cinco en SQLi. Es posible que ya conozcas este ataque de Little Bobby Tables de xkcd.

Esencialmente, los actores malintencionados pueden enviar comandos SQL que afectan su aplicación a través de alguna entrada en su sitio, como un cuadro de búsqueda que extrae resultados de su base de datos. Los sitios codificados en PHP pueden ser especialmente susceptibles a estos, y un ataque SQL exitoso puede ser devastador para el software que se basa en una base de datos (como en, su tabla de Usuarios ahora es una olla de petunias).

Un monitor con un comando SQL Select que obtiene toda su base

Puede probar su propio sitio para ver si es susceptible a este tipo de ataque. (Por favor, pruebe solo los sitios de su propiedad, ya que ejecutar inyecciones SQL donde no tiene permiso para hacerlo es, posiblemente, ilegal en su localidad; y definitivamente, universalmente, no es muy divertido). Las siguientes cargas útiles pueden usarse para entradas de prueba:

  • ' OR 1='1 se evalúa como una constante verdadera y, cuando tiene éxito, devuelve todas las filas de la tabla.
  • ' AND 0='1 se evalúa como una constante falsa y, cuando tiene éxito, no devuelve filas.

Este video demuestra las pruebas anteriores y hace un gran trabajo al mostrar cuán impactante puede ser un ataque de inyección SQL.

Afortunadamente, hay formas de mitigar los ataques de inyección de SQL, y todos se reducen a un concepto básico: no confíe en la entrada del usuario.

Mitigación de inyección de SQL

Para mitigar eficazmente las inyecciones de SQL, los desarrolladores deben evitar que los usuarios puedan enviar con éxito comandos SQL sin procesar a cualquier parte del sitio.

Algunos marcos harán la mayor parte del trabajo pesado por usted. Por ejemplo, Django implementa el concepto de mapeo relacional de objetos, u ORM, con su uso de QuerySets. Podemos pensar en estos como funciones contenedoras que ayudan a su aplicación a consultar la base de datos utilizando métodos predefinidos que evitan el uso de SQL sin formato.

Sin embargo, poder utilizar un marco nunca es una garantía. Cuando se trata directamente de una base de datos, existen otros métodos que podemos utilizar para abstraer de forma segura nuestras consultas SQL de la entrada del usuario, aunque su eficacia varía. Estos son, por orden de mayor a menor preferencia, y con enlaces a ejemplos relevantes:

  1. Declaraciones preparadas con vinculación de variables (o consultas parametrizadas),
  2. Procedimientos almacenados; y
  3. Incluir en la lista blanca o escapar de la entrada del usuario.

Si desea implementar las técnicas anteriores, las hojas de referencia vinculadas son un excelente punto de partida para profundizar más. Basta decir que el uso de estas técnicas para obtener datos en lugar de utilizar consultas SQL sin procesar ayuda a minimizar las posibilidades de que SQL sea procesado por cualquier parte de su aplicación que reciba información de los usuarios, mitigando así los ataques de inyección SQL.

La batalla, sin embargo, está ganada a medias ...

Ataques de Cross Site Scripting (XSS)

Si eres un codificador malicioso, JavaScript es prácticamente tu mejor amigo. Los comandos correctos harán cualquier cosa que pueda hacer un usuario legítimo (e incluso algunas cosas que se supone que no pueden hacer) en una página web, a veces sin ninguna interacción por parte de un usuario real.

Los ataques de Cross Site Scripting, o XSS, ocurren cuando se inyecta código JavaScript en una página web y cambia el comportamiento de esa página. Sus efectos pueden variar desde incidentes molestos de broma hasta omisiones de autenticación más graves o robo de credenciales. Este informe de incidentes de Apache en 2010 es un buen ejemplo de cómo XSS puede encadenarse en un ataque más grande para hacerse cargo de cuentas y máquinas.

Una fiesta de baile HTML con un poco de JS cortando

XSS can occur on the server or on the client side, and generally comes in three flavors: DOM (Document Object Model) based, stored, and reflected XSS. The differences amount to where the attack payload is injected into the application.

DOM based XSS

DOM based XSS occurs when a JavaScript payload affects the structure, behavior, or content of the web page the user has loaded in their browser. These are most commonly executed through modified URLs, such as in phishing.

To see how easy it would be for injected JavaScript to manipulate a page, we can create a working example with an HTML web page. Try creating a file on your local system called xss-test.html (or whatever you like) with the following HTML and JavaScript code:

  My XSS Example   

Hello there!

var name = new URLSearchParams(document.location.search).get('name'); if (name !== 'null') { document.getElementById('greeting').innerHTML = 'Hello ' + name + '!'; }

This web page will display the title “Hello there!” unless it receives a URL parameter from a query string with a value for name. To see the script work, open the page in a browser with an appended URL parameter, like so:

file:///path/to/file/xss-test.html?name=Victoria

Fun, right? Our insecure (in the safety sense, not the emotional one) page takes the URL parameter value for name and displays it in the DOM. The page is expecting the value to be a nice friendly string, but what if we change it to something else? Since the page is owned by us and only exists on our local system, we can test it all we like. What happens if we change the name parameter to, say, ?

Una captura de pantalla del ejemplo de página XSS

This is just one example, largely based on one from Brute’s post, that demonstrates how an XSS attack could be executed. Funny pop-up alerts may be amusing, but JavaScript can do a lot of harm, including helping malicious attackers steal passwords and personal information.

Stored and reflected XSS

Stored XSS occurs when the attack payload is stored on the server, such as in a database. The attack affects a victim whenever that stored data is retrieved and rendered in the browser. For example, instead of using a URL query string, an attacker might update their profile page on a social site to include a hidden script in, say, their “About Me” section. The script, improperly stored on the site’s server, would successfully execute at a later time when another user views the attacker’s profile.

One of the most famous examples of this is the Samy worm that all but took over MySpace in 2005. It propogated by sending HTTP requests that replicated it onto a victim’s profile page whenever an infected profile was viewed. Within just 20 hours, it had spread to over a million users.

Reflected XSS similarly occurs when the injected payload travels to the server, however, the malicious code does not end up stored in a database. It is instead immediately returned to the browser by the web application.

An attack like this might be executed by luring the victim to click a malicious link that sends a request to the vulnerable website’s server. The server would then send a response to the attacker as well as the victim, which may result in the attacker being able to obtain passwords, or perpetrate actions that appear to originate from the victim.

XSS attack mitigation

In all of these cases, XSS attacks can be mitigated with two key strategies: validating form fields, and avoiding the direct injection of user input on the web page.

Validating form fields

Frameworks can again help us out when it comes to making sure that user-submitted forms are on the up-and-up. One example is Django’s built-in Field classes, which provide fields that validate to some commonly used types and also specify sane defaults. Django’s EmailField, for instance, uses a set of rules to determine if the input provided is a valid email. If the submitted string has characters in it that are not typically present in email addresses, or if it doesn’t imitate the common format of an email address, then Django won’t consider the field valid and the form will not be submitted.

If relying on a framework isn’t an option, we can implement our own input validation. This can be accomplished with a few different techniques, including type conversion, for example, ensuring that a number is of type int(); checking minimum and maximum range values for numbers and lengths for strings; using a pre-defined array of choices that avoids arbitrary input, for example, months of the year; and checking data against strict regular expressions.

Thankfully, we needn’t start from scratch. Open source resources are available to help, such as the OWASP Validation Regex Repository, which provides patterns to match against for some common forms of data. Many programming languages offer validation libraries specific to their syntax, and we can find plenty of these on GitHub. Additionally, the XSS Filter Evasion Cheat Sheet has a couple suggestions for test payloads we can use to test our existing applications.

While it may seem tedious, properly implemented input validation can protect our application from being susceptible to XSS.

Avoiding direct injection

Elements of an application that directly return user input to the browser may not, on a casual inspection, be obvious. We can determine areas of our application that may be at risk by exploring a few questions:

  • How does data flow through our application?
  • What does a user expect to happen when they interact with this input?
  • Where on our page does data appear? Does it become embedded in a string or an attribute?

Here are some sample payloads that we can play with in order to test inputs on our site (again, only our own site!) courtesy of Hacker101. The successful execution of any of these samples can indicate a possible XSS vulnerability due to direct injection.

  • ">

    test

  • '+alert(1)+'
  • "onmouserover="alert(1)
  • //"onmouseover="alert(1)

As a general rule, if you are able to design around directly injecting input, do so. Alternatively, be sure to completely understand the effect of the methods you choose; for example, using innerText instead of innerHTML in JavaScript will ensure that content will be set as plain text instead of (potentially vulnerable) HTML.

Pay attention to your inputs

Los desarrolladores de software se encuentran en una marcada desventaja cuando se trata de competir con hackers maliciosos o de sombrero negro. A pesar de todo el trabajo que hacemos para proteger todas y cada una de las entradas que podrían comprometer nuestra aplicación, un atacante solo necesita encontrar la que nos perdimos. Es como instalar cerrojos en todas las puertas, ¡pero dejando una ventana abierta!

Sin embargo, si aprendemos a pensar de la misma manera que un atacante, podemos preparar mejor nuestro software para enfrentarse a los malos actores. Por emocionante que sea enviar funciones lo más rápido posible, evitaremos acumular una gran cantidad de deuda de seguridad si nos tomamos el tiempo de antemano para pensar en el flujo de nuestra aplicación, seguir los datos y prestar atención a nuestras entradas.