This seems counter-intuitive at first glance, but let me cook.
One of the problems with Anubis is that the rule matching is super
deterministic. This means that attackers can figure out what patterns
they are hitting and change things to bypass them.
The randInt function lets you have rulesets behave nondeterministically.
This is a very easy way to hang yourself, but can be great to
psychologically mess with scraper operators. Consider this rule:
```yaml
- name: deny-lightpanda-sometimes
action: DENY
expression:
all:
- userAgent.matches("LightPanda")
- randInt(16) >= 4
```
It would match about 75% of the time.
Signed-off-by: Xe Iaso <me@xeiaso.net>
63 lines
1.7 KiB
Go
63 lines
1.7 KiB
Go
package expressions
|
|
|
|
import (
|
|
"math/rand/v2"
|
|
|
|
"github.com/google/cel-go/cel"
|
|
"github.com/google/cel-go/common/types"
|
|
"github.com/google/cel-go/common/types/ref"
|
|
"github.com/google/cel-go/ext"
|
|
)
|
|
|
|
// NewEnvironment creates a new CEL environment, this is the set of
|
|
// variables and functions that are passed into the CEL scope so that
|
|
// Anubis can fail loudly and early when something is invalid instead
|
|
// of blowing up at runtime.
|
|
func NewEnvironment() (*cel.Env, error) {
|
|
return cel.NewEnv(
|
|
ext.Strings(
|
|
ext.StringsLocale("en_US"),
|
|
ext.StringsValidateFormatCalls(true),
|
|
),
|
|
|
|
// default all timestamps to UTC
|
|
cel.DefaultUTCTimeZone(true),
|
|
|
|
// Variables exposed to CEL programs:
|
|
cel.Variable("remoteAddress", cel.StringType),
|
|
cel.Variable("host", cel.StringType),
|
|
cel.Variable("method", cel.StringType),
|
|
cel.Variable("userAgent", cel.StringType),
|
|
cel.Variable("path", cel.StringType),
|
|
cel.Variable("query", cel.MapType(cel.StringType, cel.StringType)),
|
|
cel.Variable("headers", cel.MapType(cel.StringType, cel.StringType)),
|
|
|
|
// Functions exposed to CEL programs:
|
|
cel.Function("randInt",
|
|
cel.Overload("randInt_int",
|
|
[]*cel.Type{cel.IntType},
|
|
cel.IntType,
|
|
cel.UnaryBinding(func(val ref.Val) ref.Val {
|
|
n, ok := val.(types.Int)
|
|
if !ok {
|
|
return types.ValOrErr(val, "value is not an integer, but is %T", val)
|
|
}
|
|
|
|
return types.Int(rand.IntN(int(n)))
|
|
}),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
|
|
// Compile takes CEL environment and syntax tree then emits an optimized
|
|
// Program for execution.
|
|
func Compile(env *cel.Env, ast *cel.Ast) (cel.Program, error) {
|
|
return env.Program(
|
|
ast,
|
|
cel.EvalOptions(
|
|
// optimize regular expressions right now instead of on the fly
|
|
cel.OptOptimize,
|
|
),
|
|
)
|
|
}
|