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

backport panic removal + selector check patch before merge in upstream #6

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
14 changes: 7 additions & 7 deletions experimental/plugins/plugintypes/transaction.go
Original file line number Diff line number Diff line change
@@ -100,20 +100,20 @@ type TransactionVariables interface {
RequestHeaders() collection.Map
ResponseHeaders() collection.Map
MultipartName() collection.Map
MatchedVarsNames() collection.Collection
MatchedVarsNames() collection.Keyed
MultipartFilename() collection.Map
MatchedVars() collection.Map
FilesSizes() collection.Map
FilesNames() collection.Map
FilesTmpContent() collection.Map
ResponseHeadersNames() collection.Collection
RequestHeadersNames() collection.Collection
RequestCookiesNames() collection.Collection
ResponseHeadersNames() collection.Keyed
RequestHeadersNames() collection.Keyed
RequestCookiesNames() collection.Keyed
XML() collection.Map
RequestXML() collection.Map
ResponseXML() collection.Map
ArgsNames() collection.Collection
ArgsGetNames() collection.Collection
ArgsPostNames() collection.Collection
ArgsNames() collection.Keyed
ArgsGetNames() collection.Keyed
ArgsPostNames() collection.Keyed
MultipartStrictError() collection.Single
}
7 changes: 7 additions & 0 deletions http/middleware_test.go
Original file line number Diff line number Diff line change
@@ -332,6 +332,12 @@ func TestHttpServer(t *testing.T) {
expectedStatus: 403,
expectedRespHeadersKeys: expectedBlockingHeaders,
},
"deny based on number of post arguments matching a name": {
reqURI: "/hello?foobar=1&foobar=2",
expectedProto: "HTTP/1.1",
expectedStatus: 403,
expectedRespHeadersKeys: expectedBlockingHeaders,
},
}

logger := debuglog.Default().
@@ -358,6 +364,7 @@ func TestHttpServer(t *testing.T) {
SecRule RESPONSE_HEADERS:Foo "@pm bar" "id:199,phase:3,deny,t:lowercase,deny, status:401,msg:'Invalid response header',log,auditlog"
SecRule RESPONSE_BODY "@contains password" "id:200, phase:4,deny, status:403,msg:'Invalid response body',log,auditlog"
SecRule REQUEST_URI "/allow_me" "id:9,phase:1,allow,msg:'ALLOWED'"
SecRule &ARGS_GET_NAMES:foobar "@eq 2" "id:11,phase:1,deny, status:403,msg:'Invalid foobar',log,auditlog"
`).WithErrorCallback(errLogger(t)).WithDebugLogger(logger)
if l := tCase.reqBodyLimit; l > 0 {
conf = conf.WithRequestBodyAccess().WithRequestBodyLimit(l).WithRequestBodyInMemoryLimit(l)
36 changes: 33 additions & 3 deletions internal/collections/named.go
Original file line number Diff line number Diff line change
@@ -80,7 +80,7 @@ func (c *NamedCollection) Reset() {
c.Map.Reset()
}

func (c *NamedCollection) Names(rv variables.RuleVariable) collection.Collection {
func (c *NamedCollection) Names(rv variables.RuleVariable) collection.Keyed {
return &NamedCollectionNames{
variable: rv,
collection: c,
@@ -101,11 +101,41 @@ type NamedCollectionNames struct {
}

func (c *NamedCollectionNames) FindRegex(key *regexp.Regexp) []types.MatchData {
panic("selection operator not supported")
var res []types.MatchData

for k, data := range c.collection.Map.data {
if key.MatchString(k) {
for _, d := range data {
res = append(res, &corazarules.MatchData{
Variable_: c.variable,
Key_: d.key,
Value_: d.key,
})
}
}
}
return res
}

func (c *NamedCollectionNames) FindString(key string) []types.MatchData {
panic("selection operator not supported")
var res []types.MatchData

for k, data := range c.collection.Map.data {
if k == key {
for _, d := range data {
res = append(res, &corazarules.MatchData{
Variable_: c.variable,
Key_: d.key,
Value_: d.key,
})
}
}
}
return res
}

func (c *NamedCollectionNames) Get(key string) []string {
return c.collection.Map.Get(key)
}

func (c *NamedCollectionNames) FindAll() []types.MatchData {
24 changes: 24 additions & 0 deletions internal/collections/named_test.go
Original file line number Diff line number Diff line change
@@ -87,3 +87,27 @@ func TestNamedCollection(t *testing.T) {
}

}

func TestNames(t *testing.T) {
c := NewNamedCollection(variables.ArgsPost)
if c.Name() != "ARGS_POST" {
t.Error("Error getting name")
}

c.SetIndex("key", 1, "value")
c.Set("key2", []string{"value2", "value3"})

names := c.Names(variables.ArgsPostNames)

r := names.FindString("key2")

if len(r) != 2 {
t.Errorf("Error finding string, got %d instead of 2", len(r))
}

r = names.FindRegex(regexp.MustCompile("key.*"))

if len(r) != 3 {
t.Errorf("Error finding regex, got %d instead of 3", len(r))
}
}
36 changes: 19 additions & 17 deletions internal/corazawaf/transaction.go
Original file line number Diff line number Diff line change
@@ -571,13 +571,15 @@ func (tx *Transaction) GetField(rv ruleVariableParams) []types.MatchData {
if m, ok := col.(collection.Keyed); ok {
matches = m.FindRegex(rv.KeyRx)
} else {
panic("attempted to use regex with non-selectable collection: " + rv.Variable.Name())
// This should probably never happen, selectability is checked at parsing time
tx.debugLogger.Error().Str("collection", rv.Variable.Name()).Msg("attempted to use regex with non-selectable collection")
}
case rv.KeyStr != "":
if m, ok := col.(collection.Keyed); ok {
matches = m.FindString(rv.KeyStr)
} else {
panic("attempted to use string with non-selectable collection: " + rv.Variable.Name())
// This should probably never happen, selectability is checked at parsing time
tx.debugLogger.Error().Str("collection", rv.Variable.Name()).Msg("attempted to use string with non-selectable collection")
}
default:
matches = col.FindAll()
@@ -1588,11 +1590,11 @@ type TransactionVariables struct {
args *collections.ConcatKeyed
argsCombinedSize *collections.SizeCollection
argsGet *collections.NamedCollection
argsGetNames collection.Collection
argsNames *collections.ConcatCollection
argsGetNames collection.Keyed
argsNames *collections.ConcatKeyed
argsPath *collections.NamedCollection
argsPost *collections.NamedCollection
argsPostNames collection.Collection
argsPostNames collection.Keyed
duration *collections.Single
env *collections.Map
files *collections.Map
@@ -1608,7 +1610,7 @@ type TransactionVariables struct {
matchedVar *collections.Single
matchedVarName *collections.Single
matchedVars *collections.NamedCollection
matchedVarsNames collection.Collection
matchedVarsNames collection.Keyed
multipartDataAfter *collections.Single
multipartFilename *collections.Map
multipartName *collections.Map
@@ -1628,10 +1630,10 @@ type TransactionVariables struct {
requestBody *collections.Single
requestBodyLength *collections.Single
requestCookies *collections.NamedCollection
requestCookiesNames collection.Collection
requestCookiesNames collection.Keyed
requestFilename *collections.Single
requestHeaders *collections.NamedCollection
requestHeadersNames collection.Collection
requestHeadersNames collection.Keyed
requestLine *collections.Single
requestMethod *collections.Single
requestProtocol *collections.Single
@@ -1642,7 +1644,7 @@ type TransactionVariables struct {
responseContentLength *collections.Single
responseContentType *collections.Single
responseHeaders *collections.NamedCollection
responseHeadersNames collection.Collection
responseHeadersNames collection.Keyed
responseProtocol *collections.Single
responseStatus *collections.Single
responseXML *collections.Map
@@ -1756,7 +1758,7 @@ func NewTransactionVariables() *TransactionVariables {
v.argsPost,
v.argsPath,
)
v.argsNames = collections.NewConcatCollection(
v.argsNames = collections.NewConcatKeyed(
variables.ArgsNames,
v.argsGetNames,
v.argsPostNames,
@@ -1982,7 +1984,7 @@ func (v *TransactionVariables) MultipartName() collection.Map {
return v.multipartName
}

func (v *TransactionVariables) MatchedVarsNames() collection.Collection {
func (v *TransactionVariables) MatchedVarsNames() collection.Keyed {
return v.matchedVarsNames
}

@@ -2006,19 +2008,19 @@ func (v *TransactionVariables) FilesTmpContent() collection.Map {
return v.filesTmpContent
}

func (v *TransactionVariables) ResponseHeadersNames() collection.Collection {
func (v *TransactionVariables) ResponseHeadersNames() collection.Keyed {
return v.responseHeadersNames
}

func (v *TransactionVariables) ResponseArgs() collection.Map {
return v.responseArgs
}

func (v *TransactionVariables) RequestHeadersNames() collection.Collection {
func (v *TransactionVariables) RequestHeadersNames() collection.Keyed {
return v.requestHeadersNames
}

func (v *TransactionVariables) RequestCookiesNames() collection.Collection {
func (v *TransactionVariables) RequestCookiesNames() collection.Keyed {
return v.requestCookiesNames
}

@@ -2038,15 +2040,15 @@ func (v *TransactionVariables) ResponseBodyProcessor() collection.Single {
return v.resBodyProcessor
}

func (v *TransactionVariables) ArgsNames() collection.Collection {
func (v *TransactionVariables) ArgsNames() collection.Keyed {
return v.argsNames
}

func (v *TransactionVariables) ArgsGetNames() collection.Collection {
func (v *TransactionVariables) ArgsGetNames() collection.Keyed {
return v.argsGetNames
}

func (v *TransactionVariables) ArgsPostNames() collection.Collection {
func (v *TransactionVariables) ArgsPostNames() collection.Keyed {
return v.argsPostNames
}

3 changes: 3 additions & 0 deletions internal/seclang/rule_parser.go
Original file line number Diff line number Diff line change
@@ -70,6 +70,9 @@ func (rp *RuleParser) ParseVariables(vars string) error {
if err != nil {
return err
}
if curr == 1 && !v.CanBeSelected() {
return fmt.Errorf("attempting to select a value inside a non-selectable collection: %s", string(curVar))
}
// fmt.Printf("(PREVIOUS %s) %s:%s (%t %t)\n", vars, curvar, curkey, iscount, isnegation)
if isquoted {
// if it is quoted we remove the last quote
11 changes: 11 additions & 0 deletions internal/seclang/rule_parser_test.go
Original file line number Diff line number Diff line change
@@ -303,6 +303,17 @@ func TestParseRule(t *testing.T) {
}
}

func TestNonSelectableCollection(t *testing.T) {
waf := corazawaf.NewWAF()
p := NewParser(waf)
err := p.FromString(`
SecRule REQUEST_URI:foo "bar" "id:1,phase:1"
`)
if err == nil {
t.Error("expected error")
}
}

func BenchmarkParseActions(b *testing.B) {
actionsToBeParsed := "id:980170,phase:5,pass,t:none,noauditlog,msg:'Anomaly Scores:Inbound Scores - Outbound Scores',tag:test"
for i := 0; i < b.N; i++ {
19 changes: 15 additions & 4 deletions internal/variables/generator/main.go
Original file line number Diff line number Diff line change
@@ -23,8 +23,9 @@ import (
var variablesMapTmpl string

type VariablesMap struct {
Key string
Value string
Key string
Value string
CanBeSelected bool
}

func main() {
@@ -74,9 +75,19 @@ func main() {
value = "FILES_TMPNAMES"
}

canBeSelected := false
if v.Comment != nil {
for _, c := range v.Comment.List {
if strings.Contains(c.Text, "CanBeSelected") {
canBeSelected = true
}
}
}

directives = append(directives, VariablesMap{
Key: name.String(),
Value: value,
Key: name.String(),
Value: value,
CanBeSelected: canBeSelected,
})
}
}
11 changes: 11 additions & 0 deletions internal/variables/generator/variablesmap.go.tmpl
Original file line number Diff line number Diff line change
@@ -22,6 +22,17 @@ func (v RuleVariable) Name() string {
}
}

//CanBeSelected returns true if the variable supports selection (ie, `:foobar`)
func (v RuleVariable) CanBeSelected() bool {
switch v {
{{range .}}case {{ .Key }}:
return {{ .CanBeSelected }}
{{end}}
default:
return false
}
}

var rulemapRev = map[string]RuleVariable{
{{range .}}"{{ .Value }}": {{ .Key }},
{{end}}
48 changes: 24 additions & 24 deletions internal/variables/variables.go
Original file line number Diff line number Diff line change
@@ -112,21 +112,21 @@ const (
// the beginning of the transaction until this point
Duration
// ResponseHeadersNames contains the names of the response headers
ResponseHeadersNames
ResponseHeadersNames // CanBeSelected
// RequestHeadersNames contains the names of the request headers
RequestHeadersNames
RequestHeadersNames // CanBeSelected
// Args contains copies of ArgsGet and ArgsPost
Args
Args // CanBeSelected
// ArgsGet contains the GET (URL) arguments
ArgsGet
ArgsGet // CanBeSelected
// ArgsPost contains the POST (BODY) arguments
ArgsPost
ArgsPost // CanBeSelected
// ArgsPath contains the url path parts
ArgsPath
// FilesSizes contains the sizes of the uploaded files
FilesSizes
// FilesNames contains the names of the uploaded files
FilesNames
FilesNames // CanBeSelected
// FilesTmpContent is not supported
FilesTmpContent
// MultipartFilename contains the multipart data from field FILENAME
@@ -135,58 +135,58 @@ const (
MultipartName
// MatchedVarsNames is similar to MATCHED_VAR_NAME except that it is
// a collection of all matches for the current operator check.
MatchedVarsNames
MatchedVarsNames // CanBeSelected
// MatchedVars is similar to MATCHED_VAR except that it is a collection
// of all matches for the current operator check
MatchedVars
MatchedVars // CanBeSelected
// Files contains a collection of original file names
// (as they were called on the remote user’s filesys- tem).
// Available only on inspected multipart/form-data requests.
Files
// RequestCookies is a collection of all of request cookies (values only
RequestCookies
RequestCookies // CanBeSelected
// RequestHeaders can be used as either a collection of all of the request
// headers or can be used to inspect selected headers
RequestHeaders
RequestHeaders // CanBeSelected
// ResponseHeaders can be used as either a collection of all of the response
// headers or can be used to inspect selected headers
ResponseHeaders
ResponseHeaders // CanBeSelected
// ReseBodyProcessor contains the name of the response body processor used,
// no default
ResBodyProcessor
// Geo contains the location information of the client
Geo
// RequestCookiesNames contains the names of the request cookies
RequestCookiesNames
RequestCookiesNames // CanBeSelected
// FilesTmpNames contains the names of the uploaded temporal files
FilesTmpNames
FilesTmpNames // CanBeSelected
// ArgsNames contains the names of the arguments (POST and GET)
ArgsNames
ArgsNames // CanBeSelected
// ArgsGetNames contains the names of the GET arguments
ArgsGetNames
ArgsGetNames // CanBeSelected
// ArgsPostNames contains the names of the POST arguments
ArgsPostNames
ArgsPostNames // CanBeSelected
// TX contains transaction specific variables created with setvar
TX
TX // CanBeSelected
// Rule contains rule metadata
Rule
// JSON does not provide any data, might be removed
JSON
JSON // CanBeSelected
// Env contains the process environment variables
Env
Env // CanBeSelected
// UrlencodedError equals 1 if we failed to parse de URL
// It applies for URL query part and urlencoded post body
UrlencodedError
// ResponseArgs contains the response parsed arguments
ResponseArgs
ResponseArgs // CanBeSelected
// ResponseXML contains the response parsed XML
ResponseXML
ResponseXML // CanBeSelected
// RequestXML contains the request parsed XML
RequestXML
RequestXML // CanBeSelected
// XML is a pointer to ResponseXML
XML
XML // CanBeSelected
// MultipartPartHeaders contains the multipart headers
MultipartPartHeaders
MultipartPartHeaders // CanBeSelected

// Unsupported variables

201 changes: 201 additions & 0 deletions internal/variables/variablesmap.gen.go