From d12325494986185c808136133b01926d35c0d838 Mon Sep 17 00:00:00 2001 From: bui Date: Tue, 6 Jun 2023 18:27:56 +0200 Subject: [PATCH] wip --- pkg/acquisition/modules/waf/waf.go | 116 +++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 13 deletions(-) diff --git a/pkg/acquisition/modules/waf/waf.go b/pkg/acquisition/modules/waf/waf.go index f743767af..013d3ee54 100644 --- a/pkg/acquisition/modules/waf/waf.go +++ b/pkg/acquisition/modules/waf/waf.go @@ -2,16 +2,20 @@ package wafacquisition import ( "context" + "encoding/json" "fmt" "io" "io/ioutil" "net/http" + "strings" + "time" "github.com/corazawaf/coraza/v3" "github.com/corazawaf/coraza/v3/experimental" corazatypes "github.com/corazawaf/coraza/v3/types" "github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration" "github.com/crowdsecurity/crowdsec/pkg/types" + "github.com/google/uuid" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" @@ -195,12 +199,12 @@ func (w *WafSource) Dump() interface{} { return w } -func processReqWithEngine(waf coraza.WAF, r *http.Request) (*corazatypes.Interruption, error) { - tx := waf.NewTransaction() +func processReqWithEngine(waf coraza.WAF, r *http.Request, uuid string) (*corazatypes.Interruption, corazatypes.Transaction, error) { + tx := waf.NewTransactionWithID(uuid) if tx.IsRuleEngineOff() { log.Printf("engine is off") - return nil, nil + return nil, nil, nil } defer func() { @@ -239,60 +243,146 @@ func processReqWithEngine(waf coraza.WAF, r *http.Request) (*corazatypes.Interru in := tx.ProcessRequestHeaders() if in != nil { log.Printf("headerss") - return in, nil + return in, tx, nil } if tx.IsRequestBodyAccessible() { if r.Body != nil && r.Body != http.NoBody { _, _, err := tx.ReadRequestBodyFrom(r.Body) if err != nil { - return nil, errors.Wrap(err, "Cannot read request body") + return nil, nil, errors.Wrap(err, "Cannot read request body") } bodyReader, err := tx.RequestBodyReader() if err != nil { - return nil, errors.Wrap(err, "Cannot read request body") + return nil, nil, errors.Wrap(err, "Cannot read request body") } body := io.MultiReader(bodyReader, r.Body) r.Body = ioutil.NopCloser(body) in, err = tx.ProcessRequestBody() if err != nil { - return nil, errors.Wrap(err, "Cannot process request body") + return nil, nil, errors.Wrap(err, "Cannot process request body") } if in != nil { - log.Printf("nothing here") - return in, nil + log.Printf("exception while processing body") + return in, tx, nil } } } log.Printf("done") - return nil, nil + return nil, nil, nil +} + +func (w *WafSource) TxToEvents(tx corazatypes.Transaction, r *http.Request) ([]types.Event, error) { + evts := []types.Event{} + if tx == nil { + return nil, fmt.Errorf("tx is nil") + } + for idx, rule := range tx.MatchedRules() { + log.Printf("rule %d", idx) + evt, err := w.RuleMatchToEvent(rule, tx, r) + if err != nil { + return nil, errors.Wrap(err, "Cannot convert rule match to event") + } + evts = append(evts, evt) + } + + return evts, nil +} + +// Transforms a coraza interruption to a crowdsec event +func (w *WafSource) RuleMatchToEvent(rule corazatypes.MatchedRule, tx corazatypes.Transaction, r *http.Request) (types.Event, error) { + evt := types.Event{} + //we might want to change this based on in-band vs out-of-band ? + evt.Type = types.LOG + evt.ExpectMode = types.LIVE + //def needs fixing + evt.Stage = "s00-raw" + evt.Process = true + + //we build a big-ass object that is going to be marshaled in line.raw and unmarshaled later. + //why ? because it's more consistent with the other data-sources etc. and it provides users with flexibility to alter our parsers + CorazaEvent := map[string]interface{}{ + //core rule info + "rule_id": rule.Rule().ID(), + "rule_action": tx.Interruption().Action, + "rule_disruptive": rule.Disruptive(), + "rule_tags": rule.Rule().Tags(), + "rule_file": rule.Rule().File(), + "rule_file_line": rule.Rule().Line(), + "rule_revision": rule.Rule().Revision(), + "rule_secmark": rule.Rule().SecMark(), + "rule_accuracy": rule.Rule().Accuracy(), + + //http contextual infos + "upstream_addr": r.RemoteAddr, + "req_uuid": tx.ID(), + "source_ip": strings.Split(rule.ClientIPAddress(), ":")[0], + "uri": rule.URI(), + } + + corazaEventB, err := json.Marshal(CorazaEvent) + if err != nil { + return evt, fmt.Errorf("Unable to marshal coraza alert: %w", err) + } + evt.Line = types.Line{ + Time: time.Now(), + //should we add some info like listen addr/port/path ? + Labels: map[string]string{"type": "waf"}, + Process: true, + Module: "waf", + Src: "waf", + Raw: string(corazaEventB), + } + + return evt, nil } func (w *WafSource) wafHandler(rw http.ResponseWriter, r *http.Request) { log.Printf("yolo here %v", r) + //let's gen a transaction id to keep consistance accross in-band and out-of-band + uuid := uuid.New().String() //inband first - in, err := processReqWithEngine(w.inBandWaf, r) + in, tx, err := processReqWithEngine(w.inBandWaf, r, uuid) if err != nil { //things went south log.Errorf("Error while processing request : %s", err) rw.WriteHeader(http.StatusForbidden) return } if in != nil { - log.Infof("Request blocked by WAF : %+v", in) + events, err := w.TxToEvents(tx, r) + log.Infof("Request blocked by WAF, %d events to send", len(events)) + for _, evt := range events { + w.outChan <- evt + } + log.Infof("done") + if err != nil { + log.Errorf("Cannot convert transaction to events : %s", err) + rw.WriteHeader(http.StatusForbidden) + return + } rw.WriteHeader(http.StatusForbidden) return } rw.WriteHeader(http.StatusOK) //Now we can do out of band - in2, err := processReqWithEngine(w.outOfBandWaf, r) + in2, tx2, err := processReqWithEngine(w.outOfBandWaf, r, uuid) if err != nil { //things went south log.Errorf("Error while processing request : %s", err) return } if in2 != nil { + events, err := w.TxToEvents(tx2, r) + log.Infof("Request triggered by WAF, %d events to send", len(events)) + for _, evt := range events { + w.outChan <- evt + } + if err != nil { + log.Errorf("Cannot convert transaction to events : %s", err) + } + log.Infof("done") log.Infof("WAF triggered : %+v", in2) return }