javadelight / delight-nashorn-sandbox

A sandbox for executing JavaScript with Nashorn in Java.
Other
268 stars 81 forks source link

javax.script.ScriptException when evaluating proxy discovery js #74

Open foxep2001 opened 5 years ago

foxep2001 commented 5 years ago

Hello,

When using a proxy discovery library that utilizes nashorn-sandbox, an interrupt function is inserted in a spot that causes the evaluation to fail with a ScriptException. This appears similar to https://github.com/javadelight/delight-nashorn-sandbox/issues/66 but the cause is different.

The proxy detection script:


    function FindProxyForURL(url, host)
    {
        var resolved_ip = dnsResolve(host);      // LIMIT DNS LOOKUPS FOR isInNet() FUNCTIONS

        if (isPlainHostName(host))
        return "DIRECT";

        else if  (dnsDomainIs(host, "candy-ito.acme.com.edgesuite.net")
        || dnsDomainIs(host, "candy-mp.acme.com"))
        return "PROXY http://wbb.proxy.acme.com:8080";

        else if (dnsDomainIs(host, "ripped101.initech.acme.com")
        || dnsDomainIs(host, "ripened101.initech.acme.com"))
        return "DIRECT";

        else if (dnsDomainIs(host, "whymper-28801.far-west.bluemix.net")
        || dnsDomainIs(host, "acme.whymperplatform..com"))
        return "PROXY http://matwhymper-proxy-wld.at.acme.com:8080";

        else if (dnsDomainIs(host, "enough.initech.acme.com"))
        return "PROXY http://one.proxy.acme.com:8080";

        else if (dnsDomainIs(host, "gcfppymsu.noc.acme.com")
        || dnsDomainIs(host, "gcfpbgmsu.noc.acme.com"))
        return "PROXY http://bnoc.proxy.acme.com:8080";

        else if (dnsDomainIs(host, "brand.hoodu.com")
        || dnsDomainIs(host, "bizness.hoodu.com"))
        return "PROXY one.proxy.acme.com:8080; DIRECT";

        else if (dnsDomainIs(host, ".2bfatness*")
        || dnsDomainIs(host, "crimsonpermanent.com")
        || dnsDomainIs(host, "brawndonext.net")
        || dnsDomainIs(host, "brawndointeractive.com")
        || dnsDomainIs(host, "brawndo.net"))
        return "DIRECT";   
                      //^^^^^ inserted interupt function breaks the nashorn eval here 

        else if (isInNet(resolved_ip, "192.17.7.3",  "255.255.254.0")
        || isInNet(resolved_ip, "192.20.7.0",  "255.255.254.0")

        || isInNet(resolved_ip, "192.67.128.0",  "255.255.192.0")

        || isInNet(resolved_ip, "192.7.10.12",  "255.255.0.0"))
        return "DIRECT";

        else if (shExpMatch(host, "192.1.1.2"))
        alert("Proxy Test IP Match Successful");

        else if (localHostOrDomainIs(host, "autoproxy.acmedns.net"))
                return "PROXY http://loser.dns.hoodu.com:83";
        else if (localHostOrDomainIs(host, "autotail.acmedns.net"))
                return "PROXY http://loser.dns.hoodu.com:84";

        else 
        return "PROXY one.proxy.acme.com:8080; DIRECT";
    }

It causes the following stack trace.

    javax.script.ScriptException: <eval>:25:1 Expected an operand but found else
     else if (isInNet(resolved_ip, "192.17.7.3", "255.255.254.0") || isInNet(resolved_ip, "192.20.7.0", "255.255.254.0")
     ^ in <eval> at line number 25 at column number 1
        at jdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(NashornScriptEngine.java:470)
        at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:537)
        at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:524)
        at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:402)
        at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:155)
        at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
        at delight.nashornsandbox.internal.EvaluateOperation.executeScriptEngineOperation(EvaluateOperation.java:47)
        at delight.nashornsandbox.internal.NashornSandboxImpl.executeSandboxedOperation(NashornSandboxImpl.java:156)
        at delight.nashornsandbox.internal.NashornSandboxImpl.eval(NashornSandboxImpl.java:146)
        at delight.nashornsandbox.internal.NashornSandboxImpl.eval(NashornSandboxImpl.java:127)
        at com.nvoq.tools.nashorn.JavascriptEvalTest.evalJS(JavascriptEvalTest.java:29)
        at com.nvoq.tools.nashorn.JavascriptEvalTest.main(JavascriptEvalTest.java:48)
     Caused by: jdk.nashorn.internal.runtime.ParserException: <eval>:25:1 Expected an operand but found else
     else if (isInNet(resolved_ip, "192.17.7.3", "255.255.254.0") || isInNet(resolved_ip, "192.20.7.0", "255.255.254.0")
     ^
        at jdk.nashorn.internal.parser.AbstractParser.error(AbstractParser.java:294)
        at jdk.nashorn.internal.parser.AbstractParser.error(AbstractParser.java:279)
        at jdk.nashorn.internal.parser.Parser.unaryExpression(Parser.java:3182)
        at jdk.nashorn.internal.parser.Parser.expression(Parser.java:3282)
        at jdk.nashorn.internal.parser.Parser.expressionStatement(Parser.java:1150)
        at jdk.nashorn.internal.parser.Parser.statement(Parser.java:967)
        at jdk.nashorn.internal.parser.Parser.sourceElements(Parser.java:773)
        at jdk.nashorn.internal.parser.Parser.functionBody(Parser.java:2901)
        at jdk.nashorn.internal.parser.Parser.functionExpression(Parser.java:2663)
        at jdk.nashorn.internal.parser.Parser.statement(Parser.java:875)
        at jdk.nashorn.internal.parser.Parser.sourceElements(Parser.java:773)
        at jdk.nashorn.internal.parser.Parser.program(Parser.java:709)
        at jdk.nashorn.internal.parser.Parser.parse(Parser.java:283)
        at jdk.nashorn.internal.parser.Parser.parse(Parser.java:249)
        at jdk.nashorn.internal.runtime.Context.compile(Context.java:1284)
        at jdk.nashorn.internal.runtime.Context.compileScript(Context.java:1251)
        at jdk.nashorn.internal.runtime.Context.compileScript(Context.java:627)
        at jdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:535)
        ... 10 more

It appears the JsSanitizer regex (([^;]+;){9}[^;]+(?<!break|continue);) ads an interrupt function on a return statement in the middle of a long else if statement. The resulting sanitized javascript fails when evaluated with the nashorn evaluator. Here's the sanitized java script.

var __it=Java.type('delight.nashornsandbox.internal.InterruptTest');var __if=function(){__it.test();};
/*
 **********************************************************
 ** ACME IP space browser autoproxy configuration: one.pac 
 ** Version: 2038.01.19.16_03:14:07
 **********************************************************
 */
function FindProxyForURL(url, host) {__if();
 var resolved_ip = dnsResolve(host); // LIMIT DNS LOOKUPS FOR isInNet() FUNCTIONS
 if (isPlainHostName(host)) return "DIRECT";
 // CHILL FOR VIDEO
 else if (dnsDomainIs(host, "candy-ito.acme.com.edgesuite.net") || dnsDomainIs(host, "candy-mp.acme.com")) return "PROXY http://wbb.proxy.acme.com:8080";
 // initiech
 else if (dnsDomainIs(host, "ripped101.initech.acme.com") || dnsDomainIs(host, "ripened101.initech.acme.com")) return "DIRECT";
 // stark-whymper
 else if (dnsDomainIs(host, "whymper-28801.far-west.bluemix.net") || dnsDomainIs(host, "acme.whymperplatform..com")) return "PROXY http://matwhymper-proxy-wld.at.acme.com:8080";
 // BINOC PROXY DOMAINS
 else if (dnsDomainIs(host, "enough.initech.acme.com")) return "PROXY http://one.proxy.acme.com:8080";
 else if (dnsDomainIs(host, "gcfppymsu.noc.acme.com") || dnsDomainIs(host, "gcfpbgmsu.noc.acme.com")) return "PROXY http://bnoc.proxy.acme.com:8080";
 // MISC PROXY MODE EXCEPTIONS
 else if (dnsDomainIs(host, "brand.hoodu.com") || dnsDomainIs(host, "bizness.hoodu.com")) return "PROXY one.proxy.acme.com:8080; DIRECT";
 // acme ENTERPRISE DIRECT-MODE DOMAIN EXCEPTIONS
 else if (dnsDomainIs(host, ".2bfatness*") || dnsDomainIs(host, "crimsonpermanent.com") || dnsDomainIs(host, "brawndonext.net") || dnsDomainIs(host, "brawndointeractive.com") || dnsDomainIs(host, "brawndo.net")) return "DIRECT";__if();
 //^^^^^ inserted interupt function breaks the nashorn eval here 
 else if (isInNet(resolved_ip, "192.17.7.3", "255.255.254.0") || isInNet(resolved_ip, "192.20.7.0", "255.255.254.0")
  // SPECTRE
  || isInNet(resolved_ip, "192.67.128.0", "255.255.192.0")
  // INITECH
  || isInNet(resolved_ip, "192.7.10.12", "255.255.0.0")) return "DIRECT";
 else if (shExpMatch(host, "192.1.1.2")) alert("Proxy Test IP Match Successful");
 else if (localHostOrDomainIs(host, "autoproxy.acmedns.net")) return "PROXY http://loser.dns.hoodu.com:83";
 else if (localHostOrDomainIs(host, "autotail.acmedns.net")) return "PROXY http://loser.dns.hoodu.com:84";
 else return "PROXY one.proxy.acme.com:8080; DIRECT";
}

Here's a simple tester I used.

package com.test.nashorn;

import delight.nashornsandbox.*;
import delight.nashornsandbox.exceptions.ScriptCPUAbuseException;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.script.*;

public class JavascriptEvalTest {

    static private final NashornSandbox engine = NashornSandboxes.create();

    public static void main(String[] args) {
        String jsFile = args[0];

        String jsContents = "NooP";
        try {

            jsContents = new String(Files.readAllBytes(Paths.get(jsFile)));
            System.out.println("Javascript contents \n\n"+jsContents+"\n\n");
            System.out.println("Eval Javascript !!!!");
            Object ret = JavascriptEvalTest.engine.eval(jsContents); //nashorn sandbox
            System.out.println("Evaled Javascript "+ret);

            // Test for the un sandboxed Nashorn
            //ScriptEngine engineNash = new ScriptEngineManager().getEngineByName("nashorn");
            //Object ret =  engineNash.eval(jsContents);
            System.out.println("Evaled Javascript "+ret);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ScriptCPUAbuseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ScriptException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

I've tested this with Java 8 build 172

mxro commented 5 years ago

@foxep2001 Thank you for reporting the issue!

Would it be possible to add curly braces for the if statement body for now as a workaround?

Would you have an idea how the regex could be improved to work in this scenario?

foxep2001 commented 5 years ago

I wish we could add curly braces to solve this. The script is a client's auto-proxy discovery for their large network. We're a small app running on their system. So we didn't want to make their IT change their script.

I'll try some regex-foo and see if I can provide a solution for this case.

foxep2001 commented 5 years ago

@mxro So here is a regex fix that worked for our specific case. I changed the first PoisonPil JsSanitizer regex from "(([^;]+;){9}[^;]+(?<!break|continue);\n" to "(([^;]+;){9}[^;]+(?<!break|continue);!(^[\W+]else))\n"

This will skip adding the interrupt function inside an if/else block where the conditional statement ends in a semicolon and the next text is the else. Do you think this is a satisfactory solution?

mxro commented 5 years ago

@foxep2001 That looks awesome! I think that should do the trick. Could you submit a pull request for this?

I could just add it myself but I wouldn't want any wrong characters to get into the regex when I take them from your comment here.

mxro commented 5 years ago

PR merged and new version released. Thank you!