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

[BUG] Changes doc level query name field from id to rule name and adds validation #972

Merged
merged 5 commits into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public FindingDto mapFindingWithDocsToFindingDto(FindingWithDocs findingWithDocs
if (docLevelQueries.isEmpty()) { // this is finding generated by a bucket level monitor
for (Map.Entry<String, String> entry : detector.getRuleIdMonitorIdMap().entrySet()) {
if(entry.getValue().equals(findingWithDocs.getFinding().getMonitorId())) {
docLevelQueries = Collections.singletonList(new DocLevelQuery(entry.getKey(),"", Collections.emptyList(),"",Collections.emptyList()));
docLevelQueries = Collections.singletonList(new DocLevelQuery(entry.getKey(),"bucket_level_monitor", Collections.emptyList(),"",Collections.emptyList()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ public Script convertToCondition() {
StringBuilder ruleNameBuilder = new StringBuilder();
size = ruleIds.size();
for (int idx = 0; idx < size; ++idx) {
ruleNameBuilder.append(String.format(Locale.getDefault(), "query[name=%s]", ruleIds.get(idx)));
ruleNameBuilder.append(String.format(Locale.getDefault(), "query[id=%s]", ruleIds.get(idx)));
if (idx < size - 1) {
ruleNameBuilder.append(" || ");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.opensearch.securityanalytics.rules.exceptions;

public class SigmaTitleError extends SigmaError {

public SigmaTitleError(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.opensearch.securityanalytics.rules.exceptions.SigmaIdentifierError;
import org.opensearch.securityanalytics.rules.exceptions.SigmaLevelError;
import org.opensearch.securityanalytics.rules.exceptions.SigmaLogsourceError;
import org.opensearch.securityanalytics.rules.exceptions.SigmaTitleError;
import org.opensearch.securityanalytics.rules.exceptions.SigmaStatusError;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.LoaderOptions;
Expand Down Expand Up @@ -105,6 +106,12 @@ protected static SigmaRule fromDict(Map<String, Object> rule, boolean collectErr
ruleId = null;
}

String title = rule.get("title").toString();
if (!title.matches("^.{1,256}$"))
{
errors.add(new SigmaTitleError("Sigma rule title can be max 256 characters"));
}

SigmaLevel level;
if (rule.containsKey("level")) {
level = SigmaLevel.valueOf(rule.get("level").toString().toUpperCase(Locale.ROOT));
Expand Down Expand Up @@ -164,7 +171,7 @@ protected static SigmaRule fromDict(Map<String, Object> rule, boolean collectErr
throw errors.get(0);
}

return new SigmaRule(rule.get("title").toString(), logSource, detections, ruleId, status,
return new SigmaRule(title, logSource, detections, ruleId, status,
rule.get("description").toString(), rule.get("references") != null? (List<String>) rule.get("references"): null, ruleTags,
rule.get("author").toString(), ruleDate, rule.get("fields") != null? (List<String>) rule.get("fields"): null,
rule.get("falsepositives") != null? (List<String>) rule.get("falsepositives"): null, level, errors);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ private IndexMonitorRequest createDocLevelMonitorRequest(List<Pair<String, Rule>
String id = query.getLeft();

Rule rule = query.getRight();
String name = query.getLeft();
String name = rule.getTitle();
String actualQuery = rule.getQueries().get(0).getValue();

List<String> tags = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static org.opensearch.securityanalytics.model.Detector.NO_ID;
import static org.opensearch.securityanalytics.model.Detector.NO_VERSION;
Expand Down
50 changes: 50 additions & 0 deletions src/test/java/org/opensearch/securityanalytics/TestHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,35 @@ public static String randomEditedRule() {
"level: high";
}

public static String randomEditedRuleInvalidSyntax(String title) {
return "title: " + title + "\n" +
"id: 5f92fff9-82e2-48eb-8fc1-8b133556a551\n" +
"description: Detects remote RPC calls to possibly abuse remote encryption service via MS-EFSR\n" +
"references:\n" +
" - https://attack.mitre.org/tactics/TA0008/\n" +
" - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-36942\n" +
" - https://github.com/jsecurity101/MSRPC-to-ATTACK/blob/main/documents/MS-EFSR.md\n" +
" - https://github.com/zeronetworks/rpcfirewall\n" +
" - https://zeronetworks.com/blog/stopping_lateral_movement_via_the_rpc_firewall/\n" +
"tags:\n" +
" - attack.lateral_movement\n" +
"status: experimental\n" +
"author: Sagie Dulce, Dekel Paz\n" +
"date: 2022/01/01\n" +
"modified: 2022/01/01\n" +
"logsource:\n" +
" product: rpc_firewall\n" +
" category: application\n" +
" definition: 'Requirements: install and apply the RPC Firewall to all processes with \"audit:true action:block uuid:df1941c5-fe89-4e79-bf10-463657acf44d or c681d488-d850-11d0-8c52-00c04fd90f7e'\n" +
"detection:\n" +
" selection:\n" +
" EventID: 24\n" +
" condition: selection\n" +
"falsepositives:\n" +
" - Legitimate usage of remote file encryption\n" +
"level: high";
}

public static String randomRuleWithErrors() {
return "title: Remote Encrypting File System Abuse\n" +
"id: 5f92fff9-82e2-48eb-8fc1-8b133556a551\n" +
Expand All @@ -743,6 +772,27 @@ public static String randomRuleWithErrors() {
"level: high";
}

public static String randomRuleWithErrors(String title) {
return "title: " + title + "\n" +
"id: 5f92fff9-82e2-48eb-8fc1-8b133556a551\n" +
"description: Detects remote RPC calls to possibly abuse remote encryption service via MS-EFSR\n" +
"references:\n" +
" - https://attack.mitre.org/tactics/TA0008/\n" +
" - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-36942\n" +
" - https://github.com/jsecurity101/MSRPC-to-ATTACK/blob/main/documents/MS-EFSR.md\n" +
" - https://github.com/zeronetworks/rpcfirewall\n" +
" - https://zeronetworks.com/blog/stopping_lateral_movement_via_the_rpc_firewall/\n" +
"tags:\n" +
" - attack.lateral_movement\n" +
"status: experimental\n" +
"author: Sagie Dulce, Dekel Paz\n" +
"date: 2022/01/01\n" +
"modified: 2022/01/01\n" +
"falsepositives:\n" +
" - Legitimate usage of remote file encryption\n" +
"level: high";
}

public static String toJsonStringWithUser(Detector detector) throws IOException {
XContentBuilder builder = XContentFactory.jsonBuilder();
builder = detector.toXContentWithUser(builder, ToXContent.EMPTY_PARAMS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import static org.opensearch.securityanalytics.TestHelpers.randomRuleForMappingView;
import static org.opensearch.securityanalytics.TestHelpers.randomRuleWithErrors;
import static org.opensearch.securityanalytics.TestHelpers.windowsIndexMapping;
import static org.opensearch.securityanalytics.TestHelpers.randomEditedRuleInvalidSyntax;

public class RuleRestApiIT extends SecurityAnalyticsRestTestCase {

Expand Down Expand Up @@ -157,15 +158,18 @@ public void testCreatingAggregationRule() throws SigmaError, IOException {

@SuppressWarnings("unchecked")
public void testCreatingARuleWithWrongSyntax() throws IOException {
String rule = randomRuleWithErrors();
String invalidSigmaRuleTitle = "a".repeat(257);
String rule = randomRuleWithErrors(invalidSigmaRuleTitle);

try {
makeRequest(client(), "POST", SecurityAnalyticsPlugin.RULE_BASE_URI, Collections.singletonMap("category", randomDetectorType()),
new StringEntity(rule), new BasicHeader("Content-Type", "application/json"));
fail("Invalid rule syntax, creation should have failed");
} catch (ResponseException ex) {
Map<String, Object> responseBody = asMap(ex.getResponse());
String reason = ((Map<String, Object>) responseBody.get("error")).get("reason").toString();
Assert.assertEquals("{\"error\":\"Sigma rule must have a log source\",\"error\":\"Sigma rule must have a detection definitions\"}", reason);
Assert.assertEquals("{\"error\":\"Sigma rule title can be max 256 characters\",\"error\":\"Sigma rule must have a log source\"," +
"\"error\":\"Sigma rule must have a detection definitions\"}", reason);
}
}

Expand Down Expand Up @@ -403,6 +407,46 @@ public void testUpdatingUnusedRule() throws IOException {
Assert.assertEquals("Update rule failed", RestStatus.OK, restStatus(updateResponse));
}

public void testUpdatingUnusedRuleWithWrongSyntax() throws IOException {
String index = createTestIndex(randomIndex(), windowsIndexMapping());

// Execute CreateMappingsAction to add alias mapping for index
Request createMappingRequest = new Request("POST", SecurityAnalyticsPlugin.MAPPER_BASE_URI);
// both req params and req body are supported
createMappingRequest.setJsonEntity(
"{ \"index_name\":\"" + index + "\"," +
" \"rule_topic\":\"" + randomDetectorType() + "\", " +
" \"partial\":true" +
"}"
);

Response response = client().performRequest(createMappingRequest);
assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());

String rule = randomRule();

Response createResponse = makeRequest(client(), "POST", SecurityAnalyticsPlugin.RULE_BASE_URI, Collections.singletonMap("category", randomDetectorType()),
new StringEntity(rule), new BasicHeader("Content-Type", "application/json"));
Assert.assertEquals("Create rule failed", RestStatus.CREATED, restStatus(createResponse));

// update rule with invalid syntax
Map<String, Object> responseBody = asMap(createResponse);
String createdId = responseBody.get("_id").toString();

String invalidSigmaRuleTitle = "a".repeat(257);
String updatedRule = randomEditedRuleInvalidSyntax(invalidSigmaRuleTitle);

try {
makeRequest(client(), "PUT", SecurityAnalyticsPlugin.RULE_BASE_URI + "/" + createdId, Map.of("category", randomDetectorType()),
new StringEntity(updatedRule), new BasicHeader("Content-Type", "application/json"));
fail("Invalid rule name, updation should fail");
} catch (ResponseException ex) {
responseBody = asMap(ex.getResponse());
String reason = ((Map<String, Object>) responseBody.get("error")).get("reason").toString();
Assert.assertEquals("Sigma rule title can be max 256 characters", reason);
}
}

public void testUpdatingARule_custom_category() throws IOException {
String index = createTestIndex(randomIndex(), windowsIndexMapping());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.opensearch.securityanalytics.rules.exceptions.SigmaModifierError;
import org.opensearch.securityanalytics.rules.exceptions.SigmaRegularExpressionError;
import org.opensearch.securityanalytics.rules.exceptions.SigmaStatusError;
import org.opensearch.securityanalytics.rules.exceptions.SigmaTitleError;
import org.opensearch.securityanalytics.rules.exceptions.SigmaValueError;
import org.opensearch.securityanalytics.rules.modifiers.SigmaContainsModifier;
import org.opensearch.securityanalytics.rules.modifiers.SigmaEndswithModifier;
Expand Down Expand Up @@ -88,6 +89,37 @@ public void testSigmaRuleBadDate() {
});
}

public void testSigmaRuleBadTitle() {
Map<String, Object> sigmaRule = new HashMap<>();
sigmaRule.put("id", java.util.UUID.randomUUID().toString());
sigmaRule.put("level", "critical");
sigmaRule.put("status", "experimental");
sigmaRule.put("date", "2017/05/15");

// test empty string
String invalidSigmaRuleTitle = "";
sigmaRule.put("title", invalidSigmaRuleTitle);

Exception exception = assertThrows(SigmaTitleError.class, () -> {
SigmaRule.fromDict(sigmaRule, false);
});

String expectedMessage = "Sigma rule title can be max 256 characters";
String actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessage));

// test string over 256 chars
invalidSigmaRuleTitle = "a".repeat(257);
sigmaRule.put("title", invalidSigmaRuleTitle);

exception = assertThrows(SigmaTitleError.class, () -> {
SigmaRule.fromDict(sigmaRule, false);
});

actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessage));
}

public void testSigmaRuleNoLogSource() {
Map<String, Object> sigmaRule = new HashMap<>();
sigmaRule.put("id", java.util.UUID.randomUUID().toString());
Expand Down
Loading