github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/appsec/utils.go (about) 1 package appsecacquisition 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "time" 7 8 "github.com/crowdsecurity/coraza/v3/collection" 9 "github.com/crowdsecurity/coraza/v3/types/variables" 10 "github.com/crowdsecurity/crowdsec/pkg/appsec" 11 "github.com/crowdsecurity/crowdsec/pkg/models" 12 "github.com/crowdsecurity/crowdsec/pkg/types" 13 "github.com/crowdsecurity/go-cs-lib/ptr" 14 "github.com/prometheus/client_golang/prometheus" 15 log "github.com/sirupsen/logrus" 16 ) 17 18 func AppsecEventGeneration(inEvt types.Event) (*types.Event, error) { 19 //if the request didnd't trigger inband rules, we don't want to generate an event to LAPI/CAPI 20 if !inEvt.Appsec.HasInBandMatches { 21 return nil, nil 22 } 23 evt := types.Event{} 24 evt.Type = types.APPSEC 25 evt.Process = true 26 source := models.Source{ 27 Value: ptr.Of(inEvt.Parsed["source_ip"]), 28 IP: inEvt.Parsed["source_ip"], 29 Scope: ptr.Of(types.Ip), 30 } 31 32 evt.Overflow.Sources = make(map[string]models.Source) 33 evt.Overflow.Sources["ip"] = source 34 35 alert := models.Alert{} 36 alert.Capacity = ptr.Of(int32(1)) 37 alert.Events = make([]*models.Event, 0) 38 alert.Meta = make(models.Meta, 0) 39 for _, key := range []string{"target_uri", "method"} { 40 41 valueByte, err := json.Marshal([]string{inEvt.Parsed[key]}) 42 if err != nil { 43 log.Debugf("unable to serialize key %s", key) 44 continue 45 } 46 47 meta := models.MetaItems0{ 48 Key: key, 49 Value: string(valueByte), 50 } 51 alert.Meta = append(alert.Meta, &meta) 52 } 53 matchedZones := inEvt.Appsec.GetMatchedZones() 54 if matchedZones != nil { 55 valueByte, err := json.Marshal(matchedZones) 56 if err != nil { 57 log.Debugf("unable to serialize key matched_zones") 58 } else { 59 meta := models.MetaItems0{ 60 Key: "matched_zones", 61 Value: string(valueByte), 62 } 63 alert.Meta = append(alert.Meta, &meta) 64 } 65 } 66 67 alert.EventsCount = ptr.Of(int32(1)) 68 alert.Leakspeed = ptr.Of("") 69 alert.Scenario = ptr.Of(inEvt.Appsec.MatchedRules.GetName()) 70 alert.ScenarioHash = ptr.Of(inEvt.Appsec.MatchedRules.GetHash()) 71 alert.ScenarioVersion = ptr.Of(inEvt.Appsec.MatchedRules.GetVersion()) 72 alert.Simulated = ptr.Of(false) 73 alert.Source = &source 74 msg := fmt.Sprintf("AppSec block: %s from %s (%s)", inEvt.Appsec.MatchedRules.GetName(), 75 alert.Source.IP, inEvt.Parsed["remediation_cmpt_ip"]) 76 alert.Message = &msg 77 alert.StartAt = ptr.Of(time.Now().UTC().Format(time.RFC3339)) 78 alert.StopAt = ptr.Of(time.Now().UTC().Format(time.RFC3339)) 79 evt.Overflow.APIAlerts = []models.Alert{alert} 80 evt.Overflow.Alert = &alert 81 return &evt, nil 82 } 83 84 func EventFromRequest(r *appsec.ParsedRequest, labels map[string]string) (types.Event, error) { 85 evt := types.Event{} 86 //we might want to change this based on in-band vs out-of-band ? 87 evt.Type = types.LOG 88 evt.ExpectMode = types.LIVE 89 //def needs fixing 90 evt.Stage = "s00-raw" 91 evt.Parsed = map[string]string{ 92 "source_ip": r.ClientIP, 93 "target_host": r.Host, 94 "target_uri": r.URI, 95 "method": r.Method, 96 "req_uuid": r.Tx.ID(), 97 "source": "crowdsec-appsec", 98 "remediation_cmpt_ip": r.RemoteAddrNormalized, 99 //TBD: 100 //http_status 101 //user_agent 102 103 } 104 evt.Line = types.Line{ 105 Time: time.Now(), 106 //should we add some info like listen addr/port/path ? 107 Labels: labels, 108 Process: true, 109 Module: "appsec", 110 Src: "appsec", 111 Raw: "dummy-appsec-data", //we discard empty Line.Raw items :) 112 } 113 evt.Appsec = types.AppsecEvent{} 114 115 return evt, nil 116 } 117 118 func LogAppsecEvent(evt *types.Event, logger *log.Entry) { 119 req := evt.Parsed["target_uri"] 120 if len(req) > 12 { 121 req = req[:10] + ".." 122 } 123 124 if evt.Meta["appsec_interrupted"] == "true" { 125 logger.WithFields(log.Fields{ 126 "module": "appsec", 127 "source": evt.Parsed["source_ip"], 128 "target_uri": req, 129 }).Infof("%s blocked on %s (%d rules) [%v]", evt.Parsed["source_ip"], req, len(evt.Appsec.MatchedRules), evt.Appsec.GetRuleIDs()) 130 } else if evt.Parsed["outofband_interrupted"] == "true" { 131 logger.WithFields(log.Fields{ 132 "module": "appsec", 133 "source": evt.Parsed["source_ip"], 134 "target_uri": req, 135 }).Infof("%s out-of-band blocking rules on %s (%d rules) [%v]", evt.Parsed["source_ip"], req, len(evt.Appsec.MatchedRules), evt.Appsec.GetRuleIDs()) 136 } else { 137 logger.WithFields(log.Fields{ 138 "module": "appsec", 139 "source": evt.Parsed["source_ip"], 140 "target_uri": req, 141 }).Debugf("%s triggered non-blocking rules on %s (%d rules) [%v]", evt.Parsed["source_ip"], req, len(evt.Appsec.MatchedRules), evt.Appsec.GetRuleIDs()) 142 } 143 144 } 145 146 func (r *AppsecRunner) AccumulateTxToEvent(evt *types.Event, req *appsec.ParsedRequest) error { 147 148 if evt == nil { 149 //an error was already emitted, let's not spam the logs 150 return nil 151 } 152 153 if !req.Tx.IsInterrupted() { 154 //if the phase didn't generate an interruption, we don't have anything to add to the event 155 return nil 156 } 157 //if one interruption was generated, event is good for processing :) 158 evt.Process = true 159 160 if evt.Meta == nil { 161 evt.Meta = map[string]string{} 162 } 163 if evt.Parsed == nil { 164 evt.Parsed = map[string]string{} 165 } 166 if req.IsInBand { 167 evt.Meta["appsec_interrupted"] = "true" 168 evt.Meta["appsec_action"] = req.Tx.Interruption().Action 169 evt.Parsed["inband_interrupted"] = "true" 170 evt.Parsed["inband_action"] = req.Tx.Interruption().Action 171 } else { 172 evt.Parsed["outofband_interrupted"] = "true" 173 evt.Parsed["outofband_action"] = req.Tx.Interruption().Action 174 } 175 176 if evt.Appsec.Vars == nil { 177 evt.Appsec.Vars = map[string]string{} 178 } 179 180 req.Tx.Variables().All(func(v variables.RuleVariable, col collection.Collection) bool { 181 for _, variable := range col.FindAll() { 182 key := variable.Variable().Name() 183 if variable.Key() != "" { 184 key += "." + variable.Key() 185 } 186 if variable.Value() == "" { 187 continue 188 } 189 for _, collectionToKeep := range r.AppsecRuntime.CompiledVariablesTracking { 190 match := collectionToKeep.MatchString(key) 191 if match { 192 evt.Appsec.Vars[key] = variable.Value() 193 r.logger.Debugf("%s.%s = %s", variable.Variable().Name(), variable.Key(), variable.Value()) 194 } else { 195 r.logger.Debugf("%s.%s != %s (%s) (not kept)", variable.Variable().Name(), variable.Key(), collectionToKeep, variable.Value()) 196 } 197 } 198 } 199 return true 200 }) 201 202 for _, rule := range req.Tx.MatchedRules() { 203 if rule.Message() == "" || rule.DisruptiveAction() == "pass" || rule.DisruptiveAction() == "allow" { 204 r.logger.Tracef("discarding rule %d (action: %s)", rule.Rule().ID(), rule.DisruptiveAction()) 205 continue 206 } 207 kind := "outofband" 208 if req.IsInBand { 209 kind = "inband" 210 evt.Appsec.HasInBandMatches = true 211 } else { 212 evt.Appsec.HasOutBandMatches = true 213 } 214 215 var name string 216 version := "" 217 hash := "" 218 ruleNameProm := fmt.Sprintf("%d", rule.Rule().ID()) 219 220 if details, ok := appsec.AppsecRulesDetails[rule.Rule().ID()]; ok { 221 //Only set them for custom rules, not for rules written in seclang 222 name = details.Name 223 version = details.Version 224 hash = details.Hash 225 ruleNameProm = details.Name 226 r.logger.Debugf("custom rule for event, setting name: %s, version: %s, hash: %s", name, version, hash) 227 } else { 228 name = fmt.Sprintf("native_rule:%d", rule.Rule().ID()) 229 } 230 231 AppsecRuleHits.With(prometheus.Labels{"rule_name": ruleNameProm, "type": kind, "source": req.RemoteAddrNormalized, "appsec_engine": req.AppsecEngine}).Inc() 232 233 matchedZones := make([]string, 0) 234 for _, matchData := range rule.MatchedDatas() { 235 zone := matchData.Variable().Name() 236 varName := matchData.Key() 237 if varName != "" { 238 zone += "." + varName 239 } 240 matchedZones = append(matchedZones, zone) 241 } 242 243 corazaRule := map[string]interface{}{ 244 "id": rule.Rule().ID(), 245 "uri": evt.Parsed["uri"], 246 "rule_type": kind, 247 "method": evt.Parsed["method"], 248 "disruptive": rule.Disruptive(), 249 "tags": rule.Rule().Tags(), 250 "file": rule.Rule().File(), 251 "file_line": rule.Rule().Line(), 252 "revision": rule.Rule().Revision(), 253 "secmark": rule.Rule().SecMark(), 254 "accuracy": rule.Rule().Accuracy(), 255 "msg": rule.Message(), 256 "severity": rule.Rule().Severity().String(), 257 "name": name, 258 "hash": hash, 259 "version": version, 260 "matched_zones": matchedZones, 261 } 262 evt.Appsec.MatchedRules = append(evt.Appsec.MatchedRules, corazaRule) 263 } 264 265 return nil 266 267 }