Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions instrumentation-security/rhino-jsinjection-1.7.12/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
}
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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, "<cmd>", 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, "<cmd>", 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"), "<cmd>", 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, "<cmd>", 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"), "<cmd>", 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<AbstractOperation> 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<AbstractOperation> 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<AbstractOperation> 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<AbstractOperation> 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<AbstractOperation> 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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var fun1 = function(name) { return 'Hi, ' + name; };
Loading
Loading