diff --git a/go.mod b/go.mod index f1f0f21e44..bff556221b 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 github.com/DataDog/datadog-go/v5 v5.3.0 - github.com/DataDog/go-libddwaf/v3 v3.2.1 + github.com/DataDog/go-libddwaf/v3 v3.3.0 github.com/DataDog/gostackparse v0.7.0 github.com/DataDog/sketches-go v1.4.5 github.com/IBM/sarama v1.40.0 diff --git a/go.sum b/go.sum index 9bee13d939..763337876f 100644 --- a/go.sum +++ b/go.sum @@ -634,8 +634,8 @@ github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8= github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q= -github.com/DataDog/go-libddwaf/v3 v3.2.1 h1:lZPc6UxCOwioHc++nsldKR50FpIrRh1uGnGLuryqnE8= -github.com/DataDog/go-libddwaf/v3 v3.2.1/go.mod h1:AP+7Atb8ftSsrha35wht7+K3R+xuzfVSQhabSO4w6CY= +github.com/DataDog/go-libddwaf/v3 v3.3.0 h1:jS72fuQpFgJZEdEJDmHJCPAgNTEMZoz1EUvimPUOiJ4= +github.com/DataDog/go-libddwaf/v3 v3.3.0/go.mod h1:Bz/0JkpGf689mzbUjKJeheJINqsyyhM8p9PDuHdK2Ec= github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= diff --git a/internal/apps/go.mod b/internal/apps/go.mod index d633399144..65b49b91f3 100644 --- a/internal/apps/go.mod +++ b/internal/apps/go.mod @@ -9,7 +9,7 @@ require ( require ( github.com/DataDog/appsec-internal-go v1.6.0 // indirect - github.com/DataDog/go-libddwaf/v3 v3.2.1 // indirect + github.com/DataDog/go-libddwaf/v3 v3.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 // indirect github.com/ebitengine/purego v0.6.0-alpha.5 // indirect diff --git a/internal/apps/go.sum b/internal/apps/go.sum index 4156bb8e3d..635603866a 100644 --- a/internal/apps/go.sum +++ b/internal/apps/go.sum @@ -6,8 +6,8 @@ github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3 github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ= github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8= github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q= -github.com/DataDog/go-libddwaf/v3 v3.2.1 h1:lZPc6UxCOwioHc++nsldKR50FpIrRh1uGnGLuryqnE8= -github.com/DataDog/go-libddwaf/v3 v3.2.1/go.mod h1:AP+7Atb8ftSsrha35wht7+K3R+xuzfVSQhabSO4w6CY= +github.com/DataDog/go-libddwaf/v3 v3.3.0 h1:jS72fuQpFgJZEdEJDmHJCPAgNTEMZoz1EUvimPUOiJ4= +github.com/DataDog/go-libddwaf/v3 v3.3.0/go.mod h1:Bz/0JkpGf689mzbUjKJeheJINqsyyhM8p9PDuHdK2Ec= github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= diff --git a/internal/appsec/emitter/ossec/lfi.go b/internal/appsec/emitter/ossec/lfi.go index 8025bcf14b..91707446f4 100644 --- a/internal/appsec/emitter/ossec/lfi.go +++ b/internal/appsec/emitter/ossec/lfi.go @@ -18,10 +18,11 @@ import ( var badInputContextOnce sync.Once -const readMask = os.O_RDONLY | os.O_RDWR - func ProtectOpen(ctx context.Context, path string, flags int) error { - if flags&readMask == 0 { // We only care about read operations + // We only care about read operations. We don't want to scan for write operations + // os.O_RDONLY is not a flag it's simply 0 so we can't do a simple bit mask + // If os.O_CREATE is set, we also want to monitor the operation because the file must not exist + if (flags&os.O_RDWR == 0 && flags != os.O_RDONLY) || flags&os.O_CREATE != 0 { return nil } diff --git a/internal/appsec/listener/grpcsec/grpc.go b/internal/appsec/listener/grpcsec/grpc.go index 55f70be170..b2cc68858b 100644 --- a/internal/appsec/listener/grpcsec/grpc.go +++ b/internal/appsec/listener/grpcsec/grpc.go @@ -39,6 +39,9 @@ var supportedAddresses = listener.AddressSet{ GRPCServerMethodAddr: {}, GRPCServerRequestMessageAddr: {}, GRPCServerRequestMetadataAddr: {}, + sqlsec.ServerDBStatementAddr: {}, + sqlsec.ServerDBTypeAddr: {}, + ossec.ServerIOFSFileAddr: {}, httpsec.HTTPClientIPAddr: {}, httpsec.UserIDAddr: {}, httpsec.ServerIoNetURLAddr: {}, diff --git a/internal/appsec/listener/httpsec/http.go b/internal/appsec/listener/httpsec/http.go index 9d2c4a0e1c..a3749f2ff1 100644 --- a/internal/appsec/listener/httpsec/http.go +++ b/internal/appsec/listener/httpsec/http.go @@ -58,6 +58,7 @@ var supportedAddresses = listener.AddressSet{ ServerIoNetURLAddr: {}, sqlsec.ServerDBStatementAddr: {}, sqlsec.ServerDBTypeAddr: {}, + ossec.ServerIOFSFileAddr: {}, } // Install registers the HTTP WAF Event Listener on the given root operation. diff --git a/internal/appsec/waf_test.go b/internal/appsec/waf_test.go index 972ec592e0..01839c65c8 100644 --- a/internal/appsec/waf_test.go +++ b/internal/appsec/waf_test.go @@ -16,11 +16,13 @@ import ( "net/http/httptest" "net/url" "os" + "strconv" "strings" "testing" internal "github.com/DataDog/appsec-internal-go/appsec" waf "github.com/DataDog/go-libddwaf/v3" + pAppsec "gopkg.in/DataDog/dd-trace-go.v1/appsec" "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql" @@ -28,6 +30,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/config" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/ossec" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/httpsec" _ "github.com/glebarez/go-sqlite" @@ -631,6 +634,7 @@ func TestRASPSQLi(t *testing.T) { switch sp.OperationName() { case "http.request": require.Contains(t, sp.Tag("_dd.appsec.json"), "rasp-942-100") + require.Contains(t, sp.Tags(), "_dd.stack") case "sqlite.query": require.NotContains(t, sp.Tags(), "error") } @@ -642,7 +646,80 @@ func TestRASPSQLi(t *testing.T) { }) } } +} + +func TestRASPLFI(t *testing.T) { + t.Setenv("DD_APPSEC_RULES", "testdata/rasp.json") + appsec.Start() + defer appsec.Stop() + + if !appsec.RASPEnabled() { + t.Skip("RASP needs to be enabled for this test") + } + + mux := httptrace.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // Subsequent spans inherit their parent from context. + path := r.URL.Query().Get("path") + block := r.URL.Query().Get("block") + if block == "true" { + // Make sure we don't scan writing operations + require.NoError(t, ossec.ProtectOpen(r.Context(), path, os.O_WRONLY)) + require.NoError(t, ossec.ProtectOpen(r.Context(), path, os.O_CREATE|os.O_RDWR)) + // Make sure we scan reading operations + require.ErrorIs(t, ossec.ProtectOpen(r.Context(), path, os.O_RDONLY), &events.BlockingSecurityEvent{}) + return + } + + require.NoError(t, ossec.ProtectOpen(r.Context(), "/tmp/test", os.O_RDWR)) + w.WriteHeader(204) + }) + srv := httptest.NewServer(mux) + defer srv.Close() + + for _, tc := range []struct { + name string + path string + block bool + }{ + { + name: "no-error", + path: "", + block: false, + }, + { + name: "passwd", + path: "/etc/passwd", + block: true, + }, + { + name: "shadow", + path: "/etc/shadow", + block: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + req, err := http.NewRequest("GET", srv.URL+"?path="+tc.path+"&block="+strconv.FormatBool(tc.block), nil) + require.NoError(t, err) + res, err := srv.Client().Do(req) + require.NoError(t, err) + defer res.Body.Close() + + spans := mt.FinishedSpans() + require.Len(t, spans, 1) + + if tc.block { + require.Equal(t, 403, res.StatusCode) + require.Contains(t, spans[0].Tag("_dd.appsec.json"), "rasp-930-100") + require.Contains(t, spans[0].Tags(), "_dd.stack") + } else { + require.Equal(t, 204, res.StatusCode) + } + }) + } } // BenchmarkSampleWAFContext benchmarks the creation of a WAF context and running the WAF on a request/response pair diff --git a/internal/exectracetest/go.mod b/internal/exectracetest/go.mod index 1b26b20b61..4f52e94b35 100644 --- a/internal/exectracetest/go.mod +++ b/internal/exectracetest/go.mod @@ -15,7 +15,7 @@ require ( github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect github.com/DataDog/datadog-go/v5 v5.3.0 // indirect - github.com/DataDog/go-libddwaf/v3 v3.2.1 // indirect + github.com/DataDog/go-libddwaf/v3 v3.3.0 // indirect github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect github.com/DataDog/sketches-go v1.4.5 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect diff --git a/internal/exectracetest/go.sum b/internal/exectracetest/go.sum index 1a1bc2e820..6244f6e37a 100644 --- a/internal/exectracetest/go.sum +++ b/internal/exectracetest/go.sum @@ -6,8 +6,8 @@ github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3 github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ= github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8= github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q= -github.com/DataDog/go-libddwaf/v3 v3.2.1 h1:lZPc6UxCOwioHc++nsldKR50FpIrRh1uGnGLuryqnE8= -github.com/DataDog/go-libddwaf/v3 v3.2.1/go.mod h1:AP+7Atb8ftSsrha35wht7+K3R+xuzfVSQhabSO4w6CY= +github.com/DataDog/go-libddwaf/v3 v3.3.0 h1:jS72fuQpFgJZEdEJDmHJCPAgNTEMZoz1EUvimPUOiJ4= +github.com/DataDog/go-libddwaf/v3 v3.3.0/go.mod h1:Bz/0JkpGf689mzbUjKJeheJINqsyyhM8p9PDuHdK2Ec= github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=