diff --git a/instrumentation-security/rhino-jsinjection-1.7.12/build.gradle b/instrumentation-security/rhino-jsinjection-1.7.12/build.gradle new file mode 100644 index 000000000..00ede07a8 --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.12/build.gradle @@ -0,0 +1,21 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("org.mozilla:rhino:1.7.12") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.rhino-jsinjection-1.7.12' } +} + +verifyInstrumentation { + passes ('cat.inspiracio:rhino-js-engine:[1.7.12,1.7.14)') + passes ('org.mozilla:rhino:[1.7.12,1.7.14)') + excludeRegex ('.*[R|RC][0-9]') +} + +site { + title 'JSInjection' + type 'Messaging' +} \ No newline at end of file diff --git a/instrumentation-security/rhino-jsinjection-1.7.12/src/main/java/com/newrelic/agent/security/instrumentation/rhino/JSEngineUtils.java b/instrumentation-security/rhino-jsinjection-1.7.12/src/main/java/com/newrelic/agent/security/instrumentation/rhino/JSEngineUtils.java new file mode 100644 index 000000000..722834883 --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.12/src/main/java/com/newrelic/agent/security/instrumentation/rhino/JSEngineUtils.java @@ -0,0 +1,8 @@ +package com.newrelic.agent.security.instrumentation.rhino; + +public class JSEngineUtils { + + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "JSENGINE_OPERATION_LOCK_RIHNO-"; + public static final String METHOD_EXEC = "exec"; + public static final String RHINO_JS_INJECTION = "RHINO-JS-INJECTION-1.7.12"; +} diff --git a/instrumentation-security/rhino-jsinjection-1.7.12/src/main/java/org/mozilla/javascript/Context_Instrumentation.java b/instrumentation-security/rhino-jsinjection-1.7.12/src/main/java/org/mozilla/javascript/Context_Instrumentation.java new file mode 100644 index 000000000..b3f0bfa2c --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.12/src/main/java/org/mozilla/javascript/Context_Instrumentation.java @@ -0,0 +1,39 @@ +package org.mozilla.javascript; + +import com.newrelic.agent.security.instrumentation.rhino.JSEngineUtils; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.utils.logging.LogLevel; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +import java.io.IOException; +import java.io.Reader; + +@Weave(type = MatchType.ExactClass, originalName = "org.mozilla.javascript.Context") +public class Context_Instrumentation { + + @NewField + StringBuilder newScript; + + private Object compileImpl(Scriptable scope, String sourceString, String sourceName, int lineno, Object securityDomain, boolean returnFunction, Evaluator compiler, ErrorReporter compilationErrorReporter) throws IOException { + try { + if (sourceString!=null) { + newScript = new StringBuilder(sourceString); + } + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, Context_Instrumentation.class.getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, Context_Instrumentation.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, + JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, Context_Instrumentation.class.getName()); + } + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/rhino-jsinjection-1.7.12/src/main/java/org/mozilla/javascript/ScriptRuntime_Instrumentation.java b/instrumentation-security/rhino-jsinjection-1.7.12/src/main/java/org/mozilla/javascript/ScriptRuntime_Instrumentation.java new file mode 100644 index 000000000..4c4e53eaf --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.12/src/main/java/org/mozilla/javascript/ScriptRuntime_Instrumentation.java @@ -0,0 +1,106 @@ +package org.mozilla.javascript; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.JSInjectionOperation; +import com.newrelic.api.agent.security.utils.logging.LogLevel; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.newrelic.agent.security.instrumentation.rhino.JSEngineUtils; + +@Weave(type = MatchType.ExactClass, originalName = "org.mozilla.javascript.ScriptRuntime") +public class ScriptRuntime_Instrumentation { + + // TODO: changes for parameterized function calls in js script + public static Object doTopCall(Callable callable, Context_Instrumentation cx, Scriptable scope, Scriptable thisObj, Object[] args){ + boolean isLockAcquired = false; + int code = 0; + AbstractOperation operation = null; + if(cx != null) { + code = cx.hashCode(); + isLockAcquired = acquireLockIfPossible(code); + if (isLockAcquired) { + operation = preprocessSecurityHook(code, JSEngineUtils.METHOD_EXEC, cx); + } + } + + Object returnVal = null; + try { + returnVal = Weaver.callOriginal(); + } finally { + if(isLockAcquired){ + releaseLock(code); + } + } + registerExitOperation(isLockAcquired, operation); + return returnVal; + } + + public static Object doTopCall(Callable callable, Context_Instrumentation cx, Scriptable scope, Scriptable thisObj, Object[] args, boolean isTopLevelStrict) { + boolean isLockAcquired = false; + int code = 0; + AbstractOperation operation = null; + if(cx != null) { + code = cx.hashCode(); + isLockAcquired = acquireLockIfPossible(code); + if (isLockAcquired) { + operation = preprocessSecurityHook(code, JSEngineUtils.METHOD_EXEC, cx); + } + } + + Object returnVal = null; + try { + returnVal = Weaver.callOriginal(); + } finally { + if(isLockAcquired){ + releaseLock(code); + } + } + registerExitOperation(isLockAcquired, operation); + return returnVal; + } + + private static void registerExitOperation(boolean isProcessingAllowed, AbstractOperation operation) { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive() || + NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() || GenericHelper.skipExistsEvent() + ) { + return; + } + NewRelicSecurity.getAgent().registerExitEvent(operation); + } catch (Throwable e){ + NewRelicSecurity.getAgent().log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + } + } + + private static AbstractOperation preprocessSecurityHook(int hashCode, String methodName, Context_Instrumentation context){ + try { + if(StringUtils.isNotBlank(context.newScript)) { + JSInjectionOperation jsInjectionOperation = new JSInjectionOperation(String.valueOf(context.newScript), "org.mozilla.javascript.Script", methodName); + NewRelicSecurity.getAgent().registerOperation(jsInjectionOperation); + return jsInjectionOperation; + } + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + } + return null; + } + + private static void releaseLock(int code) { + GenericHelper.releaseLock(JSEngineUtils.NR_SEC_CUSTOM_ATTRIB_NAME+code); + } + + private static boolean acquireLockIfPossible(int code) { + return GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.JAVASCRIPT_INJECTION, JSEngineUtils.NR_SEC_CUSTOM_ATTRIB_NAME+code); + } +} diff --git a/instrumentation-security/rhino-jsinjection-1.7.12/src/test/java/com/nr/agent/security/instrumentation/rhino/RhinoTest.java b/instrumentation-security/rhino-jsinjection-1.7.12/src/test/java/com/nr/agent/security/instrumentation/rhino/RhinoTest.java new file mode 100644 index 000000000..0e1ea87a2 --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.12/src/test/java/com/nr/agent/security/instrumentation/rhino/RhinoTest.java @@ -0,0 +1,192 @@ +package com.nr.agent.security.instrumentation.rhino; + +import com.newrelic.agent.security.introspec.InstrumentationTestConfig; +import com.newrelic.agent.security.introspec.SecurityInstrumentationTestRunner; +import com.newrelic.agent.security.introspec.SecurityIntrospector; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.operation.JSInjectionOperation; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.Script; +import org.mozilla.javascript.Scriptable; + +import java.io.FileReader; +import java.io.IOException; +import java.util.List; + +@RunWith(SecurityInstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = { "org.mozilla.javascript" }) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class RhinoTest { + + @Trace + private static String callFunctionCall() { + Context rhino = Context.enter(); + String script = "function greet() { return 'Hello, Rhino!'; }"; + try { + Scriptable scope = rhino.initStandardObjects(); + rhino.evaluateString(scope, script, "", 1, null); + Object greet = scope.get("greet", scope); + if (greet instanceof Function) { + Function func = (Function)greet; + Object result = func.call(rhino, scope, scope, new Object[] {}); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callExec() { + Context rhino = Context.enter(); + String script = "function greet() { return 'Hello, World!'; }"; + try { + Scriptable scope = rhino.initStandardObjects(); + Script compiledScript = rhino.compileString(script, "", 1, null); + compiledScript.exec(rhino, scope); + Object greet = scope.get("greet", scope); + if (greet instanceof Function) { + Function func = (Function)greet; + Object result = func.call(rhino, scope, scope, new Object[] {}); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callExecWithReader() throws IOException { + Context rhino = Context.enter(); + String script = "var fun1 = function(name) { return 'Hi, ' + name; };"; + try { + Scriptable scope = rhino.initStandardObjects(); + Script compiledScript = rhino.compileReader(new FileReader("src/test/resources/script.js"), "", 1, null); + compiledScript.exec(rhino, scope); + Object greet = scope.get("fun1", scope); + if (greet instanceof Function) { + Function func = (Function)greet; + Object result = func.call(rhino, scope, scope, new Object[] {"rhino"}); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callCompileFunction() throws IOException { + Context rhino = Context.enter(); + String script = "function(name) { return 'Hi, ' + name; };"; + try { + Scriptable scope = rhino.initStandardObjects(); + Function func = rhino.compileFunction(scope, script, "", 1, null); + Object result = func.call(rhino, scope, scope, new Object[] {"Ash"}); + System.out.println(Context.toString(result)); + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callFunctionCallWithReader() throws IOException { + Context rhino = Context.enter(); + String script = "var fun1 = function(name) { return 'Hi, ' + name; };"; + try { + Scriptable scope = rhino.initStandardObjects(); + rhino.evaluateReader(scope, new FileReader("src/test/resources/script.js"), "", 1, null); + Object greet = scope.get("fun1", scope); + if (greet instanceof Function) { + Function func = (Function)greet; + Object result = func.call(rhino, scope, scope, new String[] { "hero" }); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Test + public void testExec(){ + String script = callExec(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testExecWithReader() throws IOException { + String script = callExecWithReader(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testCompileFunction() throws IOException { + String script = callCompileFunction(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testFunctionCall(){ + String script = callFunctionCall(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testFunctionCallWithReader() throws IOException { + String script = callFunctionCallWithReader(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } +} diff --git a/instrumentation-security/rhino-jsinjection-1.7.12/src/test/resources/script.js b/instrumentation-security/rhino-jsinjection-1.7.12/src/test/resources/script.js new file mode 100644 index 000000000..19636830a --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.12/src/test/resources/script.js @@ -0,0 +1 @@ +var fun1 = function(name) { return 'Hi, ' + name; }; \ No newline at end of file diff --git a/instrumentation-security/rhino-jsinjection-1.7.14/build.gradle b/instrumentation-security/rhino-jsinjection-1.7.14/build.gradle new file mode 100644 index 000000000..afd534764 --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.14/build.gradle @@ -0,0 +1,34 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("org.mozilla:rhino:1.7.14") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.rhino-jsinjection-1.7.14' } +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +test { + // These instrumentation tests only run on Java 17+ regardless of the -PtestN gradle property that is set. + onlyIf { + !project.hasProperty('test8') && !project.hasProperty('test11') + } +} + +verifyInstrumentation { + passes ('cat.inspiracio:rhino-js-engine:[1.7.14,1.8.0)') + passes ('org.mozilla:rhino:[1.7.14,1.8.0)') + excludeRegex ('.*[R|RC][0-9]') +} + +site { + title 'JSInjection' + type 'Messaging' +} \ No newline at end of file diff --git a/instrumentation-security/rhino-jsinjection-1.7.14/src/main/java/com/newrelic/agent/security/instrumentation/rhino/JSEngineUtils.java b/instrumentation-security/rhino-jsinjection-1.7.14/src/main/java/com/newrelic/agent/security/instrumentation/rhino/JSEngineUtils.java new file mode 100644 index 000000000..dbca25703 --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.14/src/main/java/com/newrelic/agent/security/instrumentation/rhino/JSEngineUtils.java @@ -0,0 +1,8 @@ +package com.newrelic.agent.security.instrumentation.rhino; + +public class JSEngineUtils { + + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "JSENGINE_OPERATION_LOCK_RIHNO-"; + public static final String METHOD_EXEC = "exec"; + public static final String RHINO_JS_INJECTION = "RHINO-JS-INJECTION-1.7.14"; +} diff --git a/instrumentation-security/rhino-jsinjection-1.7.14/src/main/java/org/mozilla/javascript/Context_Instrumentation.java b/instrumentation-security/rhino-jsinjection-1.7.14/src/main/java/org/mozilla/javascript/Context_Instrumentation.java new file mode 100644 index 000000000..8ea8fc60e --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.14/src/main/java/org/mozilla/javascript/Context_Instrumentation.java @@ -0,0 +1,39 @@ +package org.mozilla.javascript; + +import com.newrelic.agent.security.instrumentation.rhino.JSEngineUtils; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.utils.logging.LogLevel; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +import java.io.IOException; +import java.io.Reader; + +@Weave(type = MatchType.ExactClass, originalName = "org.mozilla.javascript.Context") +public class Context_Instrumentation { + + @NewField + StringBuilder newScript; + + protected Object compileImpl(Scriptable scope, String sourceString, String sourceName, int lineno, Object securityDomain, boolean returnFunction, Evaluator compiler, ErrorReporter compilationErrorReporter) throws IOException { + try { + if (sourceString!=null) { + newScript = new StringBuilder(sourceString); + } + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, Context_Instrumentation.class.getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, Context_Instrumentation.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, + JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, Context_Instrumentation.class.getName()); + } + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/rhino-jsinjection-1.7.14/src/main/java/org/mozilla/javascript/ScriptRuntime_Instrumentation.java b/instrumentation-security/rhino-jsinjection-1.7.14/src/main/java/org/mozilla/javascript/ScriptRuntime_Instrumentation.java new file mode 100644 index 000000000..4c4e53eaf --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.14/src/main/java/org/mozilla/javascript/ScriptRuntime_Instrumentation.java @@ -0,0 +1,106 @@ +package org.mozilla.javascript; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.JSInjectionOperation; +import com.newrelic.api.agent.security.utils.logging.LogLevel; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.newrelic.agent.security.instrumentation.rhino.JSEngineUtils; + +@Weave(type = MatchType.ExactClass, originalName = "org.mozilla.javascript.ScriptRuntime") +public class ScriptRuntime_Instrumentation { + + // TODO: changes for parameterized function calls in js script + public static Object doTopCall(Callable callable, Context_Instrumentation cx, Scriptable scope, Scriptable thisObj, Object[] args){ + boolean isLockAcquired = false; + int code = 0; + AbstractOperation operation = null; + if(cx != null) { + code = cx.hashCode(); + isLockAcquired = acquireLockIfPossible(code); + if (isLockAcquired) { + operation = preprocessSecurityHook(code, JSEngineUtils.METHOD_EXEC, cx); + } + } + + Object returnVal = null; + try { + returnVal = Weaver.callOriginal(); + } finally { + if(isLockAcquired){ + releaseLock(code); + } + } + registerExitOperation(isLockAcquired, operation); + return returnVal; + } + + public static Object doTopCall(Callable callable, Context_Instrumentation cx, Scriptable scope, Scriptable thisObj, Object[] args, boolean isTopLevelStrict) { + boolean isLockAcquired = false; + int code = 0; + AbstractOperation operation = null; + if(cx != null) { + code = cx.hashCode(); + isLockAcquired = acquireLockIfPossible(code); + if (isLockAcquired) { + operation = preprocessSecurityHook(code, JSEngineUtils.METHOD_EXEC, cx); + } + } + + Object returnVal = null; + try { + returnVal = Weaver.callOriginal(); + } finally { + if(isLockAcquired){ + releaseLock(code); + } + } + registerExitOperation(isLockAcquired, operation); + return returnVal; + } + + private static void registerExitOperation(boolean isProcessingAllowed, AbstractOperation operation) { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive() || + NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() || GenericHelper.skipExistsEvent() + ) { + return; + } + NewRelicSecurity.getAgent().registerExitEvent(operation); + } catch (Throwable e){ + NewRelicSecurity.getAgent().log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + } + } + + private static AbstractOperation preprocessSecurityHook(int hashCode, String methodName, Context_Instrumentation context){ + try { + if(StringUtils.isNotBlank(context.newScript)) { + JSInjectionOperation jsInjectionOperation = new JSInjectionOperation(String.valueOf(context.newScript), "org.mozilla.javascript.Script", methodName); + NewRelicSecurity.getAgent().registerOperation(jsInjectionOperation); + return jsInjectionOperation; + } + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + } + return null; + } + + private static void releaseLock(int code) { + GenericHelper.releaseLock(JSEngineUtils.NR_SEC_CUSTOM_ATTRIB_NAME+code); + } + + private static boolean acquireLockIfPossible(int code) { + return GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.JAVASCRIPT_INJECTION, JSEngineUtils.NR_SEC_CUSTOM_ATTRIB_NAME+code); + } +} diff --git a/instrumentation-security/rhino-jsinjection-1.7.14/src/test/java/com/nr/agent/security/instrumentation/rhino/RhinoTest.java b/instrumentation-security/rhino-jsinjection-1.7.14/src/test/java/com/nr/agent/security/instrumentation/rhino/RhinoTest.java new file mode 100644 index 000000000..155ef6121 --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.14/src/test/java/com/nr/agent/security/instrumentation/rhino/RhinoTest.java @@ -0,0 +1,188 @@ +package com.nr.agent.security.instrumentation.rhino; + +import com.newrelic.agent.security.introspec.InstrumentationTestConfig; +import com.newrelic.agent.security.introspec.SecurityInstrumentationTestRunner; +import com.newrelic.agent.security.introspec.SecurityIntrospector; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.operation.JSInjectionOperation; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.Script; +import org.mozilla.javascript.Scriptable; + +import java.io.FileReader; +import java.io.IOException; +import java.util.List; + +@RunWith(SecurityInstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = { "org.mozilla.javascript" }) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class RhinoTest { + + @Trace + private static String callFunctionCall() { + Context rhino = Context.enter(); + String script = "function greet() { return 'Hello, Rhino!'; }"; + try { + Scriptable scope = rhino.initStandardObjects(); + rhino.evaluateString(scope, script, "", 1, null); + Object greet = scope.get("greet", scope); + if (greet instanceof Function func) { + Object result = func.call(rhino, scope, scope, new Object[] {}); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callExec() { + Context rhino = Context.enter(); + String script = "function greet() { return 'Hello, World!'; }"; + try { + Scriptable scope = rhino.initStandardObjects(); + Script compiledScript = rhino.compileString(script, "", 1, null); + compiledScript.exec(rhino, scope); + Object greet = scope.get("greet", scope); + if (greet instanceof Function func) { + Object result = func.call(rhino, scope, scope, new Object[] {}); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callExecWithReader() throws IOException { + Context rhino = Context.enter(); + String script = "var fun1 = function(name) { return 'Hi, ' + name; };"; + try { + Scriptable scope = rhino.initStandardObjects(); + Script compiledScript = rhino.compileReader(new FileReader("src/test/resources/script.js"), "", 1, null); + compiledScript.exec(rhino, scope); + Object greet = scope.get("fun1", scope); + if (greet instanceof Function func) { + Object result = func.call(rhino, scope, scope, new Object[] {"rhino"}); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callCompileFunction() throws IOException { + Context rhino = Context.enter(); + String script = "function(name) { return 'Hi, ' + name; };"; + try { + Scriptable scope = rhino.initStandardObjects(); + Function func = rhino.compileFunction(scope, script, "", 1, null); + Object result = func.call(rhino, scope, scope, new Object[] {"Ash"}); + System.out.println(Context.toString(result)); + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callFunctionCallWithReader() throws IOException { + Context rhino = Context.enter(); + String script = "var fun1 = function(name) { return 'Hi, ' + name; };"; + try { + Scriptable scope = rhino.initStandardObjects(); + rhino.evaluateReader(scope, new FileReader("src/test/resources/script.js"), "", 1, null); + Object greet = scope.get("fun1", scope); + if (greet instanceof Function func) { + Object result = func.call(rhino, scope, scope, new String[] { "hero" }); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Test + public void testExec(){ + String script = callExec(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testExecWithReader() throws IOException { + String script = callExecWithReader(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testCompileFunction() throws IOException { + String script = callCompileFunction(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testFunctionCall(){ + String script = callFunctionCall(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testFunctionCallWithReader() throws IOException { + String script = callFunctionCallWithReader(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertTrue("No operations detected", operations.size() > 0); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } +} diff --git a/instrumentation-security/rhino-jsinjection-1.7.14/src/test/resources/script.js b/instrumentation-security/rhino-jsinjection-1.7.14/src/test/resources/script.js new file mode 100644 index 000000000..19636830a --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.7.14/src/test/resources/script.js @@ -0,0 +1 @@ +var fun1 = function(name) { return 'Hi, ' + name; }; \ No newline at end of file diff --git a/instrumentation-security/rhino-jsinjection-1.8.0/build.gradle b/instrumentation-security/rhino-jsinjection-1.8.0/build.gradle new file mode 100644 index 000000000..8479d438c --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.8.0/build.gradle @@ -0,0 +1,34 @@ +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("org.mozilla:rhino:1.8.0") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.rhino-jsinjection-1.8.0' } +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +test { + // These instrumentation tests only run on Java 17+ regardless of the -PtestN gradle property that is set. + onlyIf { + !project.hasProperty('test8') && !project.hasProperty('test11') + } +} + +verifyInstrumentation { + passes ('cat.inspiracio:rhino-js-engine:[1.8.0,)') + passes ('org.mozilla:rhino:[1.8.0,)') + excludeRegex ('.*[R|RC][0-9]') +} + +site { + title 'JSInjection' + type 'Messaging' +} \ No newline at end of file diff --git a/instrumentation-security/rhino-jsinjection-1.8.0/src/main/java/com/newrelic/agent/security/instrumentation/rhino/JSEngineUtils.java b/instrumentation-security/rhino-jsinjection-1.8.0/src/main/java/com/newrelic/agent/security/instrumentation/rhino/JSEngineUtils.java new file mode 100644 index 000000000..4de1f99b8 --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.8.0/src/main/java/com/newrelic/agent/security/instrumentation/rhino/JSEngineUtils.java @@ -0,0 +1,10 @@ +package com.newrelic.agent.security.instrumentation.rhino; + +public class JSEngineUtils { + + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "JSENGINE_OPERATION_LOCK_RIHNO-"; + + public static final String METHOD_EXEC = "exec"; + + public static final String RHINO_JS_INJECTION = "RHINO-JS-INJECTION-1.8.0"; +} diff --git a/instrumentation-security/rhino-jsinjection-1.8.0/src/main/java/org/mozilla/javascript/Context_Instrumentation.java b/instrumentation-security/rhino-jsinjection-1.8.0/src/main/java/org/mozilla/javascript/Context_Instrumentation.java new file mode 100644 index 000000000..626629160 --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.8.0/src/main/java/org/mozilla/javascript/Context_Instrumentation.java @@ -0,0 +1,47 @@ +package org.mozilla.javascript; + +import com.newrelic.agent.security.instrumentation.rhino.JSEngineUtils; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.utils.logging.LogLevel; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.NewField; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +import java.io.IOException; +import java.util.function.Consumer; + +@Weave(type = MatchType.ExactClass, originalName = "org.mozilla.javascript.Context") +public class Context_Instrumentation { + + @NewField + StringBuilder newScript; + + protected Object compileImpl( + Scriptable scope, + String sourceString, + String sourceName, + int lineno, + Object securityDomain, + boolean returnFunction, + Evaluator compiler, + ErrorReporter compilationErrorReporter, + Consumer compilerEnvironProcessor) { + try { + if (sourceString!=null) { + newScript = new StringBuilder(sourceString); + } + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, Context_Instrumentation.class.getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, Context_Instrumentation.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, + JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, Context_Instrumentation.class.getName()); + } + return Weaver.callOriginal(); + } +} diff --git a/instrumentation-security/rhino-jsinjection-1.8.0/src/main/java/org/mozilla/javascript/ScriptRuntime_Instrumentation.java b/instrumentation-security/rhino-jsinjection-1.8.0/src/main/java/org/mozilla/javascript/ScriptRuntime_Instrumentation.java new file mode 100644 index 000000000..96cff67a1 --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.8.0/src/main/java/org/mozilla/javascript/ScriptRuntime_Instrumentation.java @@ -0,0 +1,82 @@ +package org.mozilla.javascript; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.StringUtils; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.JSInjectionOperation; +import com.newrelic.api.agent.security.utils.logging.LogLevel; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.newrelic.agent.security.instrumentation.rhino.JSEngineUtils; + +@Weave(type = MatchType.ExactClass, originalName = "org.mozilla.javascript.ScriptRuntime") +public class ScriptRuntime_Instrumentation { + + // TODO: changes for parameterized function calls in js script + public static Object doTopCall(Callable callable, Context_Instrumentation cx, Scriptable scope, Scriptable thisObj, Object[] args, boolean isTopLevelStrict) { + boolean isLockAcquired = false; + int code = 0; + AbstractOperation operation = null; + if(cx != null) { + code = cx.hashCode(); + isLockAcquired = acquireLockIfPossible(code); + if (isLockAcquired) { + operation = preprocessSecurityHook(JSEngineUtils.METHOD_EXEC, cx); + } + } + + Object returnVal = null; + try { + returnVal = Weaver.callOriginal(); + } finally { + if(isLockAcquired){ + releaseLock(code); + } + } + registerExitOperation(isLockAcquired, operation); + return returnVal; + } + + private static void registerExitOperation(boolean isProcessingAllowed, AbstractOperation operation) { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive() || + NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() || GenericHelper.skipExistsEvent() + ) { + return; + } + NewRelicSecurity.getAgent().registerExitEvent(operation); + } catch (Throwable e){ + NewRelicSecurity.getAgent().log(LogLevel.FINEST, String.format(GenericHelper.EXIT_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + } + } + + private static AbstractOperation preprocessSecurityHook(String methodName, Context_Instrumentation context){ + try { + if(StringUtils.isNotBlank(context.newScript)) { + JSInjectionOperation jsInjectionOperation = new JSInjectionOperation(String.valueOf(context.newScript), "org.mozilla.javascript.Script", methodName); + NewRelicSecurity.getAgent().registerOperation(jsInjectionOperation); + return jsInjectionOperation; + } + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(GenericHelper.SECURITY_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + throw e; + } + NewRelicSecurity.getAgent().log(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.SEVERE, String.format(GenericHelper.REGISTER_OPERATION_EXCEPTION_MESSAGE, JSEngineUtils.RHINO_JS_INJECTION, e.getMessage()), e, ScriptRuntime_Instrumentation.class.getName()); + } + return null; + } + + private static void releaseLock(int code) { + GenericHelper.releaseLock(JSEngineUtils.NR_SEC_CUSTOM_ATTRIB_NAME+code); + } + + private static boolean acquireLockIfPossible(int code) { + return GenericHelper.acquireLockIfPossible(VulnerabilityCaseType.JAVASCRIPT_INJECTION, JSEngineUtils.NR_SEC_CUSTOM_ATTRIB_NAME+code); + } +} diff --git a/instrumentation-security/rhino-jsinjection-1.8.0/src/test/java/com/nr/agent/security/instrumentation/rhino/RhinoTest.java b/instrumentation-security/rhino-jsinjection-1.8.0/src/test/java/com/nr/agent/security/instrumentation/rhino/RhinoTest.java new file mode 100644 index 000000000..61d65f9ac --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.8.0/src/test/java/com/nr/agent/security/instrumentation/rhino/RhinoTest.java @@ -0,0 +1,188 @@ +package com.nr.agent.security.instrumentation.rhino; + +import com.newrelic.agent.security.introspec.InstrumentationTestConfig; +import com.newrelic.agent.security.introspec.SecurityInstrumentationTestRunner; +import com.newrelic.agent.security.introspec.SecurityIntrospector; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.VulnerabilityCaseType; +import com.newrelic.api.agent.security.schema.operation.JSInjectionOperation; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.Script; +import org.mozilla.javascript.Scriptable; + +import java.io.FileReader; +import java.io.IOException; +import java.util.List; + +@RunWith(SecurityInstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = { "org.mozilla.javascript" }) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class RhinoTest { + + @Trace + private static String callFunctionCall() { + Context rhino = Context.enter(); + String script = "function greet() { return 'Hello, Rhino!'; }"; + try { + Scriptable scope = rhino.initStandardObjects(); + rhino.evaluateString(scope, script, "", 1, null); + Object greet = scope.get("greet", scope); + if (greet instanceof Function func) { + Object result = func.call(rhino, scope, scope, new Object[] {}); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callExec() { + Context rhino = Context.enter(); + String script = "function greet() { return 'Hello, World!'; }"; + try { + Scriptable scope = rhino.initStandardObjects(); + Script compiledScript = rhino.compileString(script, "", 1, null); + compiledScript.exec(rhino, scope); + Object greet = scope.get("greet", scope); + if (greet instanceof Function func) { + Object result = func.call(rhino, scope, scope, new Object[] {}); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callExecWithReader() throws IOException { + Context rhino = Context.enter(); + String script = "var fun1 = function(name) { return 'Hi, ' + name; };"; + try { + Scriptable scope = rhino.initStandardObjects(); + Script compiledScript = rhino.compileReader(new FileReader("src/test/resources/script.js"), "", 1, null); + compiledScript.exec(rhino, scope); + Object greet = scope.get("fun1", scope); + if (greet instanceof Function func) { + Object result = func.call(rhino, scope, scope, new Object[] {"rhino"}); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callCompileFunction() throws IOException { + Context rhino = Context.enter(); + String script = "function(name) { return 'Hi, ' + name; };"; + try { + Scriptable scope = rhino.initStandardObjects(); + Function func = rhino.compileFunction(scope, script, "", 1, null); + Object result = func.call(rhino, scope, scope, new Object[] {"Ash"}); + System.out.println(Context.toString(result)); + } finally { + Context.exit(); + } + return script; + } + + @Trace + private static String callFunctionCallWithReader() throws IOException { + Context rhino = Context.enter(); + String script = "var fun1 = function(name) { return 'Hi, ' + name; };"; + try { + Scriptable scope = rhino.initStandardObjects(); + rhino.evaluateReader(scope, new FileReader("src/test/resources/script.js"), "", 1, null); + Object greet = scope.get("fun1", scope); + if (greet instanceof Function func) { + Object result = func.call(rhino, scope, scope, new String[] { "hero" }); + System.out.println(Context.toString(result)); + } + } finally { + Context.exit(); + } + return script; + } + + @Test + public void testExec(){ + String script = callExec(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertFalse("No operations detected", operations.isEmpty()); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testExecWithReader() throws IOException { + String script = callExecWithReader(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertFalse("No operations detected", operations.isEmpty()); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testCompileFunction() throws IOException { + String script = callCompileFunction(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertFalse("No operations detected", operations.isEmpty()); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testFunctionCall(){ + String script = callFunctionCall(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertFalse("No operations detected", operations.isEmpty()); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } + + @Test + public void testFunctionCallWithReader() throws IOException { + String script = callFunctionCallWithReader(); + + SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector(); + List operations = introspector.getOperations(); + Assert.assertFalse("No operations detected", operations.isEmpty()); + JSInjectionOperation operation = (JSInjectionOperation) operations.get(0); + Assert.assertEquals("Invalid executed parameters.", script, operation.getJavaScriptCode()); + Assert.assertEquals("Invalid event category.", VulnerabilityCaseType.JAVASCRIPT_INJECTION, operation.getCaseType()); + Assert.assertEquals("Invalid executed class name.", Script.class.getName(), operation.getClassName()); + Assert.assertEquals("Invalid executed method name.", "exec", operation.getMethodName()); + } +} diff --git a/instrumentation-security/rhino-jsinjection-1.8.0/src/test/resources/script.js b/instrumentation-security/rhino-jsinjection-1.8.0/src/test/resources/script.js new file mode 100644 index 000000000..19636830a --- /dev/null +++ b/instrumentation-security/rhino-jsinjection-1.8.0/src/test/resources/script.js @@ -0,0 +1 @@ +var fun1 = function(name) { return 'Hi, ' + name; }; \ No newline at end of file diff --git a/instrumentation-security/rhino-jsinjection/build.gradle b/instrumentation-security/rhino-jsinjection/build.gradle index 9e73e354c..ff7ccbfeb 100644 --- a/instrumentation-security/rhino-jsinjection/build.gradle +++ b/instrumentation-security/rhino-jsinjection/build.gradle @@ -1,5 +1,3 @@ - - dependencies { implementation(project(":newrelic-security-api")) implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") @@ -12,7 +10,9 @@ jar { } verifyInstrumentation { - passesOnly 'cat.inspiracio:rhino-js-engine:[1.7.7.1,1.7.14)' + passesOnly ('cat.inspiracio:rhino-js-engine:[1.7.7.1,1.7.12)') + passesOnly ('org.mozilla:rhino:[1.7.6,1.7.12)') + excludeRegex('.*[R|RC][0-9]') } diff --git a/settings.gradle b/settings.gradle index afec08f66..5129b7ed5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -105,6 +105,9 @@ include 'instrumentation:jaxen-xpath' include 'instrumentation:jaxen-xpath-1.1' include 'instrumentation:nashorn-jsinjection' include 'instrumentation:rhino-jsinjection' +include 'instrumentation:rhino-jsinjection-1.7.12' +include 'instrumentation:rhino-jsinjection-1.7.14' +include 'instrumentation:rhino-jsinjection-1.8.0' include 'instrumentation:graalvm-jsinjection-19.0.0' include 'instrumentation:graalvm-jsinjection-22.0.0' include 'instrumentation:apache-log4j-2.0'