From b7e2dc2d04e079f2fd3fb6d9226a24fc083c85bb Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Wed, 12 Apr 2023 16:31:14 +0200 Subject: [PATCH] internal/appsec: add server.request.method address This address is now received by the WAF. The recommended ruleset does not use it but custom rules coming we need to support it. Signed-off-by: Eliott Bouhana --- .../dyngo/instrumentation/httpsec/http.go | 3 ++ internal/appsec/testdata/custom_rules.json | 31 +++++++++++++++ internal/appsec/waf.go | 4 ++ internal/appsec/waf_test.go | 39 +++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 internal/appsec/testdata/custom_rules.json diff --git a/internal/appsec/dyngo/instrumentation/httpsec/http.go b/internal/appsec/dyngo/instrumentation/httpsec/http.go index 4b6b8e79cd..1288a173a9 100644 --- a/internal/appsec/dyngo/instrumentation/httpsec/http.go +++ b/internal/appsec/dyngo/instrumentation/httpsec/http.go @@ -33,6 +33,8 @@ import ( type ( // HandlerOperationArgs is the HTTP handler operation arguments. HandlerOperationArgs struct { + // Method is the http method verb of the request, address is `server.request.method` + Method string // RequestURI corresponds to the address `server.request.uri.raw` RequestURI string // Headers corresponds to the address `server.request.headers.no_cookies` @@ -143,6 +145,7 @@ func MakeHandlerOperationArgs(r *http.Request, clientIP netip.Addr, pathParams m cookies := makeCookies(r) // TODO(Julio-Guerra): avoid actively parsing the cookies thanks to dynamic instrumentation headers["host"] = []string{r.Host} return HandlerOperationArgs{ + Method: r.Method, RequestURI: r.RequestURI, Headers: headers, Cookies: cookies, diff --git a/internal/appsec/testdata/custom_rules.json b/internal/appsec/testdata/custom_rules.json new file mode 100644 index 0000000000..2fb5cbda5d --- /dev/null +++ b/internal/appsec/testdata/custom_rules.json @@ -0,0 +1,31 @@ +{ + "version": "2.2", + "metadata": { + "rules_version": "1.4.2" + }, + "rules": [ + { + "id": "custom-001", + "name": "Custom Rule", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.method" + } + ], + "regex": "^POST$" + }, + "operator": "match_regex" + } + ], + "transformers": [] + } + ] +} diff --git a/internal/appsec/waf.go b/internal/appsec/waf.go index a9ad0f588d..cd08e93c5e 100644 --- a/internal/appsec/waf.go +++ b/internal/appsec/waf.go @@ -170,6 +170,8 @@ func newHTTPWAFEventListener(handle *waf.Handle, addresses []string, timeout tim values := make(map[string]interface{}, len(addresses)) for _, addr := range addresses { switch addr { + case serverRequestMethodAddr: + values[serverRequestMethodAddr] = args.Method case serverRequestRawURIAddr: values[serverRequestRawURIAddr] = args.RequestURI case serverRequestHeadersNoCookiesAddr: @@ -378,6 +380,7 @@ func runWAF(wafCtx *waf.Context, values map[string]interface{}, timeout time.Dur // HTTP rule addresses currently supported by the WAF const ( + serverRequestMethodAddr = "server.request.method" serverRequestRawURIAddr = "server.request.uri.raw" serverRequestHeadersNoCookiesAddr = "server.request.headers.no_cookies" serverRequestCookiesAddr = "server.request.cookies" @@ -391,6 +394,7 @@ const ( // List of HTTP rule addresses currently supported by the WAF var httpAddresses = []string{ + serverRequestMethodAddr, serverRequestRawURIAddr, serverRequestHeadersNoCookiesAddr, serverRequestCookiesAddr, diff --git a/internal/appsec/waf_test.go b/internal/appsec/waf_test.go index fec85a5359..67fe727f3b 100644 --- a/internal/appsec/waf_test.go +++ b/internal/appsec/waf_test.go @@ -24,6 +24,45 @@ import ( "github.com/stretchr/testify/require" ) +func TestCustomRules(t *testing.T) { + t.Setenv("DD_APPSEC_RULES", "testdata/custom_rules.json") + appsec.Start() + defer appsec.Stop() + + if !appsec.Enabled() { + t.Skip("appsec disabled") + } + + // Start and trace an HTTP server + mux := httptrace.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello World!\n")) + }) + + srv := httptest.NewServer(mux) + defer srv.Close() + + t.Run("method", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL, nil) + if err != nil { + panic(err) + } + + _, err = srv.Client().Do(req) + require.NoError(t, err) + + spans := mt.FinishedSpans() + require.Len(t, spans, 1) + + event := spans[0].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.Contains(t, event, "custom-001") + }) +} + // TestWAF is a simple validation test of the WAF protecting a net/http server. It only mockups the agent and tests that // the WAF is properly detecting an LFI attempt and that the corresponding security event is being sent to the agent. // Additionally, verifies that rule matching through SDK body instrumentation works as expected