La guía definitiva de XSS

Una guía para los ataques de secuencias de comandos entre sitios. ¿Cómo trabajan? ¿Cómo puedes prevenirlos?

¿Qué es XSS, también conocido como Cross Site Scripting?

XSS es el término que usamos para definir un tipo particular de ataque en el que un sitio web (su sitio web, si no presta atención) podría usarse como un vector para atacar a sus usuarios, debido a una manipulación insegura de la entrada del usuario.

Básicamente, un mal actor (el atacante) puede inyectar JavaScript, de una forma u otra, en nuestro sitio, aprovechando una vulnerabilidad que dejamos en nuestro código.

Usando esta vulnerabilidad, pueden robar información del usuario.

Según cómo se explote la vulnerabilidad XSS, tenemos 3 tipos principales de ataques XSS:

  • XSS persistente
  • XSS reflejado
  • XSS basado en DOM

¿Por qué es peligroso XSS?

Imagina que tienes un sitio web. Un atacante puede, de alguna manera, inyectar código JavaScript proporcionado por su sitio web y, sin su voluntad y sin que usted lo sepa, los navegadores de su usuario lo ejecutan.

Esto es muy peligroso.

Debido a su negligencia al corregir una vulnerabilidad XSS, su sitio puede usarse como un vector de ataque y la información de sus usuarios está en riesgo.

En particular, cuando cualquier persona puede inyectar JavaScript en una página, puede tener acceso a las cookies del usuario asociadas con el sitio web y leer cualquier información contenida en ellas. Y envíe esto a sus propios servidores. Pueden escuchar eventos de teclado y obtener acceso a cualquier persona que el usuario ingrese a la página y enviarla a los servidores del atacante usando fetch o XHR. Nombres de usuario y contraseñas, por ejemplo. También pueden manipular el DOM y con este poder pueden realizar muchas cosas malas.

¿XSS es un problema de frontend o backend?

Son ambos. Es un problema de arquitectura de un sitio web que involucra tanto al frontend como al backend.

Un ejemplo de ataque XSS

XSS está básicamente habilitado cuando le permite al usuario ingresar información, que usted almacena (en su backend) y luego la presenta.

Supongamos que tiene un blog y permite que los usuarios comenten en el blog. Si acepta ciegamente cualquier contenido que ingresen los usuarios, un usuario malintencionado puede intentar ingresar un fragmento de JavaScript, en su forma más básica incluida en<script></script>. Por ejemplo<script>alert('test')</script>.

Puede almacenar este comentario en su base de datos y, cuando se vuelva a cargar la página, de nuevo, si no tiene ninguna prevención, todos los usuarios que carguen esa página ejecutarán ese fragmento de JavaScript.

Utilicé un simplealert()llamar para hacer un ejemplo, pero como se indica arriba, un usuario puede ingresar cualquier tipo de secuencia de comandos. En este punto, el sitio está comprometido.

¿Qué es XSS persistente?

XSS persistente es uno de los 3 tipos de XSS que encontramos en la naturaleza. Es el que describí anteriormente en el ejemplo de la publicación del blog.

En este caso, el código de la vulnerabilidad se almacena en la base de datos o en alguna otra fuente, que usted mismo aloja.

Una vez que alguien puede ingresar un fragmento de JavaScript, su sitio lo publica automáticamente sin que se requiera ninguna otra acción.

¿Qué es XSS reflejado?

Reflected XSS es una forma de explotar una vulnerabilidad en su sitio sobre la marcha al proporcionar al usuario final un enlace que tiene un script en su interior.

De esta forma, el atacante proporciona un enlace similar a

yoursite.com/?example=<script>alert('test')</script>

If your site uses the example GET variable to perform something and display it on the page, and you don’t check and sanitize its value, now that script will be executed by the user’s browser.

A typical example is a search form. It might live in the /search URL and you might accept the search term using the GET term variable.

You might display the You searched for <term> string when someone searches for it. Now, if you didn’t sanitize the value, you now have a problem.

Spam/phishing emails are a common medium for this XSS attack. Of course, the bigger and more important the site, the more frequently hackers will try to hack it.

What is DOM-based XSS?

With persistent XSS, the attacking code must be sent to the server, where it can be (and hopefully it is) sanitized. With reflected XSS, the same is true.

DOM-based XSS is a kind of XSS where the malicious code is never sent to the server. It’s common for this to happen by using the fragment part of a URL, or by referencing document.URL/document.location.href. Some examples you find online don’t really work any more because modern browsers automatically escape JS in the address bar for us. They only work if you unescape it, which is kind of scary (don’t do it!).

Here’s a simple working example. Say you have a page listening on http://127.0.0.1:8081/testxss.html. Your client-side JavaScript looks at the test variable passed in the fragment part of the URL:

http://127.0.0.1:8081/testxss.html#test=something

The #test=something value is never send to the server. It’s only local. Persistent/reflected XSS would not work. But say your script accesses that value using:

const pos = document.URL.indexOf("test=") + 5;
const value = document.URL.substring(document.URL.indexOf("test=") + 5, document.URL.length)

and you write it directly into the DOM:

document.write(value)

All is fine, until someone calls the URL like this:

http://127.0.0.1:8081/testxss.html#test=

Now, thanks to the automatic escaping that happens by referencing document.URL nothing should happen in this specific case.

You’d get

%3Cscript%3Ealert('x')%3C/script%3E

printed to the page. The value is escaped, so it’s not interpreted as HTML.

But if for some reason you unescape the document.URL value, you have a problem now, as the JavaScript is run. Any JS can be run on your users browsers.

On older browser, this was a much bigger problem, since they didn’t auto-escape JS put into the address bar.

Are static sites vulnerable to XSS?

Yes! Any kind of site, actually. Because being static does not mean there is no information loaded from other sources. For example you might roll your own form or comments, even without a database.

Or, we might have a search functionality that accepts input from an HTTP GET or POST variable. You are not immune just by not having a database.

How can we prevent XSS?

There are 3 techniques we can use:

  • encoding
  • validation
  • CSP

Encoding is done to escape the data. Doing so, the browser will not interpret the JavaScript because, for example, <script> will be encoded to %3Cscript%3E.

Encoding, as a general rule, should be always done.

Server-side frameworks commonly provide helper functions to provide this functionality to you.

In client-side JavaScript we use a different encoding mechanism depending on the use case.

If you need to add content to an HTML element, the best way is to assign the user-generated input to that element using the textContent property. The browser will do all the escaping for you:

document.querySelector('#myElement').textContent = theUserGeneratedInput

If you need to create an element use document.createTextNode():

const el = document.createTextNode(theUserGeneratedInput)

If you need to add content to an HTML attribute, use the setAttribute() method of the element:

document.querySelector('#myElement').setAttribute('attributeName', theUserGeneratedInput)

If you need to add content to the URL, use the window.encodeURIComponent() function:

window.location.href = window.location.href + '?test=' + window.encodeURIComponent(theUserGeneratedInput)

Validation is usually done when you cannot use escaping to filter the input. A common example is a CMS that lets the user define the content of the page in HTML. You can’t escape that.

You either use a blacklisting or whitelisting strategy for validation. The difference is that with blacklisting you decide which tags you want to disallow. With whitelisting you decide which tags you want to allow. Whitelisting is safer because blacklisting is prone to errors, complex and also not future-proof.

CSP means Content Security Policy. It’s a new standard implemented by browsers to enforce only executing JavaScript code coming from secure and trusted sources, and you can disallow running inline JavaScript in your code. The kind of JavaScript that allowed the above XSS exploits, for example.

CSP is enabled by the Web Server, by adding the Content‑Security‑Policy HTTP Header when serving the page.


More security tutorials: