Skip to content

Commit

Permalink
Fix metadata inspection
Browse files Browse the repository at this point in the history
  • Loading branch information
piyushroshan committed Nov 22, 2024
1 parent 80e7634 commit b1bc13f
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 24 deletions.
2 changes: 1 addition & 1 deletion experimental/types/rule_match.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ type MatchData interface {
// Chain depth of variable match
ChainLevel() int
// Metadata of the matched data
Metadata() DataMetadataList
DataMetadata() DataMetadataList
}
77 changes: 71 additions & 6 deletions experimental/types/value_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package types

import (
"net/url"
"unicode"
)

Expand All @@ -26,6 +27,22 @@ const (
ValueMetadataBoolean
// ValueMetadataUnicode represents a unicode value.
ValueMetadataUnicode
// NotValueMetadataAlphanumeric represents a non-alphanumeric value.
NotValueMetadataAlphanumeric
// NotValueMetadataAscii represents a non-ASCII value.
NotValueMetadataAscii
// NotValueMetadataBase64 represents a non-base64 value.
NotValueMetadataBase64
// NotValueMetadataURI represents a non-URI value.
NotValueMetadataURI
// NotValueMetadataDomain represents a non-domain value.
NotValueMetadataDomain
// NotValueMetadataNumeric represents a non-numeric value.
NotValueMetadataNumeric
// NotValueMetadataBoolean represents a non-boolean value.
NotValueMetadataBoolean
// NotValueMetadataUnicode represents a non-unicode value.
NotValueMetadataUnicode
)

// NewValueMetadata returns a new ValueMetadata from a string.
Expand All @@ -47,34 +64,57 @@ func NewValueMetadata(metadata string) (DataMetadata, bool) {
return ValueMetadataDomain, true
case "unicode":
return ValueMetadataUnicode, true
case "not_numeric":
return NotValueMetadataNumeric, true
case "not_boolean":
return NotValueMetadataBoolean, true
case "not_alphanumeric":
return NotValueMetadataAlphanumeric, true
case "not_ascii":
return NotValueMetadataAscii, true
case "not_base64":
return NotValueMetadataBase64, true
case "not_uri":
return NotValueMetadataURI, true
case "not_domain":
return NotValueMetadataDomain, true
case "not_unicode":
return NotValueMetadataUnicode, true
}
return 0, false
}

// DataMetadataList is a list of ValueMetadata.
type DataMetadataList struct {
metadata map[DataMetadata]bool
evaluated bool
}

func (v *DataMetadataList) Evaluate(data string) {
func NewDataMetadataList() DataMetadataList {
return DataMetadataList{}
}

func (v *DataMetadataList) EvaluateMetadata(data string) {
// we do the analysis only once
if v.metadata == nil {
if !v.evaluated {
v.metadata = make(map[DataMetadata]bool)
v.evaluateNumeric(data)
v.evaluateBoolean(data)
v.evaluateNumeric(data)
v.evaluateAlphanumeric(data)
v.evaluateAscii(data)
v.evaluateBase64(data)
// v.evaluateURI(data)
v.evaluateURI(data)
// v.evaluateDomain(data)
// v.evaluateUnicode(data)
v.evaluateUnicode(data)
v.evaluated = true
}
}

func (v *DataMetadataList) evaluateAlphanumeric(data string) bool {
for _, c := range data {
if !unicode.IsLetter(c) && !unicode.IsNumber(c) {
if !unicode.IsLetter(c) && !unicode.IsNumber(c) && !unicode.IsSpace(c){
v.metadata[ValueMetadataAlphanumeric] = false
v.metadata[NotValueMetadataAlphanumeric] = true
break
}
}
Expand All @@ -90,6 +130,7 @@ func (v *DataMetadataList) evaluateAscii(data string) bool {
}
}
v.metadata[ValueMetadataAscii] = res
v.metadata[NotValueMetadataAscii] = !res
return res
}

Expand All @@ -106,6 +147,7 @@ func (v *DataMetadataList) evaluateBase64(data string) bool {
}
}
v.metadata[ValueMetadataBase64] = res
v.metadata[NotValueMetadataBase64] = !res
return res
}

Expand All @@ -118,6 +160,7 @@ func (v *DataMetadataList) evaluateNumeric(data string) bool {
}
}
v.metadata[ValueMetadataNumeric] = res
v.metadata[NotValueMetadataNumeric] = !res
return res
}

Expand All @@ -127,6 +170,28 @@ func (v *DataMetadataList) evaluateBoolean(data string) bool {
res = true
}
v.metadata[ValueMetadataBoolean] = res
v.metadata[NotValueMetadataBoolean] = !res
return res
}

func (v *DataMetadataList) evaluateURI(data string) bool {
res := false
u, err := url.Parse(data)
v.metadata[ValueMetadataURI] = err == nil && u.Scheme != "" && u.Host != ""
v.metadata[NotValueMetadataURI] = !v.metadata[ValueMetadataURI]
return res
}

func (v *DataMetadataList) evaluateUnicode(data string) bool {
res := false
for _, c := range data {
if c > unicode.MaxASCII {
res = true
break
}
}
v.metadata[ValueMetadataUnicode] = res
v.metadata[NotValueMetadataUnicode] = !res
return res
}

Expand Down
10 changes: 7 additions & 3 deletions internal/collections/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func (c *Map) FindRegex(key *regexp.Regexp) []types.MatchData {
Variable_: c.variable,
Key_: d.key,
Value_: d.value,
Metadata_: d.metadata,
})
}
}
Expand All @@ -94,6 +95,7 @@ func (c *Map) FindString(key string) []types.MatchData {
Variable_: c.variable,
Key_: aVar.key,
Value_: aVar.value,
Metadata_: aVar.metadata,
})
}
}
Expand All @@ -109,6 +111,7 @@ func (c *Map) FindAll() []types.MatchData {
Variable_: c.variable,
Key_: d.key,
Value_: d.value,
Metadata_: d.metadata,
})
}
}
Expand All @@ -117,7 +120,7 @@ func (c *Map) FindAll() []types.MatchData {

// Add adds a new key-value pair to the map.
func (c *Map) Add(key string, value string) {
aVal := keyValue{key: key, value: value}
aVal := keyValue{key: key, value: value, metadata: &types.DataMetadataList{}}
if !c.isCaseSensitive {
key = strings.ToLower(key)
}
Expand All @@ -137,7 +140,7 @@ func (c *Map) Set(key string, values []string) {
dataSlice = dataSlice[:len(values)] // Reuse existing slice with the same length
}
for i, v := range values {
dataSlice[i] = keyValue{key: originalKey, value: v}
dataSlice[i] = keyValue{key: originalKey, value: v, metadata: &types.DataMetadataList{}}
}
c.data[key] = dataSlice
}
Expand All @@ -149,7 +152,7 @@ func (c *Map) SetIndex(key string, index int, value string) {
key = strings.ToLower(key)
}
values := c.data[key]
av := keyValue{key: originalKey, value: value}
av := keyValue{key: originalKey, value: value, metadata: &types.DataMetadataList{}}

switch {
case len(values) == 0:
Expand Down Expand Up @@ -219,4 +222,5 @@ func (c *Map) Len() int {
type keyValue struct {
key string
value string
metadata *types.DataMetadataList
}
3 changes: 3 additions & 0 deletions internal/collections/named.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ func (c *NamedCollectionNames) FindRegex(key *regexp.Regexp) []types.MatchData {
Variable_: c.variable,
Key_: d.key,
Value_: d.key,
Metadata_: d.metadata,
})
}
}
Expand All @@ -130,6 +131,7 @@ func (c *NamedCollectionNames) FindString(key string) []types.MatchData {
Variable_: c.variable,
Key_: d.key,
Value_: d.key,
Metadata_: d.metadata,
})
}
}
Expand All @@ -150,6 +152,7 @@ func (c *NamedCollectionNames) FindAll() []types.MatchData {
Variable_: c.variable,
Key_: d.key,
Value_: d.key,
Metadata_: d.metadata,
})
}
}
Expand Down
8 changes: 4 additions & 4 deletions internal/corazarules/rule_match.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type MatchData struct {
// Multiphase specific field
ChainLevel_ int
// Metadata of the matched data
Metadata_ experimentalTypes.DataMetadataList
Metadata_ *experimentalTypes.DataMetadataList
}

var _ types.MatchData = (*MatchData)(nil)
Expand Down Expand Up @@ -64,10 +64,10 @@ func (m MatchData) ChainLevel() int {
return m.ChainLevel_
}

func (m *MatchData) Metadata() experimentalTypes.DataMetadataList {
func (m *MatchData) DataMetadata() experimentalTypes.DataMetadataList {
// Evaluate the metadata if it's not set
m.Metadata_.Evaluate(m.Value_)
return m.Metadata_
m.Metadata_.EvaluateMetadata(m.Value_)
return *m.Metadata_
}

// ActionName is used to identify an action.
Expand Down
4 changes: 2 additions & 2 deletions internal/corazawaf/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ func (r *Rule) doEvaluate(logger debuglog.Logger, phase types.RulePhase, tx *Tra
var errs []error
var argsLen int
for i, arg := range values {
if len(allowedMetadatas) > 0 {
argDataMetadataList := arg.Metadata()
if tx.AllowMetadataInspection && len(allowedMetadatas) > 0 {
argDataMetadataList := arg.DataMetadata()
if !argDataMetadataList.IsInScope(allowedMetadatas) {
vLog.Debug().Msg("Skipping evaluation for " + arg.Key() + " because it is not in scope")
continue
Expand Down
11 changes: 11 additions & 0 deletions internal/corazawaf/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ type Transaction struct {
// it will write to the audit log
audit bool

AllowMetadataInspection bool

variables TransactionVariables

transformationCache map[transformationKey]*transformationValue
Expand Down Expand Up @@ -622,6 +624,15 @@ func (tx *Transaction) GetField(rv ruleVariableParams) []experimentalTypes.Match
return matches
}

func (tx *Transaction) SetMetadataInspection(allow bool) bool {
tx.AllowMetadataInspection = allow
return tx.AllowMetadataInspection
}

func (tx *Transaction) MetadataInspection() bool {
return tx.AllowMetadataInspection
}

// RemoveRuleTargetByID Removes the VARIABLE:KEY from the rule ID
// It's mostly used by CTL to dynamically remove targets from rules
func (tx *Transaction) RemoveRuleTargetByID(id int, variable variables.RuleVariable, key string) {
Expand Down
1 change: 1 addition & 0 deletions internal/corazawaf/waf.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func (w *WAF) newTransaction(opts Options) *Transaction {
tx.RequestBodyLimit = int64(w.RequestBodyLimit)
tx.ResponseBodyAccess = w.ResponseBodyAccess
tx.ResponseBodyLimit = int64(w.ResponseBodyLimit)
tx.AllowMetadataInspection = false
tx.RuleEngine = w.RuleEngine
tx.HashEngine = false
tx.HashEnforcement = false
Expand Down
Loading

0 comments on commit b1bc13f

Please sign in to comment.