corazawaf / coraza

OWASP Coraza WAF is a golang modsecurity compatible web application firewall library
https://www.coraza.io
Apache License 2.0
2.16k stars 212 forks source link

Error (Block-) Logs in JSON Format #1151

Open superstes opened 1 week ago

superstes commented 1 week ago

Summary

I have not seen any option in the documentation to change the error/block log format.

JSON format would make sense for many use-cases. Also the audit-logs seem to already support it.

Basic example

It would be nice to get this as json object: [client \"::ffff:95.214.55.x\"] Coraza: Warning. Host header is a numeric IP address [file \"/etc/coraza-spoa/coreruleset/rules/@owasp_crs/REQUEST-920-PROTOCOL-ENFORCEMENT.conf\"] [line \"1772\"] [id \"920350\"] [rev \"\"] [msg \"Host header is a numeric IP address\"] [data \"159.69.187.x\"] [severity \"warning\"] [ver \"OWASP_CRS/4.0.0-rc2\"] [maturity \"0\"] [accuracy \"0\"] [tag \"application-multi\"] [tag \"language-multi\"] [tag \"platform-multi\"] [tag \"attack-protocol\"] [tag \"paranoia-level/1\"] [tag \"OWASP_CRS\"] [tag \"capec/1000/210/272\"] [tag \"PCI/6.5.10\"] [hostname \"::ffff:159.69.187.x\"] [uri \"/\"] [unique_id \"FMPGEMUVBOHBCEMH\"]

Motivation

JSON is much easier to parse than the stringified format. Log systems like Graylog can parse JSON natively & easily. That is very convenient - especially as such security-logs are very important to process.

From what I've read into the source - this is where the logs are written: https://github.com/corazawaf/coraza/blob/main/internal/corazarules/rule_match.go#L254

Related to: https://github.com/corazawaf/coraza/issues/856, https://github.com/corazawaf/coraza-caddy/issues/20, https://github.com/corazawaf/coraza-spoa/issues/91, https://github.com/corazawaf/coraza/issues/1150

I'm open to contribute.

superstes commented 6 days ago

Of course - one could use WithErrorCallback to override it with a custom format - but that might not be doable or be favourable for 'users'.

superstes commented 6 days ago

Basic callback for reference:

func createWAF() coraza.WAF {
    waf, err := coraza.NewWAF(
        coraza.NewWAFConfig().
            WithErrorCallback(logErrorJSON),
    )
    if err != nil {
        log.Fatal(err)
    }
    return waf
}

type errorLogJSON struct {
    File       string   `json:"file"`
    Line       int      `json:"line"`
    ID         int      `json:"rule_id"`
    Revision   string   `json:"rev"`
    Msg        string   `json:"msg"`
    Data       string   `json:"data"`
    SeverityID int      `json:"sev_id"`
    Severity   string   `json:"sev"`
    Version    string   `json:"ver"`
    Maturity   int      `json:"mat"`
    Accuracy   int      `json:"acc"`
    Client     string   `json:"client"`
    Disruptive bool     `json:"disruptive"`
    Tags       []string `json:"tags"`
    Server     string   `json:"server"`
    URI        string   `json:"uri"`
    UniqueID   string   `json:"unique_id"`
}

func logErrorJSON(mr types.MatchedRule) {
    r := mr.Rule()
    j, _ := json.Marshal(errorLogJSON{
        File:       r.File(),
        Line:       r.Line(),
        ID:         r.ID(),
        Revision:   r.Revision(),
        Msg:        mr.Message(),
        Data:       mr.Data(),
        Severity:   r.Severity().String(),
        SeverityID: r.Severity().Int(),
        Version:    r.Version(),
        Maturity:   r.Maturity(),
        Accuracy:   r.Accuracy(),
        Client:     mr.ClientIPAddress(),
        Server:     mr.ServerIPAddress(),
        Disruptive: mr.Disruptive(),
        Tags:       r.Tags(),
        URI:        mr.URI(),
        UniqueID:   mr.TransactionID(),
    })
    fmt.Println(string(j[:]))
}

Results in: {"file":"coreruleset/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf","line":1210,"rule_id":941160,"rev":"","msg":"NoScript XSS InjectionChecker: HTML Injection","data":"Matched Data: \u003cscript found within REQUEST_HEADERS:Referer: http://localhost:8090/?a=\u003cscript\u003ealert(1)\u003c/script\u003e","sev_id":2,"sev":"critical","ver":"OWASP_CRS/4.7.0-dev","mat":0,"acc":0,"client":"[::1]","disruptive":false,"tags":["application-multi","language-multi","platform-multi","attack-xss","xss-perf-disable","paranoia-level/1","OWASP_CRS","capec/1000/152/242"],"server":"","uri":"/favicon.ico","unique_id":"YuxjbYnedOyrPYNkyFy"}

superstes commented 6 days ago

BTW: Looks like the ocsf-auditlog does something similar - https://github.com/corazawaf/coraza/blob/main/internal/auditlog/formats_ocsf.go#L84