This commit is contained in:
bui 2023-06-06 18:27:56 +02:00
parent ee8b31348b
commit d123254949

View file

@ -2,16 +2,20 @@ package wafacquisition
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings"
"time"
"github.com/corazawaf/coraza/v3" "github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/experimental" "github.com/corazawaf/coraza/v3/experimental"
corazatypes "github.com/corazawaf/coraza/v3/types" corazatypes "github.com/corazawaf/coraza/v3/types"
"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration" "github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
"github.com/crowdsecurity/crowdsec/pkg/types" "github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/google/uuid"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -195,12 +199,12 @@ func (w *WafSource) Dump() interface{} {
return w return w
} }
func processReqWithEngine(waf coraza.WAF, r *http.Request) (*corazatypes.Interruption, error) { func processReqWithEngine(waf coraza.WAF, r *http.Request, uuid string) (*corazatypes.Interruption, corazatypes.Transaction, error) {
tx := waf.NewTransaction() tx := waf.NewTransactionWithID(uuid)
if tx.IsRuleEngineOff() { if tx.IsRuleEngineOff() {
log.Printf("engine is off") log.Printf("engine is off")
return nil, nil return nil, nil, nil
} }
defer func() { defer func() {
@ -239,60 +243,146 @@ func processReqWithEngine(waf coraza.WAF, r *http.Request) (*corazatypes.Interru
in := tx.ProcessRequestHeaders() in := tx.ProcessRequestHeaders()
if in != nil { if in != nil {
log.Printf("headerss") log.Printf("headerss")
return in, nil return in, tx, nil
} }
if tx.IsRequestBodyAccessible() { if tx.IsRequestBodyAccessible() {
if r.Body != nil && r.Body != http.NoBody { if r.Body != nil && r.Body != http.NoBody {
_, _, err := tx.ReadRequestBodyFrom(r.Body) _, _, err := tx.ReadRequestBodyFrom(r.Body)
if err != nil { 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() bodyReader, err := tx.RequestBodyReader()
if err != nil { 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) body := io.MultiReader(bodyReader, r.Body)
r.Body = ioutil.NopCloser(body) r.Body = ioutil.NopCloser(body)
in, err = tx.ProcessRequestBody() in, err = tx.ProcessRequestBody()
if err != nil { 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 { if in != nil {
log.Printf("nothing here") log.Printf("exception while processing body")
return in, nil return in, tx, nil
} }
} }
} }
log.Printf("done") 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) { func (w *WafSource) wafHandler(rw http.ResponseWriter, r *http.Request) {
log.Printf("yolo here %v", r) 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 //inband first
in, err := processReqWithEngine(w.inBandWaf, r) in, tx, err := processReqWithEngine(w.inBandWaf, r, uuid)
if err != nil { //things went south if err != nil { //things went south
log.Errorf("Error while processing request : %s", err) log.Errorf("Error while processing request : %s", err)
rw.WriteHeader(http.StatusForbidden) rw.WriteHeader(http.StatusForbidden)
return return
} }
if in != nil { 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) rw.WriteHeader(http.StatusForbidden)
return return
} }
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
//Now we can do out of band //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 if err != nil { //things went south
log.Errorf("Error while processing request : %s", err) log.Errorf("Error while processing request : %s", err)
return return
} }
if in2 != nil { 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) log.Infof("WAF triggered : %+v", in2)
return return
} }