node.jsctftiming-attack

nodejs: timing attack on "=="


Recently I came past this write up of a CTF on hackerone. In this writeup part of completing the challenge was to perform a timing attack. It spiked my interest and I wanted to create a webite that would be prone to a timing attack.

To do this I decided on nodejs, as that is what I am most familiar with. However, I was not able to replicate it, so I had to create my own strcmp function and induce time difference inside that function. For now the code looks like this

const { urlencoded } = require('body-parser');
const express = require('express')
const session = require('express-session')
const app = express()

app.use(express.static(__dirname+'/public'));
app.use(express.urlencoded({extended: false}))
app.set('view engine', 'ejs')

//im using this function with some bullcrap code in it to just extend the time it runs
const strcmp_test = (op1, op2) => {
    if(op1.length != op2.length) return false;
    for(let i = 0; i < op1.length; ++i) {
        if (op1[i] != op2[i]) return false;
        for(let bleh = 1; bleh < 10000000; bleh++){
            let test = 1000+bleh
            let test2 = test/155
        }
    }
    return true;
}

//MAIN SITE
app.get('/', (req,res) => {
    res.render('index')
})

app.get('/some/place', (req,res) => {
    res.render('a-view')
})


//LOGIN
app.post('/', (req,res) => {
    //I didnt care for setting up a db
    password_hash="c3e9fee675716951c547abe11e49e58190f9b1854924fa605b92d423be8716ab"
    username="admin"

    //if no body
    if(!req.body){
        console.log('NO BODY SENT')
        res.render('index')
    }
    //missing param
    if(!req.body.password || !req.body.login){
        console.log('MISSING PARAMETER')
        res.render('index')
    }
    //if password dont match. I know there is no check of username here, I guess that wont matter for the timing attack...?
    //before I used my own made function I used-->
    //if(req.body.login==username && req.body.password == password_hash) //
    if(strcmp_test(req.body.password,password_hash)){
        res.redirect('/some/place')
    } else {
        res.render('index')
    }
})



//IF NO ROUTE EXISTS
app.use(function(req, res, next) {
    res.status(404)
    res.render('error')
});

app.listen(3000)

To test the time I used pythons requests library. My code is pretty much similar to what was used in the writeup, which looks like this

def padding(h):
    r = h + ('f' * (64 - len(h)))
    return r
   
def send(payload):
    URL = 'http://127.0.0.1:8080/'
    r = requests.post(URL, data={'hash':payload})
    return r.elapsed.total_seconds()
    
if __name__ == '__main__':
    times = {}
    for x in range(0,0xff):
        times[format(x, 'x').zfill(2)] = send(padding(format(x, 'x').zfill(2)))
    print(times)

My question is: why didnt it work in my case to perform a timing attack by just using ==? And if it should work and I wanted to implement it, what would I need to do differently on the website?


Solution

  • I would imagine that the time required to set up and process an HTTP POST request is much greater than the time taken to compare two characters in a string.

    Try aggregating the time required for multiple calls with the same value. Perhaps then you'll see a difference:

    def send(payload):
        URL = 'http://127.0.0.1:8080/'
        t = 0.0
        # Might help to make one initial call to set up the HTTP pathway
        # requests.post(URL, data={'hash':payload})
        for _ in range(10000):
            t += requests.post(URL, data={'hash':payload}).elapsed.total_seconds()
        return t