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 1 commit
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
13 changes: 9 additions & 4 deletions actions/ctl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package actions

import (
"context"
"testing"

"github.com/corazawaf/coraza/v3"
Expand All @@ -24,7 +25,7 @@ import (

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 +86,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.GetCollection(variables.ReqbodyProcessor).String() != bp {
t.Error("failed to set RequestBodyProcessor " + bp)
}
}
Expand All @@ -95,10 +96,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
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, collection [types.VariablesCount]collection.Collection, _ Options) error {
col := collection[variables.ArgsPost]
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, collection [types.VariablesCount]collection.Collection, _ Options) error {
col := collection[variables.ResponseArgs]
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{}
})
}
73 changes: 20 additions & 53 deletions bodyprocessors/multipart.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ import (
"os"
"strings"

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

type multipartBodyProcessor struct {
collections *CollectionsMap
}

func (mbp *multipartBodyProcessor) Read(reader io.Reader, options Options) error {
func (_ *multipartBodyProcessor) ProcessRequest(reader io.Reader, collection [types.VariablesCount]collection.Collection, options Options) error {
mimeType := options.Mime
storagePath := options.StoragePath
mediaType, params, err := mime.ParseMediaType(mimeType)
Expand All @@ -42,12 +43,10 @@ func (mbp *multipartBodyProcessor) Read(reader io.Reader, options Options) error
}
mr := multipart.NewReader(reader, params["boundary"])
totalSize := int64(0)
filesNames := []string{}
filesArgNames := []string{}
fileList := []string{}
fileSizes := []string{}
postNames := []string{}
postFields := map[string][]string{}
filesCol := collection[variables.Files]
filesTmpNamesCol := collection[variables.FilesTmpNames]
fileSizesCol := collection[variables.FilesSizes]
postCol := collection[variables.ArgsPost]
for {
p, err := mr.NextPart()
if err == io.EOF {
Expand All @@ -69,63 +68,25 @@ func (mbp *multipartBodyProcessor) Read(reader io.Reader, options Options) error
return err
}
totalSize += sz
filesNames = append(filesNames, filename)
fileList = append(fileList, temp.Name())
fileSizes = append(fileSizes, fmt.Sprintf("%d", sz))
filesArgNames = append(filesArgNames, p.FormName())
filesCol.Add("", filename)
filesTmpNamesCol.Add("", temp.Name())
fileSizesCol.SetIndex(filename, 0, fmt.Sprintf("%d", sz))
} else {
// if is a field
data, err := io.ReadAll(p)
if err != nil {
return err
}
totalSize += int64(len(data))
postNames = append(postNames, p.FormName())
if _, ok := postFields[p.FormName()]; !ok {
postFields[p.FormName()] = []string{}
}
postFields[p.FormName()] = append(postFields[p.FormName()], string(data))

postCol.Add(p.FormName(), string(data))
}
collection[variables.FilesCombinedSize].SetIndex("", 0, fmt.Sprintf("%d", totalSize))
}
pn := map[string][]string{}
for _, value := range postNames {
pn[value] = []string{value}
}
mbp.collections = &CollectionsMap{
variables.FilesNames: map[string][]string{
"": filesArgNames,
},
variables.FilesTmpNames: map[string][]string{
"": fileList,
},
variables.Files: map[string][]string{
"": filesNames,
},
variables.FilesSizes: map[string][]string{
"": fileSizes,
},
variables.ArgsPostNames: pn,
variables.ArgsPost: postFields,
variables.Args: postFields,
variables.FilesCombinedSize: map[string][]string{
"": {fmt.Sprintf("%d", totalSize)},
},
}

return nil
}

func (mbp *multipartBodyProcessor) Collections() CollectionsMap {
return *mbp.collections
}

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

func (mbp *multipartBodyProcessor) VariableHook() variables.RuleVariable {
return variables.JSON
func (_ *multipartBodyProcessor) ProcessResponse(reader io.Reader, collection [types.VariablesCount]collection.Collection, options Options) error {
return nil
}

var (
Expand All @@ -146,3 +107,9 @@ func originFileName(p *multipart.Part) string {

return dispositionParams["filename"]
}

func init() {
Register("multipart", func() BodyProcessor {
return &multipartBodyProcessor{}
})
}
Loading