Skip to content

Commit 7838d6b

Browse files
committed
EQL: Add wildcard functionality to : operator (#65188)
Additionally, restrict the grammar to allow only a constant on the right hand side of :. Fix #65154 (cherry picked from commit db43a1c)
1 parent e1faf82 commit 7838d6b

File tree

15 files changed

+666
-411
lines changed

15 files changed

+666
-411
lines changed

x-pack/plugin/eql/qa/common/src/main/resources/test_queries.toml

Lines changed: 67 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -342,19 +342,19 @@ query = '''
342342
registry where length(bytes_written_string_list) == 2 and bytes_written_string_list[1] : "EN"
343343
'''
344344

345-
# [[queries]]
346-
# name = "keyPathWildcard"
347-
# query = '''
348-
# registry where key_path : "*\\MACHINE\\SAM\\SAM\\*\\Account\\Us*ers\\00*03E9\\F"
349-
# '''
350-
# expected_event_ids = [79]
351-
#
352-
# [[queries]]
353-
# name = "processPathWildcardAndIN"
354-
# query = '''
355-
# process where process_path : "*\\red_ttp\\wininit.*" and opcode in (0,1,2,3,4)
356-
# '''
357-
# expected_event_ids = [84, 85]
345+
[[queries]]
346+
name = "keyPathWildcard"
347+
query = '''
348+
registry where key_path : "*\\MACHINE\\SAM\\SAM\\*\\Account\\Us*ers\\00*03E9\\F"
349+
'''
350+
expected_event_ids = [79]
351+
352+
[[queries]]
353+
name = "processPathWildcardAndIN"
354+
query = '''
355+
process where process_path : "*\\red_ttp\\wininit.*" and opcode in (0,1,2,3,4)
356+
'''
357+
expected_event_ids = [84, 85]
358358

359359
[[queries]]
360360
name = "descendant1"
@@ -385,13 +385,13 @@ process where opcode==1 and process_name : "smss.exe"
385385
'''
386386
expected_event_ids = [78]
387387

388-
# [[queries]]
389-
# name = "wildcardAndMultipleConditions1"
390-
# query = '''
391-
# file where file_path:"*\\red_ttp\\winin*.*"
392-
# and opcode in (0,1,2) and user_name:"vagrant"
393-
# '''
394-
# expected_event_ids = [83, 86]
388+
[[queries]]
389+
name = "wildcardAndMultipleConditions1"
390+
query = '''
391+
file where file_path:"*\\red_ttp\\winin*.*"
392+
and opcode in (0,1,2) and user_name:"vagrant"
393+
'''
394+
expected_event_ids = [83, 86]
395395

396396
[[queries]]
397397
name = "wildcardAndMultipleConditions2"
@@ -401,13 +401,13 @@ file where file_path:"*\\red_ttp\\winin*.*"
401401
'''
402402
expected_event_ids = []
403403

404-
# [[queries]]
405-
# name = "wildcardAndMultipleConditions3"
406-
# query = '''
407-
# file where file_path:"*\\red_ttp\\winin*.*"
408-
# and opcode not in (3, 4, 5, 6 ,7) and user_name:"vagrant"
409-
# '''
410-
# expected_event_ids = [83, 86]
404+
[[queries]]
405+
name = "wildcardAndMultipleConditions3"
406+
query = '''
407+
file where file_path:"*\\red_ttp\\winin*.*"
408+
and opcode not in (3, 4, 5, 6 ,7) and user_name:"vagrant"
409+
'''
410+
expected_event_ids = [83, 86]
411411

412412

413413
[[queries]]
@@ -878,16 +878,16 @@ sequence
878878
'''
879879
expected_event_ids = [87, 92]
880880

881-
# [[queries]]
882-
# name = "doubleSameSequenceWithByUntilAndHead1"
883-
# query = '''
884-
# sequence
885-
# [file where opcode==0 and file_name:"*.exe"] by unique_pid
886-
# [file where opcode==0 and file_name:"*.exe"] by unique_pid
887-
# until [process where opcode==5000] by unique_ppid
888-
# | head 1
889-
# '''
890-
# expected_event_ids = [55, 61]
881+
[[queries]]
882+
name = "doubleSameSequenceWithByUntilAndHead1"
883+
query = '''
884+
sequence
885+
[file where opcode==0 and file_name:"*.exe"] by unique_pid
886+
[file where opcode==0 and file_name:"*.exe"] by unique_pid
887+
until [process where opcode==5000] by unique_ppid
888+
| head 1
889+
'''
890+
expected_event_ids = [55, 61]
891891

892892
[[queries]]
893893
name = "doubleSameSequenceWithByUntilAndHead2"
@@ -1040,16 +1040,16 @@ query = '''
10401040
registry where length(bad_field) > 0
10411041
'''
10421042

1043-
# [[queries]]
1044-
# name = "multipleConditions2"
1045-
# query = '''
1046-
# process where opcode == 1
1047-
# and process_name in ("net.exe", "net1.exe")
1048-
# and not (parent_process_name : "net.exe"
1049-
# and process_name : "net1.exe")
1050-
# and command_line : "*group *admin*" and command_line != "* /add*"
1051-
# '''
1052-
# expected_event_ids = [97]
1043+
[[queries]]
1044+
name = "multipleConditions2"
1045+
query = '''
1046+
process where opcode == 1
1047+
and process_name in ("net.exe", "net1.exe")
1048+
and not (parent_process_name : "net.exe"
1049+
and process_name : "net1.exe")
1050+
and command_line : "*group *admin*" and command_line != "* /add*"
1051+
'''
1052+
expected_event_ids = [97]
10531053

10541054
[[queries]]
10551055
name = "anyWithUnique"
@@ -1246,26 +1246,26 @@ sequence
12461246
'''
12471247
expected_event_ids = [54, 55, 56, 54, 61, 62, 54, 67, 68, 54, 72, 73]
12481248

1249-
# [[queries]]
1250-
# name = "wildcard1"
1251-
# query = '''
1252-
# process where command_line : "*%*"
1253-
# '''
1254-
# expected_event_ids = [4, 6, 28]
1255-
#
1256-
# [[queries]]
1257-
# name = "wildcard2"
1258-
# query = '''
1259-
# process where command_line : "*%*%*"
1260-
# '''
1261-
# expected_event_ids = [4, 6, 28]
1262-
#
1263-
# [[queries]]
1264-
# name = "wildcard3"
1265-
# query = '''
1266-
# process where command_line : "%*%*"
1267-
# '''
1268-
# expected_event_ids = [4, 6, 28]
1249+
[[queries]]
1250+
name = "wildcard1"
1251+
query = '''
1252+
process where command_line : "*%*"
1253+
'''
1254+
expected_event_ids = [4, 6, 28]
1255+
1256+
[[queries]]
1257+
name = "wildcard2"
1258+
query = '''
1259+
process where command_line : "*%*%*"
1260+
'''
1261+
expected_event_ids = [4, 6, 28]
1262+
1263+
[[queries]]
1264+
name = "wildcard3"
1265+
query = '''
1266+
process where command_line : "%*%*"
1267+
'''
1268+
expected_event_ids = [4, 6, 28]
12691269

12701270
[[queries]]
12711271
name = "uniqueCount1"

x-pack/plugin/eql/qa/correctness/src/javaRestTest/resources/queries.toml

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -487,26 +487,26 @@ sequence by hostname with maxspan=5m
487487
time = 7.179757833480835
488488
type = "sequence"
489489

490-
#[[queries]]
491-
#queryNo = 28
492-
#case_insensitive = true
493-
#count = 1
494-
#expected_event_ids = [3299718, 3364047]
495-
#filter_counts = [24, 3, 37]
496-
#filters = [
497-
# 'process where process_name == "powershell.exe" and opcode == 1',
498-
# 'powershell where message == "*Get-NetShare*"',
499-
# 'process where process_name == "powershell.exe" and opcode == 2'
500-
#]
501-
#query = """
502-
#sequence by hostname, pid
503-
# [process where process_name == "powershell.exe" and opcode == 1]
504-
# [powershell where message == "*Get-NetShare*"]
505-
#until
506-
# [process where process_name == "powershell.exe" and opcode == 2]
507-
#"""
508-
#time = 2.123962879180908
509-
#type = "sequence"
490+
[[queries]]
491+
queryNo = 28
492+
case_insensitive = true
493+
count = 1
494+
expected_event_ids = [3299718, 3364047]
495+
filter_counts = [24, 3, 37]
496+
filters = [
497+
'process where process_name : "powershell.exe" and opcode == 1',
498+
'powershell where message : "*Get-NetShare*"',
499+
'process where process_name : "powershell.exe" and opcode == 2'
500+
]
501+
query = """
502+
sequence by hostname, pid
503+
[process where process_name : "powershell.exe" and opcode == 1]
504+
[powershell where message : "*Get-NetShare*"]
505+
until
506+
[process where process_name : "powershell.exe" and opcode == 2]
507+
"""
508+
time = 2.123962879180908
509+
type = "sequence"
510510

511511
[[queries]]
512512
queryNo = 29

x-pack/plugin/eql/src/main/antlr/EqlBase.g4

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ operatorExpression
100100
// https://github.com/antlr/antlr4/issues/781
101101
predicate
102102
: NOT? kind=IN LP expression (COMMA expression)* RP
103+
| seqPredicate
103104
;
104105

105106
primaryExpression
@@ -124,8 +125,12 @@ constant
124125
| string #stringLiteral
125126
;
126127

128+
seqPredicate
129+
: SEQ constant #seqValue
130+
;
131+
127132
comparisonOperator
128-
: SEQ | EQ | NEQ | LT | LTE | GT | GTE
133+
: EQ | NEQ | LT | LTE | GT | GTE
129134
;
130135

131136
booleanValue

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/optimizer/Optimizer.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
package org.elasticsearch.xpack.eql.optimizer;
88

99
import org.elasticsearch.xpack.eql.EqlIllegalArgumentException;
10+
import org.elasticsearch.xpack.eql.expression.predicate.operator.comparison.InsensitiveBinaryComparison;
11+
import org.elasticsearch.xpack.eql.expression.predicate.operator.comparison.InsensitiveNotEquals;
1012
import org.elasticsearch.xpack.eql.plan.logical.Join;
1113
import org.elasticsearch.xpack.eql.plan.logical.KeyedFilter;
1214
import org.elasticsearch.xpack.eql.plan.logical.LimitWithOffset;
1315
import org.elasticsearch.xpack.eql.plan.physical.LocalRelation;
1416
import org.elasticsearch.xpack.eql.session.Payload.Type;
1517
import org.elasticsearch.xpack.eql.util.MathUtils;
18+
import org.elasticsearch.xpack.eql.util.StringUtils;
1619
import org.elasticsearch.xpack.ql.expression.Expression;
1720
import org.elasticsearch.xpack.ql.expression.FieldAttribute;
1821
import org.elasticsearch.xpack.ql.expression.Literal;
@@ -23,12 +26,14 @@
2326
import org.elasticsearch.xpack.ql.expression.predicate.Predicates;
2427
import org.elasticsearch.xpack.ql.expression.predicate.logical.And;
2528
import org.elasticsearch.xpack.ql.expression.predicate.logical.BinaryLogic;
29+
import org.elasticsearch.xpack.ql.expression.predicate.logical.Not;
2630
import org.elasticsearch.xpack.ql.expression.predicate.logical.Or;
2731
import org.elasticsearch.xpack.ql.expression.predicate.nulls.IsNotNull;
2832
import org.elasticsearch.xpack.ql.expression.predicate.nulls.IsNull;
2933
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.BinaryComparison;
3034
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.Equals;
3135
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.NotEquals;
36+
import org.elasticsearch.xpack.ql.expression.predicate.regex.Like;
3237
import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.BooleanFunctionEqualsElimination;
3338
import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.BooleanLiteralsOnTheRight;
3439
import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.BooleanSimplification;
@@ -66,6 +71,7 @@ public LogicalPlan optimize(LogicalPlan verified) {
6671
@Override
6772
protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
6873
Batch substitutions = new Batch("Substitution", Limiter.ONCE,
74+
new ReplaceWildcards(),
6975
new ReplaceSurrogateFunction(),
7076
new ReplaceRegexMatch());
7177

@@ -106,6 +112,47 @@ protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
106112
return asList(substitutions, syntactic, operators, constraints, operators, ordering, local, label);
107113
}
108114

115+
private static class ReplaceWildcards extends OptimizerRule<Filter> {
116+
117+
@Override
118+
protected LogicalPlan rule(Filter filter) {
119+
return filter.transformExpressionsUp(e -> {
120+
// expr : "wildcard*phrase" || expr !: "wildcard*phrase"
121+
if (e instanceof InsensitiveBinaryComparison) {
122+
InsensitiveBinaryComparison cmp = (InsensitiveBinaryComparison) e;
123+
124+
Expression target = null;
125+
String wildString = null;
126+
127+
// check only the right side
128+
if (isWildcard(cmp.right())) {
129+
wildString = (String) cmp.right().fold();
130+
target = cmp.left();
131+
}
132+
133+
if (target != null) {
134+
Expression like = new Like(e.source(), target, StringUtils.toLikePattern(wildString), true);
135+
if (e instanceof InsensitiveNotEquals) {
136+
like = new Not(e.source(), like);
137+
}
138+
139+
e = like;
140+
}
141+
}
142+
143+
return e;
144+
});
145+
}
146+
147+
private static boolean isWildcard(Expression expr) {
148+
if (expr instanceof Literal) {
149+
Object value = expr.fold();
150+
return value instanceof String && ((String) value).contains("*");
151+
}
152+
return false;
153+
}
154+
}
155+
109156
private static class AddMissingEquals extends OptimizerRule<Filter> {
110157

111158
@Override

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/EqlBaseBaseListener.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,18 @@ class EqlBaseBaseListener implements EqlBaseListener {
431431
* <p>The default implementation does nothing.</p>
432432
*/
433433
@Override public void exitStringLiteral(EqlBaseParser.StringLiteralContext ctx) { }
434+
/**
435+
* {@inheritDoc}
436+
*
437+
* <p>The default implementation does nothing.</p>
438+
*/
439+
@Override public void enterSeqValue(EqlBaseParser.SeqValueContext ctx) { }
440+
/**
441+
* {@inheritDoc}
442+
*
443+
* <p>The default implementation does nothing.</p>
444+
*/
445+
@Override public void exitSeqValue(EqlBaseParser.SeqValueContext ctx) { }
434446
/**
435447
* {@inheritDoc}
436448
*

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/EqlBaseBaseVisitor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,13 @@ class EqlBaseBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements EqlBa
256256
* {@link #visitChildren} on {@code ctx}.</p>
257257
*/
258258
@Override public T visitStringLiteral(EqlBaseParser.StringLiteralContext ctx) { return visitChildren(ctx); }
259+
/**
260+
* {@inheritDoc}
261+
*
262+
* <p>The default implementation returns the result of calling
263+
* {@link #visitChildren} on {@code ctx}.</p>
264+
*/
265+
@Override public T visitSeqValue(EqlBaseParser.SeqValueContext ctx) { return visitChildren(ctx); }
259266
/**
260267
* {@inheritDoc}
261268
*

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/EqlBaseListener.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,18 @@ interface EqlBaseListener extends ParseTreeListener {
391391
* @param ctx the parse tree
392392
*/
393393
void exitStringLiteral(EqlBaseParser.StringLiteralContext ctx);
394+
/**
395+
* Enter a parse tree produced by the {@code seqValue}
396+
* labeled alternative in {@link EqlBaseParser#seqPredicate}.
397+
* @param ctx the parse tree
398+
*/
399+
void enterSeqValue(EqlBaseParser.SeqValueContext ctx);
400+
/**
401+
* Exit a parse tree produced by the {@code seqValue}
402+
* labeled alternative in {@link EqlBaseParser#seqPredicate}.
403+
* @param ctx the parse tree
404+
*/
405+
void exitSeqValue(EqlBaseParser.SeqValueContext ctx);
394406
/**
395407
* Enter a parse tree produced by {@link EqlBaseParser#comparisonOperator}.
396408
* @param ctx the parse tree

0 commit comments

Comments
 (0)