Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release target 1.0.8 Public Preview #140

Merged
merged 46 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
1f70368
Stored procedure query identification (initial poc)
monu-k2io Nov 6, 2023
786351f
Support env and script file detection
lovesh-ap Nov 7, 2023
3a23214
Merge branch 'feature/stored-procedure' into owasp/enchancments/syste…
lovesh-ap Nov 9, 2023
ec02922
Fix event queue size check for processing
lovesh-ap Nov 14, 2023
e01b871
Update Statement_Instrumentation.java
monu-k2io Nov 16, 2023
57095ee
Unit test cases for stored procedure calls
monu-k2io Nov 16, 2023
54f3136
Improvements for user file and method detection
monu-k2io Nov 17, 2023
1105a08
Fixes in UserClassEntity detection.
harshit-ap Nov 18, 2023
9738b8c
Fix for null user class entity
monu-k2io Nov 18, 2023
d507f0b
Removed unnecessary WebServlet_Instrumentation annotation hook
harshit-ap Nov 18, 2023
b9ca586
Take method into account for LowSeverityEvent Encountered List
lovesh-ap Nov 20, 2023
288ca27
Check for isHookProcessingActive
lovesh-ap Nov 20, 2023
2928bb4
add another check for empty request
lovesh-ap Nov 20, 2023
e91fe0a
Low priority instrumentation module enabled by default
monu-k2io Nov 21, 2023
6308076
Add app server directory path detection.
lovesh-ap Nov 21, 2023
882df40
Merge remote-tracking branch 'newrelic/owasp/enchancments/system-coom…
lovesh-ap Nov 21, 2023
52c5dd3
Changes to support applicationDirectory in agentMetaData. Updates in …
harshit-ap Nov 22, 2023
cff53bb
Add app directory path detection.
lovesh-ap Nov 22, 2023
886a269
join app base directory with catalina base
lovesh-ap Nov 22, 2023
62749a7
Enable low severity hooks based on mode (enabled for IAST)
monu-k2io Nov 23, 2023
5ed7697
Keep config for security.low-priority-instrumentation.enabled default…
lovesh-ap Nov 23, 2023
826d32f
Merge branch 'develop' into owasp/enchancments/system-coomand
lovesh-ap Nov 23, 2023
d90a130
Remove extra statement
lovesh-ap Nov 23, 2023
49fe21d
Disable invalid UTs for now
lovesh-ap Nov 24, 2023
b2d5d71
Merge branch 'release/v1.0.7' into owasp/enchancments/system-coomand
lovesh-ap Nov 29, 2023
0f0aa2f
Release Target 1.0.8
lovesh-ap Nov 29, 2023
d2fef00
remove duplicate
lovesh-ap Nov 29, 2023
c58a0ba
Added RegEx for stored procedure call detection
monu-k2io Dec 1, 2023
e0ecc3f
Enhancements in stored procedure call detection
monu-k2io Dec 1, 2023
306cc9a
Updated UTs for stored procedure call detection
monu-k2io Dec 1, 2023
723315e
Code refactoring
monu-k2io Dec 1, 2023
1b458f1
SecureCookie Vulnerabilities now take httpOnly and SameSite=Strict in…
lovesh-ap Dec 8, 2023
6134866
Update Changelog.md
lovesh-ap Dec 8, 2023
61b57ae
Merge branch 'owasp/enchancments/system-coomand' into release/v1.0.8
lovesh-ap Dec 8, 2023
b323b9a
SecureCookie bean update
lovesh-ap Dec 8, 2023
b27960d
SecureCookie SAME_SITE_COOKIES instrumentation for tomcat
lovesh-ap Dec 11, 2023
c08392a
SecureCookie check for same site and httpOnly is removed
lovesh-ap Dec 12, 2023
88a1129
Merge branch 'develop' into release/v1.0.8
lovesh-ap Dec 12, 2023
a801b3c
Fixes for SameSite attribute for SecureCookie hook.
harshit-ap Dec 12, 2023
3fad48c
NR-212000 Add a log if low severity hooks are enabled
lovesh-ap Jan 5, 2024
480952d
Adjust version range for tomcat 8
lovesh-ap Jan 5, 2024
f408883
Adjust exclude regex for tomcat 8
lovesh-ap Jan 5, 2024
f97cbe7
NR-212000 Modify log
lovesh-ap Jan 5, 2024
35c1e3a
NR-212000 Modify log message
lovesh-ap Jan 5, 2024
2d08deb
Update release date
lovesh-ap Jan 10, 2024
360aa39
Merge branch 'main' into owasp/release/1.0.8
harshit-ap Jan 10, 2024
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
13 changes: 13 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ Noteworthy changes to the agent are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [1.0.8-public-preview] - 2024-1-11
### Changes
- Support for stored procedure call detection in SQL events
- Support for extracting environment variables in case of Remote Code Execution events
- Support for executing script file analysis in case of Remote Code Execution events
- Enabled the transformation of the low-priority instrumentation module by default in case of IAST
- SecureCookie schema check has been removed
### Fixes
- Incorrect user file details in the vulnerability details
- Low severity hook event was not generated when the same url can process multiple request methods
- Detection of server app directory to mitigate false positives for File Access vulnerability

## [1.0.7-public-preview] - 2023-12-6
### Changes
- Async HttpClient v2+ Support: The security agent now also supports Async HTTP client version 2 and above
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# The agent version.
agentVersion=1.0.7
agentVersion=1.0.8
jsonVersion=1.1.1
# Updated exposed NR APM API version.
nrAPIVersion=8.3.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package java.sql;

import com.newrelic.api.agent.security.NewRelicSecurity;
import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper;
import com.newrelic.api.agent.security.instrumentation.helpers.JdbcHelper;
import com.newrelic.api.agent.security.schema.AbstractOperation;
import com.newrelic.api.agent.security.schema.JDBCVendor;
Expand All @@ -22,6 +23,7 @@
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;

@Weave(originalName = "java.sql.PreparedStatement", type = MatchType.Interface)
public abstract class PreparedStatement_Instrumentation {
Expand Down Expand Up @@ -53,6 +55,21 @@ private AbstractOperation preprocessSecurityHook (String sql, String methodName)
SQLOperation sqlOperation = new SQLOperation(this.getClass().getName(), methodName);
sqlOperation.setQuery(sql);
sqlOperation.setParams(this.params);

// first check for quoted strings and remove them for final check
String localSqlCopy = new String(sql);
Matcher quotedStringMatcher = GenericHelper.QUOTED_STRING_PATTERN.matcher(localSqlCopy);
while (quotedStringMatcher.find()) {
String replaceChars = quotedStringMatcher.group();
localSqlCopy = localSqlCopy.replace(replaceChars, "_TEMP_");
}
// final check to identify the stored procedure call
Matcher storedProcedureMatcher = GenericHelper.STORED_PROCEDURE_PATTERN.matcher(localSqlCopy);
while (storedProcedureMatcher.find()) {
sqlOperation.setStoredProcedureCall(true);
break;
}

sqlOperation.setDbName(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(JDBCVendor.META_CONST_JDBC_VENDOR, String.class));
sqlOperation.setPreparedCall(true);
NewRelicSecurity.getAgent().registerOperation(sqlOperation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package java.sql;

import com.newrelic.api.agent.security.NewRelicSecurity;
import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper;
import com.newrelic.api.agent.security.instrumentation.helpers.JdbcHelper;
import com.newrelic.api.agent.security.schema.AbstractOperation;
import com.newrelic.api.agent.security.schema.JDBCVendor;
Expand All @@ -18,6 +19,7 @@
import com.newrelic.api.agent.weaver.NewField;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import java.util.regex.Matcher;

@Weave(originalName = "java.sql.Statement", type = MatchType.Interface)
public abstract class Statement_Instrumentation {
Expand Down Expand Up @@ -45,6 +47,21 @@ private AbstractOperation preprocessSecurityHook (String sql, String methodName)
}
SQLOperation sqlOperation = new SQLOperation(this.getClass().getName(), methodName);
sqlOperation.setQuery(sql);

// first check for quoted strings and remove them for final check
String localSqlCopy = new String(sql);
Matcher quotedStringMatcher = GenericHelper.QUOTED_STRING_PATTERN.matcher(localSqlCopy);
while (quotedStringMatcher.find()) {
String replaceChars = quotedStringMatcher.group();
localSqlCopy = localSqlCopy.replace(replaceChars, "_TEMP_");
}
// final check to identify the stored procedure call
Matcher storedProcedureMatcher = GenericHelper.STORED_PROCEDURE_PATTERN.matcher(localSqlCopy);
while (storedProcedureMatcher.find()) {
sqlOperation.setStoredProcedureCall(true);
break;
}

sqlOperation.setDbName(NewRelicSecurity.getAgent().getSecurityMetaData().getCustomAttribute(JDBCVendor.META_CONST_JDBC_VENDOR, String.class));
sqlOperation.setPreparedCall(false);
NewRelicSecurity.getAgent().registerOperation(sqlOperation);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package com.nr.instrumentation.java.sql;

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.BatchSQLOperation;
import com.newrelic.api.agent.security.schema.operation.SQLOperation;
import org.h2.jdbc.JdbcPreparedStatement;
import org.h2.jdbc.JdbcStatement;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

@RunWith(SecurityInstrumentationTestRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@InstrumentationTestConfig(includePrefixes = { "javax.sql", "java.sql" })
public class StoredProcedureTest {
private static final String DB_DRIVER = "org.h2.Driver";
private static final String DB_CONNECTION = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
private static final String DB_USER = "";
private static final String DB_PASSWORD = "";
private static final Connection CONNECTION = getDBConnection();

private static final List<String> QUERIES = new ArrayList<>();
private static final List<String> FAIL_QUERIES = new ArrayList<>();

@AfterClass
public static void teardown() throws SQLException {
CONNECTION.close();
}

private static Connection getDBConnection() {
Connection dbConnection = null;
try {
Class.forName(DB_DRIVER);
dbConnection = DriverManager.getConnection(DB_CONNECTION, DB_USER, DB_PASSWORD);
return dbConnection;
} catch (Exception e) {
e.printStackTrace();
}
return dbConnection;
}

@BeforeClass
public static void initData() throws SQLException {
QUERIES.add("CREATE ALIAS getH2Version FOR \"org.h2.engine.Constants.getVersion\"");
QUERIES.add("call getH2Version()");
QUERIES.add(" call getH2Version() ");
QUERIES.add("{call getH2Version()}");
QUERIES.add("{ call getH2Version() }");
QUERIES.add(" { call getH2Version() } ");
QUERIES.add(" { call getH2Version() } ");

QUERIES.add("CALL getH2Version()");
QUERIES.add(" CALL getH2Version() ");
QUERIES.add("{CALL getH2Version()}");
QUERIES.add("{ CALL getH2Version() }");
QUERIES.add(" { CALL getH2Version() } ");
QUERIES.add(" { CALL getH2Version() } ");

FAIL_QUERIES.add("CREATE TABLE IF NOT EXISTS MYUSERS(id int primary key, first_name varchar(255), last_name varchar(255))");
FAIL_QUERIES.add("{select * from myusers where first_name='call'}");
FAIL_QUERIES.add("{select * from myusers where first_name='{call'}");
FAIL_QUERIES.add("select * from myusers where first_name='call'");
FAIL_QUERIES.add("select * from myusers where first_name='{call'");
FAIL_QUERIES.add("SELECT * FROM MYUSERS WHERE first_name='call'");
FAIL_QUERIES.add("SELECT * FROM MYUSERS WHERE first_name='{call'");
FAIL_QUERIES.add("{SELECT * FROM MYUSERS WHERE first_name='call'}");
FAIL_QUERIES.add("{SELECT * FROM MYUSERS WHERE first_name='{call'}");
FAIL_QUERIES.add("{select * from myusers where first_name='{call func()}'}");

// set up data in h2
Statement stmt = CONNECTION.createStatement();
stmt.execute(QUERIES.get(0));
stmt.execute(FAIL_QUERIES.get(0));
stmt.close();
}

@Test
public void testProcedureCasePass() throws SQLException {
for (int i = 1; i < QUERIES.size(); i++) {
_case1(QUERIES.get(i));
SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector();
List<AbstractOperation> operations = introspector.getOperations();
Assert.assertTrue("No operations detected", operations.size() > 0);

SQLOperation operation = (SQLOperation) operations.get(i-1);

Assert.assertEquals(String.format("[case-%d] Invalid executed parameters.", i), QUERIES.get(i), operation.getQuery());
Assert.assertEquals(String.format("[case-%d] Invalid event category.", i), VulnerabilityCaseType.SQL_DB_COMMAND, operation.getCaseType());
Assert.assertTrue(String.format("[case-%d] Expected a stored procedure call.", i), operation.isStoredProcedureCall());
Assert.assertEquals(String.format("[case-%d] Invalid executed class name.", i), JdbcStatement.class.getName(), operation.getClassName());
Assert.assertEquals(String.format("[case-%d] Invalid executed method name.", i), "execute", operation.getMethodName());
}
}

@Test
public void testProcedureCaseFail() throws SQLException {
for (int i = 1; i < FAIL_QUERIES.size(); i++) {
_case1(FAIL_QUERIES.get(i));
SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector();
List<AbstractOperation> operations = introspector.getOperations();
Assert.assertTrue("No operations detected", operations.size() > 0);

SQLOperation operation = (SQLOperation) operations.get(i-1);

Assert.assertEquals(String.format("[case-%d] Invalid executed parameters.", i), FAIL_QUERIES.get(i), operation.getQuery());
Assert.assertEquals(String.format("[case-%d] Invalid event category.", i), VulnerabilityCaseType.SQL_DB_COMMAND, operation.getCaseType());
Assert.assertFalse(String.format("[case-%d] Expected a stored procedure call.", i), operation.isStoredProcedureCall());
Assert.assertEquals(String.format("[case-%d] Invalid executed class name.", i), JdbcStatement.class.getName(), operation.getClassName());
Assert.assertEquals(String.format("[case-%d] Invalid executed method name.", i), "execute", operation.getMethodName());
}
}

@Test
public void testPreparedProcedureCasePass() throws SQLException {
for (int i = 1; i < QUERIES.size(); i++) {
_case2(QUERIES.get(i));
SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector();
List<AbstractOperation> operations = introspector.getOperations();
Assert.assertTrue("No operations detected", operations.size() > 0);

SQLOperation operation = (SQLOperation) operations.get(i-1);

Assert.assertEquals(String.format("[case-%d] Invalid executed parameters.", i), QUERIES.get(i), operation.getQuery());
Assert.assertEquals(String.format("[case-%d] Invalid event category.", i), VulnerabilityCaseType.SQL_DB_COMMAND, operation.getCaseType());
Assert.assertTrue(String.format("[case-%d] Expected a stored procedure call.", i), operation.isStoredProcedureCall());
Assert.assertEquals(String.format("[case-%d] Invalid executed class name.", i), JdbcPreparedStatement.class.getName(), operation.getClassName());
Assert.assertEquals(String.format("[case-%d] Invalid executed method name.", i), "execute", operation.getMethodName());
}
}

@Test
public void testPreparedProcedureCaseFail() throws SQLException {
for (int i = 1; i < FAIL_QUERIES.size(); i++) {
_case2(FAIL_QUERIES.get(i));
SecurityIntrospector introspector = SecurityInstrumentationTestRunner.getIntrospector();
List<AbstractOperation> operations = introspector.getOperations();
Assert.assertTrue("No operations detected", operations.size() > 0);

SQLOperation operation = (SQLOperation) operations.get(i-1);

Assert.assertEquals(String.format("[case-%d] Invalid executed parameters.", i), FAIL_QUERIES.get(i), operation.getQuery());
Assert.assertEquals(String.format("[case-%d] Invalid event category.", i), VulnerabilityCaseType.SQL_DB_COMMAND, operation.getCaseType());
Assert.assertFalse(String.format("[case-%d] Expected a stored procedure call.", i), operation.isStoredProcedureCall());
Assert.assertEquals(String.format("[case-%d] Invalid executed class name.", i), JdbcPreparedStatement.class.getName(), operation.getClassName());
Assert.assertEquals(String.format("[case-%d] Invalid executed method name.", i), "execute", operation.getMethodName());
}
}

@Trace(dispatcher = true)
private void _case1(String sql) throws SQLException {
Statement stmt = CONNECTION.createStatement();
stmt.execute(sql);
stmt.close();
}

@Trace(dispatcher = true)
private void _case2(String sql) throws SQLException {
PreparedStatement stmt = CONNECTION.prepareStatement(sql);
stmt.execute();
stmt.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;

Expand Down Expand Up @@ -157,7 +158,8 @@ public static void preprocessSecurityHook(HttpServletRequest httpServletRequest)
}
securityRequest.setContentType(httpServletRequest.getContentType());

securityAgentMetaData.setServiceTrace(Thread.currentThread().getStackTrace());
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length));
securityRequest.setRequestParsed(true);
} catch (Throwable ignored) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;

Expand Down Expand Up @@ -157,7 +158,8 @@ public static void preprocessSecurityHook(HttpServletRequest httpServletRequest)
}
securityRequest.setContentType(httpServletRequest.getContentType());

securityAgentMetaData.setServiceTrace(Thread.currentThread().getStackTrace());
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 2, trace.length));
securityRequest.setRequestParsed(true);
} catch (Throwable ignored) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies {

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.low-priority-instrumentation',
'Enabled': 'false' }
'Enabled': 'true' }
}

verifyInstrumentation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static void processSecurityRequest(ChannelHandlerContext ctx, Object msg,
securityRequest.setProtocol(((HttpRequest) msg).getProtocolVersion().protocolName());
securityRequest.setContentType(securityRequest.getHeaders().get("content-type"));
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(stack, 1, stack.length));
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(stack, 2, stack.length));
securityRequest.setRequestParsed(true);
} else if (msg instanceof HttpContent) {
if (!(secMetaObj instanceof SecurityMetaData) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;

@Weave(type = MatchType.Interface, originalName = "javax.servlet.FilterChain")
public abstract class FilterChain_Instrumentation {
Expand Down Expand Up @@ -77,7 +78,9 @@ private void preprocessSecurityHook(ServletRequest request, ServletResponse resp
}
securityRequest.setContentType(httpServletRequest.getContentType());

securityAgentMetaData.setServiceTrace(Thread.currentThread().getStackTrace());

StackTraceElement[] trace = Thread.currentThread().getStackTrace();
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 1, trace.length));
securityRequest.setRequestParsed(true);
} catch (Throwable ignored){}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;

@Weave(type = MatchType.Interface, originalName = "javax.servlet.Filter")
public abstract class Filter_Instrumentation {
Expand Down Expand Up @@ -79,7 +80,8 @@ private void preprocessSecurityHook(ServletRequest request, ServletResponse resp
}
securityRequest.setContentType(httpServletRequest.getContentType());

securityAgentMetaData.setServiceTrace(Thread.currentThread().getStackTrace());
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
securityMetaData.getMetaData().setServiceTrace(Arrays.copyOfRange(trace, 1, trace.length));
securityRequest.setRequestParsed(true);
} catch (Throwable ignored){}
}
Expand Down
Loading
Loading