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 (
"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
}