JSReg down but not out

A few months ago some very talented people called Jonas Magazinius aka @internot_ and Alexey Silin aka @lever_one broke JSReg. Maybe broke is the wrong word obliterated is more accurate. This was very humbling for me, I knew it wasn’t perfect this is why I tried to tempt them to break it by stating it was unbreakable 🙂 and it worked, they did and broke it so much more than I was hoping. I must admit I considered abandoning the project since the root of code was rotten. Over the months after I was slowly rewriting and taking a different approach, today I hit some inspiration and managed to finish off what I had started.

The core of the last version was centred around RegExes, I had to slightly adjust this path and allow a mix of regexes and states. Since I’m a rookie at parsers, I decided to take the approach of tokenizing and rewriting in one step. I read many recommendations that you could tokenize your code first then parse it but I feel it can be done at once.

So the modified loop looks something like this:-

pos = 0;
left = 0;
while(pos < code.length) {
chr = code.substr(pos, 1);
prevText = code.substr(0, pos);
currentText = code.substr(pos);
next = code.substr(pos+1, 1);
prev = code.substr(pos-1, 1);
....
} else if(test(objectIdentifiers, currentText)) {
matchStr = match(objectIdentifiers, currentText);
pos += matchStr.length;
output += rewriteObjectIdentifiers(matchStr);
left = 0;
lastState = 'objectIdentifiers';
....

Instead of using just a String replace I loop char by char and move the position along if the regex matches along with any parsing I do without regexes. This way I can have a nice mixture of them both. I had to revise the regex object detection with it’s own state machine since it’s very difficult to parse especially with IE bugs that Lever_One found. Consider the following regex on IE7 (provided by Lever_one):


/[]/]/,alert(1)

So IE7 seems to continue parsing as a regex when it encounters a blank regex class. Thankfully fixed in IE9 standards.

To fix other bugs I started to track parenthesis and check for curly braces after inside for/if statements etc, this fixed many of the vectors were no curlys confused the state of JSReg. Because I’ve collected a huge amount of vector data thanks to Jonas and Alexey, I created a automated check of previous vectors. If you trying to write something similar to JSReg then the following might help you.


//sandbox vectors by Jonas Magazinius, Alexey Silin and other sla.ckers
var tests = ['/lo\\[/;log($placeholder$);;/lo/;','/lo\\/;log($placeholder$);;/lo/;',
'/[/;lo="]/;log($placeholder$);//";','/lo\\//,/;lo=/;log($placeholder$);//;',
"lo=''/**/;log($placeholder$);//';",'/**/log/**/($placeholder$/**//**/);',
"/lo/+/'//log($placeholder$);//';","/lo='/?log($placeholder$);:''",
"[/lo='/];log($placeholder$+'');","lo=';log($placeholder$+'')",
";log($placeholder$+'')","1/ /x='/;log($placeholder$+'');",
"1/(/x='/);log($placeholder$+'');","0/0/*lo='*/+log($placeholder$+'')",
"[/lo='/];log($placeholder$+'');",'/[;\nlo="]/&log($placeholder$+"");',
'alert(1===/x/ /1+/**/log($placeholder$)/**/)',"(0)[/[']/+log($placeholder$);+']/']",
'(/[/]/)[/(\/\))\/+log($placeholder$);+"\/"/i]',"[/lo='/];log($placeholder$+'');",
'/\\\\\',"m","/;lo=");log($placeholder$);//";','<>lo="<\/>;log($placeholder$);;""',
';log($placeholder$);;lo="";','<{\'_\'} {\'o\'}="\\"/>,log($placeholder$);//"\n\n\'/,log($placeholder$);,"lo/";',
"1/\n/lo='/,log($placeholder$+'')","<È>x=';log($placeholder$+'')","lo=';log($placeholder$+'')",
";log($placeholder$+'')","1/ /x='/;log($placeholder$+'');",
"1/(/x='/);log($placeholder$+'');", "0/0/*lo='*/+log($placeholder$+'')",
"{}/lo='/,log($placeholder$);//'",'_:/\\[/+log($placeholder$);/i','{}/\[/+log($placeholder$);/i','typeof/\[/+log($placeholder$);/i',
'0?0:/\\[/+log($placeholder$);/i','delete/\\[/+log($placeholder$);/i','void/\\[/+log($placeholder$);/i','with({})/\\[/+log($placeholder$);/i',
'if(1)/\\[/+log($placeholder$);/i','while(1)/\\[/+log($placeholder$);/i','try{/\\[/+log($placeholder$);/i}catch(a){}',
'throw/\\[/+log($placeholder$);/i','(function(){return/\\[/+log($placeholder$);/i})()','do{/\\[/+log($placeholder$);/i}while(1)',
'switch(0){case 0:/\\[/+log($placeholder$);/i}','_:/(]\\[)/+log($placeholder$);//]',"_:/'/+log($placeholder$);/i",
"_:<{'x'}>'+log($placeholder$);//'","_:/{/;log($placeholder$);//","_:/\\(/;log($placeholder$);//","_:/ /+log($placeholder$);//",
'+{}/ /lo="/,log($placeholder$);//"',"/[]/,'lo]/,log($placeholder$);//'",
"/[^]/,'lo]/,log($placeholder$);//'","$='@mozilla.org/js/function';\n$::['log']($placeholder$);","true/'/'+log($placeholder$);+''","this/'/'+log($placeholder$);+''",
"undefined/'/'+log($placeholder$);+''","null/'/'+log($placeholder$);+''","false/'/'+log($placeholder$);+''","Infinity/'/'+log($placeholder$);+''","NaN/'/'+log($placeholder$);+''",
"+{}\n{}/lo='/,log($placeholder$);//'","0?0:{}/log($placeholder$);/i","switch(0){case {}/log($placeholder$);/i:1}",
"+function(){1}/log($placeholder$);/lo","~\n{}/log($placeholder$);/lo","lo:{}/'/,log($placeholder$);//'",
"lo:function x(){1}/'/,log($placeholder$);//'","0\n{}/lo='/,log($placeholder$);//'",
"i=0,i++\n{}/lo='/,log($placeholder$);//'","var È=È/log($placeholder$);/È",
"lo:{}/'/,log($placeholder$)//'","lo:function x(){1}/'/,log($placeholder$)//'",
"0\n{}/lo='/,log($placeholder$)//'","i=0,i++\n{}/lo='/,log($placeholder$)//'",
"(function lo(){1})(1,/'/);log($placeholder$)//'","{}'lo'.replace(/'/,\"\"),log($placeholder$)//'",
"var È=È/log($placeholder$)/È","var lo=0?\nlo:{}/log($placeholder$)/0","~{\nlo1:{lo2:1}/log($placeholder$)/1\n}",
"(\n{lo:1}/log($placeholder$)/1\n)","{lo1:\n{lo2:1}/'/,log($placeholder$)//'\n}",
"[\n{lo:1}/log($placeholder$)/1\n]","1?\n{}/log($placeholder$)/i:0",
"0?0?\nlo:lo:{}/log($placeholder$)/1","0\n{/'lo/,log($placeholder$)//'}",
"while(0)/'/;log($placeholder$)//","if(0)/'/;log($placeholder$)//","for(;0;)/'/;log($placeholder$)//","with(0)/'/;log($placeholder$)//",
"0/function(){}/log($placeholder$)//","1\n~/'lo/+log($placeholder$)//'",
"true\n{}/'lo/+log($placeholder$)//'",
"1?\nfunction lo(){1}/log($placeholder$)/1:1",
"var i=1\ni+++\n{lo:1}/log($placeholder$)/1",
"[\nfunction(lo){}/log($placeholder$)/1\n]",
"(function(){}['constructor'])('log($placeholder$)')()",
"(function(){}/log($placeholder$)/1)",
"1

That's about it I want to thank Jonas Magazinius and Alexey Silin once again for their great work and you should hire those guys if you have a js sandbox that needs testing.

Think you can break JSReg too? Visit here for the demo:-
JSReg demo

and report any bugs here:-
JSReg bugs

2 Responses to “JSReg down but not out”

  1. AnonAcademic writes:

    I seem to remember expressing (years ago) some skepticism that a regexp-based Javascript sandbox would be robust enough to resist attack. As a steady sequence of holes in the regexp-based sandbox emerges, this skepticism is looking increasingly valid. Are you taking a step back from a purely regexp-based Javascript sandbox? Perhaps the basic concept/approach needs to be re-thought?

  2. Gareth Heyes writes:

    Yeah I now use a mixture of traditional parsing (especially for regex objects) and regexes. The main issue was matching pairs of characters together for example needing to know the ending “}” in a object literal pair. Also the browsers have some bugs in JavaScript handling those bugs is difficult for regexes alone.

    The sandbox as it stands now is pretty good, there are still attacks but they are getting more and more difficult to construct, I probably should wrap objects and functions etc but for now I’m trying to make the sandboxed code as lightweight as possible.