123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- package appsecacquisition
- import (
- "encoding/json"
- "fmt"
- "time"
- "github.com/crowdsecurity/coraza/v3/collection"
- "github.com/crowdsecurity/coraza/v3/types/variables"
- "github.com/crowdsecurity/crowdsec/pkg/appsec"
- "github.com/crowdsecurity/crowdsec/pkg/models"
- "github.com/crowdsecurity/crowdsec/pkg/types"
- "github.com/crowdsecurity/go-cs-lib/ptr"
- "github.com/prometheus/client_golang/prometheus"
- log "github.com/sirupsen/logrus"
- )
- func AppsecEventGeneration(inEvt types.Event) (*types.Event, error) {
- //if the request didnd't trigger inband rules, we don't want to generate an event to LAPI/CAPI
- if !inEvt.Appsec.HasInBandMatches {
- return nil, nil
- }
- evt := types.Event{}
- evt.Type = types.APPSEC
- evt.Process = true
- source := models.Source{
- Value: ptr.Of(inEvt.Parsed["source_ip"]),
- IP: inEvt.Parsed["source_ip"],
- Scope: ptr.Of(types.Ip),
- }
- evt.Overflow.Sources = make(map[string]models.Source)
- evt.Overflow.Sources["ip"] = source
- alert := models.Alert{}
- alert.Capacity = ptr.Of(int32(1))
- alert.Events = make([]*models.Event, 0)
- alert.Meta = make(models.Meta, 0)
- for _, key := range []string{"target_uri", "method"} {
- valueByte, err := json.Marshal([]string{inEvt.Parsed[key]})
- if err != nil {
- log.Debugf("unable to serialize key %s", key)
- continue
- }
- meta := models.MetaItems0{
- Key: key,
- Value: string(valueByte),
- }
- alert.Meta = append(alert.Meta, &meta)
- }
- matchedZones := inEvt.Appsec.GetMatchedZones()
- if matchedZones != nil {
- valueByte, err := json.Marshal(matchedZones)
- if err != nil {
- log.Debugf("unable to serialize key matched_zones")
- } else {
- meta := models.MetaItems0{
- Key: "matched_zones",
- Value: string(valueByte),
- }
- alert.Meta = append(alert.Meta, &meta)
- }
- }
- alert.EventsCount = ptr.Of(int32(1))
- alert.Leakspeed = ptr.Of("")
- alert.Scenario = ptr.Of(inEvt.Appsec.MatchedRules.GetName())
- alert.ScenarioHash = ptr.Of(inEvt.Appsec.MatchedRules.GetHash())
- alert.ScenarioVersion = ptr.Of(inEvt.Appsec.MatchedRules.GetVersion())
- alert.Simulated = ptr.Of(false)
- alert.Source = &source
- msg := fmt.Sprintf("AppSec block: %s from %s (%s)", inEvt.Appsec.MatchedRules.GetName(),
- alert.Source.IP, inEvt.Parsed["remediation_cmpt_ip"])
- alert.Message = &msg
- alert.StartAt = ptr.Of(time.Now().UTC().Format(time.RFC3339))
- alert.StopAt = ptr.Of(time.Now().UTC().Format(time.RFC3339))
- evt.Overflow.APIAlerts = []models.Alert{alert}
- evt.Overflow.Alert = &alert
- return &evt, nil
- }
- func EventFromRequest(r *appsec.ParsedRequest, labels map[string]string) (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.Parsed = map[string]string{
- "source_ip": r.ClientIP,
- "target_host": r.Host,
- "target_uri": r.URI,
- "method": r.Method,
- "req_uuid": r.Tx.ID(),
- "source": "crowdsec-appsec",
- "remediation_cmpt_ip": r.RemoteAddrNormalized,
- //TBD:
- //http_status
- //user_agent
- }
- evt.Line = types.Line{
- Time: time.Now(),
- //should we add some info like listen addr/port/path ?
- Labels: labels,
- Process: true,
- Module: "appsec",
- Src: "appsec",
- Raw: "dummy-appsec-data", //we discard empty Line.Raw items :)
- }
- evt.Appsec = types.AppsecEvent{}
- return evt, nil
- }
- func LogAppsecEvent(evt *types.Event, logger *log.Entry) {
- req := evt.Parsed["target_uri"]
- if len(req) > 12 {
- req = req[:10] + ".."
- }
- if evt.Meta["appsec_interrupted"] == "true" {
- logger.WithFields(log.Fields{
- "module": "appsec",
- "source": evt.Parsed["source_ip"],
- "target_uri": req,
- }).Infof("%s blocked on %s (%d rules) [%v]", evt.Parsed["source_ip"], req, len(evt.Appsec.MatchedRules), evt.Appsec.GetRuleIDs())
- } else if evt.Parsed["outofband_interrupted"] == "true" {
- logger.WithFields(log.Fields{
- "module": "appsec",
- "source": evt.Parsed["source_ip"],
- "target_uri": req,
- }).Infof("%s out-of-band blocking rules on %s (%d rules) [%v]", evt.Parsed["source_ip"], req, len(evt.Appsec.MatchedRules), evt.Appsec.GetRuleIDs())
- } else {
- logger.WithFields(log.Fields{
- "module": "appsec",
- "source": evt.Parsed["source_ip"],
- "target_uri": req,
- }).Debugf("%s triggered non-blocking rules on %s (%d rules) [%v]", evt.Parsed["source_ip"], req, len(evt.Appsec.MatchedRules), evt.Appsec.GetRuleIDs())
- }
- }
- func (r *AppsecRunner) AccumulateTxToEvent(evt *types.Event, req *appsec.ParsedRequest) error {
- if evt == nil {
- //an error was already emitted, let's not spam the logs
- return nil
- }
- if !req.Tx.IsInterrupted() {
- //if the phase didn't generate an interruption, we don't have anything to add to the event
- return nil
- }
- //if one interruption was generated, event is good for processing :)
- evt.Process = true
- if evt.Meta == nil {
- evt.Meta = map[string]string{}
- }
- if evt.Parsed == nil {
- evt.Parsed = map[string]string{}
- }
- if req.IsInBand {
- evt.Meta["appsec_interrupted"] = "true"
- evt.Meta["appsec_action"] = req.Tx.Interruption().Action
- evt.Parsed["inband_interrupted"] = "true"
- evt.Parsed["inband_action"] = req.Tx.Interruption().Action
- } else {
- evt.Parsed["outofband_interrupted"] = "true"
- evt.Parsed["outofband_action"] = req.Tx.Interruption().Action
- }
- if evt.Appsec.Vars == nil {
- evt.Appsec.Vars = map[string]string{}
- }
- req.Tx.Variables().All(func(v variables.RuleVariable, col collection.Collection) bool {
- for _, variable := range col.FindAll() {
- key := variable.Variable().Name()
- if variable.Key() != "" {
- key += "." + variable.Key()
- }
- if variable.Value() == "" {
- continue
- }
- for _, collectionToKeep := range r.AppsecRuntime.CompiledVariablesTracking {
- match := collectionToKeep.MatchString(key)
- if match {
- evt.Appsec.Vars[key] = variable.Value()
- r.logger.Debugf("%s.%s = %s", variable.Variable().Name(), variable.Key(), variable.Value())
- } else {
- r.logger.Debugf("%s.%s != %s (%s) (not kept)", variable.Variable().Name(), variable.Key(), collectionToKeep, variable.Value())
- }
- }
- }
- return true
- })
- for _, rule := range req.Tx.MatchedRules() {
- if rule.Message() == "" || rule.DisruptiveAction() == "pass" || rule.DisruptiveAction() == "allow" {
- r.logger.Tracef("discarding rule %d (action: %s)", rule.Rule().ID(), rule.DisruptiveAction())
- continue
- }
- kind := "outofband"
- if req.IsInBand {
- kind = "inband"
- evt.Appsec.HasInBandMatches = true
- } else {
- evt.Appsec.HasOutBandMatches = true
- }
- var name string
- version := ""
- hash := ""
- ruleNameProm := fmt.Sprintf("%d", rule.Rule().ID())
- if details, ok := appsec.AppsecRulesDetails[rule.Rule().ID()]; ok {
- //Only set them for custom rules, not for rules written in seclang
- name = details.Name
- version = details.Version
- hash = details.Hash
- ruleNameProm = details.Name
- r.logger.Debugf("custom rule for event, setting name: %s, version: %s, hash: %s", name, version, hash)
- } else {
- name = fmt.Sprintf("native_rule:%d", rule.Rule().ID())
- }
- AppsecRuleHits.With(prometheus.Labels{"rule_name": ruleNameProm, "type": kind, "source": req.RemoteAddrNormalized, "appsec_engine": req.AppsecEngine}).Inc()
- matchedZones := make([]string, 0)
- for _, matchData := range rule.MatchedDatas() {
- zone := matchData.Variable().Name()
- varName := matchData.Key()
- if varName != "" {
- zone += "." + varName
- }
- matchedZones = append(matchedZones, zone)
- }
- corazaRule := map[string]interface{}{
- "id": rule.Rule().ID(),
- "uri": evt.Parsed["uri"],
- "rule_type": kind,
- "method": evt.Parsed["method"],
- "disruptive": rule.Disruptive(),
- "tags": rule.Rule().Tags(),
- "file": rule.Rule().File(),
- "file_line": rule.Rule().Line(),
- "revision": rule.Rule().Revision(),
- "secmark": rule.Rule().SecMark(),
- "accuracy": rule.Rule().Accuracy(),
- "msg": rule.Message(),
- "severity": rule.Rule().Severity().String(),
- "name": name,
- "hash": hash,
- "version": version,
- "matched_zones": matchedZones,
- }
- evt.Appsec.MatchedRules = append(evt.Appsec.MatchedRules, corazaRule)
- }
- return nil
- }
|