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

new variables engine for v3 #277

Merged
merged 5 commits into from
Jul 9, 2022
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func main() {
}

// Then we create a transaction and assign some variables
tx := waf.NewTransaction()
tx := waf.NewTransaction(context.Background())
defer func(){
tx.ProcessLogging()
tx.Clean()
Expand Down
2 changes: 1 addition & 1 deletion actions/ctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (a *ctlFn) Evaluate(r *coraza.Rule, tx *coraza.Transaction) {
}
}
case ctlRequestBodyProcessor:
tx.GetCollection(variables.ReqbodyProcessor).Set("", []string{strings.ToUpper(a.value)})
tx.Variables.ReqbodyProcessor.Set(strings.ToUpper(a.value))
case ctlHashEngine:
// Not supported yet
case ctlHashEnforcement:
Expand Down
14 changes: 9 additions & 5 deletions actions/ctl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@
package actions

import (
"context"
"testing"

"github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/types"
"github.com/corazawaf/coraza/v3/types/variables"
)

func TestCtl(t *testing.T) {
waf := coraza.NewWaf()
tx := waf.NewTransaction()
tx := waf.NewTransaction(context.Background())
r := coraza.NewRule()
ctlf := ctl()

Expand Down Expand Up @@ -85,7 +85,7 @@ func TestCtl(t *testing.T) {
t.Errorf("failed to init requestBodyProcessor %s", bp)
}
ctlf.Evaluate(r, tx)
if tx.GetCollection(variables.ReqbodyProcessor).GetFirstString("") != bp {
if tx.Variables.ReqbodyProcessor.String() != bp {
t.Error("failed to set RequestBodyProcessor " + bp)
}
}
Expand All @@ -95,10 +95,14 @@ func TestCtlParseRange(t *testing.T) {
a := &ctlFn{}
rules := []*coraza.Rule{
{
ID: 5,
RuleMetadata: types.RuleMetadata{
ID: 5,
},
},
{
ID: 15,
RuleMetadata: types.RuleMetadata{
ID: 15,
},
},
}
ints, err := a.rangeToInts(rules, "1-2")
Expand Down
3 changes: 1 addition & 2 deletions actions/setenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (

"github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/types"
"github.com/corazawaf/coraza/v3/types/variables"
)

type setenvFn struct {
Expand Down Expand Up @@ -50,7 +49,7 @@ func (a *setenvFn) Evaluate(r *coraza.Rule, tx *coraza.Transaction) {
tx.Waf.Logger.Error("[%s] Error setting env variable for rule %d: %v", tx.ID, r.ID, err)
}
// TODO is this ok?
tx.GetCollection(variables.Env).Set(a.key, []string{v})
tx.Variables.Env.Set(a.key, []string{v})

}

Expand Down
17 changes: 9 additions & 8 deletions actions/setvar.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"strings"

"github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/collection"
"github.com/corazawaf/coraza/v3/types"
"github.com/corazawaf/coraza/v3/types/variables"
)
Expand Down Expand Up @@ -78,25 +79,25 @@ func (a *setvarFn) Type() types.RuleActionType {
}

func (a *setvarFn) evaluateTxCollection(r *coraza.Rule, tx *coraza.Transaction, key string, value string) {
collection := tx.GetCollection(a.collection)
if collection == nil {
col := (tx.Collections[a.collection]).(*collection.CollectionMap)
if col == nil {
// fmt.Println("Invalid Collection " + a.Collection) LOG error?
return
}

if a.isRemove {
collection.Remove(key)
col.Remove(key)
return
}
res := ""
if r := collection.Get(key); len(r) > 0 {
if r := col.Get(key); len(r) > 0 {
res = r[0]
}
var err error
switch {
case len(value) == 0:
// if nothing to input
collection.Set(key, []string{""})
col.Set(key, []string{""})
case value[0] == '+':
// if we want to sum
sum := 0
Expand All @@ -115,16 +116,16 @@ func (a *setvarFn) evaluateTxCollection(r *coraza.Rule, tx *coraza.Transaction,
return
}
}
collection.Set(key, []string{strconv.Itoa(sum + val)})
col.Set(key, []string{strconv.Itoa(sum + val)})
case value[0] == '-':
me, _ := strconv.Atoi(value[1:])
txv, err := strconv.Atoi(res)
if err != nil {
return
}
collection.Set(key, []string{strconv.Itoa(txv - me)})
col.Set(key, []string{strconv.Itoa(txv - me)})
default:
collection.Set(key, []string{value})
col.Set(key, []string{value})
}
}

Expand Down
51 changes: 10 additions & 41 deletions bodyprocessors/bodyprocessors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,10 @@ import (
"io"
"io/fs"

"github.com/corazawaf/coraza/v3/types/variables"
"github.com/corazawaf/coraza/v3/collection"
"github.com/corazawaf/coraza/v3/types"
)

// CollectionsMap is used to store results for collections, example:
// REQUEST_HEADERS:
// cookies: [cookie1: value1, cookie2: value2]
// user-agent: ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"]
type CollectionsMap map[variables.RuleVariable]map[string][]string

// Options are used by BodyProcessors to provide some settings
// like a path to store temporary files.
// Implementations may ignore the options.
Expand All @@ -39,6 +34,8 @@ type Options struct {
StoragePath string
// FileMode is the mode of the file that will be created
FileMode fs.FileMode
// DirMode is the mode of the directory that will be created
DirMode fs.FileMode
}

// BodyProcessor interface is used to create
Expand All @@ -47,54 +44,26 @@ type Options struct {
// Hook to some variable and return data based on special
// expressions like XPATH, JQ, etc.
type BodyProcessor interface {
// Read will process the body and initialize the body processor
// It will return an error if the body is not valid
Read(reader io.Reader, options Options) error
// Collections returns a map of collections, for example,
// the ARGS_POST variables from the REQUEST_BODY.
Collections() CollectionsMap
// Find returns the values in the body based on the input string
// A string might be an xpath, a regex, a variable name, etc
// The find function is responsible of transforming the input
// string into a valid usable expression
Find(string) (map[string][]string, error)
// VariableHook tells the transaction to hook a variable
// to the body processor, it will execute Find
// rather than read it from the collections map
VariableHook() variables.RuleVariable
ProcessRequest(reader io.Reader, collection [types.VariablesCount]collection.Collection, options Options) error
ProcessResponse(reader io.Reader, collection [types.VariablesCount]collection.Collection, options Options) error
}

type bodyProcessorWrapper = func() BodyProcessor

var processors = map[string]bodyProcessorWrapper{}

// RegisterPlugin registers a body processor
// Register registers a body processor
// by name. If the body processor is already registered,
// it will be overwritten
func RegisterPlugin(name string, fn func() BodyProcessor) {
func Register(name string, fn func() BodyProcessor) {
processors[name] = fn
}

// GetBodyProcessor returns a body processor by name
// Get returns a body processor by name
// If the body processor is not found, it returns an error
func GetBodyProcessor(name string) (BodyProcessor, error) {
func Get(name string) (BodyProcessor, error) {
if fn, ok := processors[name]; ok {
return fn(), nil
}
return nil, fmt.Errorf("invalid bodyprocessor %q", name)
}

func init() {
RegisterPlugin("json", func() BodyProcessor {
return &jsonBodyProcessor{}
})
RegisterPlugin("urlencoded", func() BodyProcessor {
return &urlencodedBodyProcessor{}
})
RegisterPlugin("multipart", func() BodyProcessor {
return &multipartBodyProcessor{}
})
RegisterPlugin("xml", func() BodyProcessor {
return &xmlBodyProcessor{}
})
}
66 changes: 36 additions & 30 deletions bodyprocessors/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,39 @@ import (
"io"
"strconv"

"github.com/corazawaf/coraza/v3/collection"
"github.com/corazawaf/coraza/v3/types"
"github.com/corazawaf/coraza/v3/types/variables"
)

type jsonBodyProcessor struct {
collections CollectionsMap
}

func (js *jsonBodyProcessor) Read(reader io.Reader, _ Options) error {
func (js *jsonBodyProcessor) ProcessRequest(reader io.Reader, collections [types.VariablesCount]collection.Collection, _ Options) error {
col := (collections[variables.ArgsPost]).(*collection.CollectionMap)
data, err := readJSON(reader)
if err != nil {
return err
}
for key, value := range data {
col.SetIndex(key, 0, value)
}
return nil
}

func (js *jsonBodyProcessor) ProcessResponse(reader io.Reader, collections [types.VariablesCount]collection.Collection, _ Options) error {
col := (collections[variables.ResponseArgs]).(*collection.CollectionMap)
data, err := readJSON(reader)
if err != nil {
return err
}
for key, value := range data {
col.SetIndex(key, 0, value)
}
return nil
}

func readJSON(reader io.Reader) (map[string]string, error) {
// dump reader to byte array
var data []byte
buf := make([]byte, 1024)
Expand All @@ -37,40 +62,15 @@ func (js *jsonBodyProcessor) Read(reader io.Reader, _ Options) error {
if err == io.EOF {
break
}
return err
return nil, err
}
data = append(data, buf[:n]...)
}
fields, err := jsonToMap(data)
if err != nil {
return err
}
f := map[string][]string{}
names := []string{}
for key, value := range fields {
f[key] = []string{value}
names = append(names, key)
}
js.collections = CollectionsMap{
variables.Args: f,
variables.ArgsPost: f,
variables.ArgsPostNames: map[string][]string{
"": names,
},
return nil, err
}
return nil
}

func (js *jsonBodyProcessor) Collections() CollectionsMap {
return js.collections
}

func (js *jsonBodyProcessor) Find(expr string) (map[string][]string, error) {
return nil, nil
}

func (js *jsonBodyProcessor) VariableHook() variables.RuleVariable {
return variables.JSON
return fields, nil
}

// Transform JSON to a map[string]string
Expand Down Expand Up @@ -188,3 +188,9 @@ func interfaceToMap(data map[string]interface{}) (map[string]string, error) {
var (
_ BodyProcessor = &jsonBodyProcessor{}
)

func init() {
Register("json", func() BodyProcessor {
return &jsonBodyProcessor{}
})
}
Loading