Search code examples
javascriptnode.jshandlebars.jseval

Safe way to let users register handelbars helpers in nodejs


I have a node js web app that is using handlebars. Users are asking me to let them register their own handlebars helpers.

I'm quite hesitant about letting them do it... but I'll give it a go if there is a secure way of doing it so.

var Handlebars = require("handlebars");
var fs = require("fs");
var content = fs.readFileSync("template.html", "utf8");


//This helper will be posted by the user
var userHandlebarsHelpers = "Handlebars.registerHelper('foo', function(value) { return 'Foo' + value; });"

//eval(userHandlebarsHelpers); This I do not like! Eval is evil

//Compile handlebars with user submitted Helpers
var template = Handlebars.compile(content);
var handleBarContent = template({ foo: bar });


//Save compiled template and some extra code.

Thank you in advance!


Solution

  • Following @jfriend00 input and after some serious testing I found a way to do it using nodejs vm module.

    Users will input their helpers with this format:

    [[HBHELPER 'customHelper' value]]
       value.replace(/[0-9]/g, "");
    [[/HBHELPER]]
    
    [[HBHELPER 'modulus' index mod result block]]
       if(parseInt(index) % mod === parseInt(result))
          block.fn(this);
    [[/HBHELPER]]
    
    //This will throw an error when executed "Script execution timed out."
    [[HBHELPER 'infiniteLoop' value]] 
       while(1){}
    [[/HBHELPER]]
    

    I translate that block into this and execute it:

     Handlebars.registerHelper('customHelper', function(value) {
        //All the code is executed inside the VM
        return vm.runInNewContext('value.replace(/[0-9]/g, "");', {
            value: value
        }, {
            timeout: 1000
        });
    });
    
    Handlebars.registerHelper('modulus', function(index, mod, result, block) {
        return vm.runInNewContext('if(parseInt(index) % mod === parseInt(result)) block.fn(this);', {
            index: index,
            mod: mod,
            result: result,
            block: block
        }, {
            timeout: 1000
        });
    });
    
    Handlebars.registerHelper('infiniteLoop', function(value) {
        //Error
        return vm.runInNewContext('while(1){}', {
            value: value
        }, {
            timeout: 1000
        });
    });
    

    I made multiple tests so far, trying to delete files, require modules, infinite loops. Everything is going perfectly, all those operations failed.

    Running the handlebar helper callback function in a VM is what made this work for me, because my main problem using VM's and running the whole code inside was adding those helpers to my global Handlebars object.

    I'll update if I found a way to exploit it.