Add a new datasource that: - Receives HTTP requests from remediation components - Apply rules on them to determine whether they are malicious or not - Rules can be evaluated in-band (the remediation component will block the request directly) or out-band (the RC will let the request through, but crowdsec can still process the rule matches with scenarios) The PR also adds support for 2 new hub items: - appsec-configs: Configure the Application Security Engine (which rules to load, in which phase) - appsec-rules: a rule that is added in the Application Security Engine (can use either our own format, or seclang) --------- Co-authored-by: alteredCoder <kevin@crowdsec.net> Co-authored-by: Sebastien Blot <sebastien@crowdsec.net> Co-authored-by: mmetc <92726601+mmetc@users.noreply.github.com> Co-authored-by: Marco Mariani <marco@crowdsec.net>
145 lines
3.8 KiB
Go
145 lines
3.8 KiB
Go
package appsec
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/crowdsecurity/crowdsec/pkg/appsec/appsec_rule"
|
|
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type AppsecCollection struct {
|
|
collectionName string
|
|
Rules []string
|
|
}
|
|
|
|
var APPSEC_RULE = "appsec-rule"
|
|
|
|
// to be filled w/ seb update
|
|
type AppsecCollectionConfig struct {
|
|
Type string `yaml:"type"`
|
|
Name string `yaml:"name"`
|
|
Debug bool `yaml:"debug"`
|
|
Description string `yaml:"description"`
|
|
SecLangFilesRules []string `yaml:"seclang_files_rules"`
|
|
SecLangRules []string `yaml:"seclang_rules"`
|
|
Rules []appsec_rule.CustomRule `yaml:"rules"`
|
|
|
|
Labels map[string]interface{} `yaml:"labels"` //Labels is K:V list aiming at providing context the overflow
|
|
|
|
Data interface{} `yaml:"data"` //Ignore it
|
|
hash string `yaml:"-"`
|
|
version string `yaml:"-"`
|
|
}
|
|
|
|
type RulesDetails struct {
|
|
LogLevel log.Level
|
|
Hash string
|
|
Version string
|
|
Name string
|
|
}
|
|
|
|
// Should it be a global ?
|
|
// Is using the id is a good idea ? might be too specific to coraza and not easily reusable
|
|
var AppsecRulesDetails = make(map[int]RulesDetails)
|
|
|
|
func LoadCollection(pattern string, logger *log.Entry) ([]AppsecCollection, error) {
|
|
ret := make([]AppsecCollection, 0)
|
|
|
|
for _, appsecRule := range appsecRules {
|
|
|
|
tmpMatch, err := exprhelpers.Match(pattern, appsecRule.Name)
|
|
|
|
if err != nil {
|
|
logger.Errorf("unable to match %s with %s : %s", appsecRule.Name, pattern, err)
|
|
continue
|
|
}
|
|
|
|
matched, ok := tmpMatch.(bool)
|
|
|
|
if !ok {
|
|
logger.Errorf("unable to match %s with %s : %s", appsecRule.Name, pattern, err)
|
|
continue
|
|
}
|
|
|
|
if !matched {
|
|
continue
|
|
}
|
|
|
|
appsecCol := AppsecCollection{
|
|
collectionName: appsecRule.Name,
|
|
}
|
|
|
|
if appsecRule.SecLangFilesRules != nil {
|
|
for _, rulesFile := range appsecRule.SecLangFilesRules {
|
|
logger.Debugf("Adding rules from %s", rulesFile)
|
|
fullPath := filepath.Join(hub.GetDataDir(), rulesFile)
|
|
c, err := os.ReadFile(fullPath)
|
|
if err != nil {
|
|
logger.Errorf("unable to read file %s : %s", rulesFile, err)
|
|
continue
|
|
}
|
|
for _, line := range strings.Split(string(c), "\n") {
|
|
if strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
if strings.TrimSpace(line) == "" {
|
|
continue
|
|
}
|
|
appsecCol.Rules = append(appsecCol.Rules, line)
|
|
}
|
|
}
|
|
}
|
|
|
|
if appsecRule.SecLangRules != nil {
|
|
logger.Tracef("Adding inline rules %+v", appsecRule.SecLangRules)
|
|
appsecCol.Rules = append(appsecCol.Rules, appsecRule.SecLangRules...)
|
|
}
|
|
|
|
if appsecRule.Rules != nil {
|
|
for _, rule := range appsecRule.Rules {
|
|
strRule, rulesId, err := rule.Convert(appsec_rule.ModsecurityRuleType, appsecRule.Name)
|
|
if err != nil {
|
|
logger.Errorf("unable to convert rule %s : %s", rule.Name, err)
|
|
return nil, err
|
|
}
|
|
logger.Debugf("Adding rule %s", strRule)
|
|
appsecCol.Rules = append(appsecCol.Rules, strRule)
|
|
|
|
//We only take the first id, as it's the one of the "main" rule
|
|
if _, ok := AppsecRulesDetails[int(rulesId[0])]; !ok {
|
|
AppsecRulesDetails[int(rulesId[0])] = RulesDetails{
|
|
LogLevel: log.InfoLevel,
|
|
Hash: appsecRule.hash,
|
|
Version: appsecRule.version,
|
|
Name: appsecRule.Name,
|
|
}
|
|
} else {
|
|
logger.Warnf("conflicting id %d for rule %s !", rulesId[0], rule.Name)
|
|
}
|
|
|
|
for _, id := range rulesId {
|
|
SetRuleDebug(int(id), appsecRule.Debug)
|
|
}
|
|
}
|
|
}
|
|
ret = append(ret, appsecCol)
|
|
}
|
|
if len(ret) == 0 {
|
|
return nil, fmt.Errorf("no appsec-rules found for pattern %s", pattern)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (w AppsecCollection) String() string {
|
|
ret := ""
|
|
for _, rule := range w.Rules {
|
|
ret += rule + "\n"
|
|
}
|
|
return ret
|
|
}
|