Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

appsec: Attacker Fingerprinting #2899

Merged
merged 8 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions appsec/appsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var appsecDisabledLog sync.Once
// Note that passing the raw bytes of the HTTP request body is not expected and would
// result in inaccurate attack detection.
// This function always returns nil when appsec is disabled.
func MonitorParsedHTTPBody(ctx context.Context, body interface{}) error {
func MonitorParsedHTTPBody(ctx context.Context, body any) error {
if !appsec.Enabled() {
appsecDisabledLog.Do(func() { log.Warn("appsec: not enabled. Body blocking checks won't be performed.") })
return nil
Expand All @@ -60,7 +60,15 @@ func SetUser(ctx context.Context, id string, opts ...tracer.UserMonitoringOption
appsecDisabledLog.Do(func() { log.Warn("appsec: not enabled. User blocking checks won't be performed.") })
return nil
}
return usersec.MonitorUser(ctx, id)

op, errPtr := usersec.StartUserLoginOperation(ctx, usersec.UserLoginOperationArgs{})
op.Finish(usersec.UserLoginOperationRes{
UserID: id,
SessionID: getSessionID(opts...),
Success: true,
})

return *errPtr
}

// TrackUserLoginSuccessEvent sets a successful user login event, with the given
Expand All @@ -76,17 +84,7 @@ func SetUser(ctx context.Context, id string, opts ...tracer.UserMonitoringOption
// Take-Over (ATO) monitoring, ultimately blocking the IP address and/or user id
// associated to them.
func TrackUserLoginSuccessEvent(ctx context.Context, uid string, md map[string]string, opts ...tracer.UserMonitoringOption) error {
span := getRootSpan(ctx)
if span == nil {
return nil
}

const tagPrefix = "appsec.events.users.login.success."
span.SetTag(tagPrefix+"track", true)
for k, v := range md {
span.SetTag(tagPrefix+k, v)
}
span.SetTag(ext.SamplingPriority, ext.PriorityUserKeep)
TrackCustomEvent(ctx, "users.login.success", md)
return SetUser(ctx, uid, opts...)
}

Expand All @@ -106,14 +104,15 @@ func TrackUserLoginFailureEvent(ctx context.Context, uid string, exists bool, md
return
}

const tagPrefix = "appsec.events.users.login.failure."
span.SetTag(tagPrefix+"track", true)
span.SetTag(tagPrefix+"usr.id", uid)
span.SetTag(tagPrefix+"usr.exists", exists)
for k, v := range md {
span.SetTag(tagPrefix+k, v)
}
span.SetTag(ext.SamplingPriority, ext.PriorityUserKeep)
// We need to do the first call to SetTag ourselves because the map taken by TrackCustomEvent is map[string]string
// and not map [string]any, so the `exists` boolean variable does not fit int
span.SetTag("appsec.events.users.login.failure.usr.exists", exists)
span.SetTag("appsec.events.users.login.failure.usr.id", uid)

TrackCustomEvent(ctx, "users.login.failure", md)

op, _ := usersec.StartUserLoginOperation(ctx, usersec.UserLoginOperationArgs{})
op.Finish(usersec.UserLoginOperationRes{UserID: uid, Success: false})
}

// TrackCustomEvent sets a custom event as service entry span tags. This span is
Expand Down Expand Up @@ -153,3 +152,13 @@ func getRootSpan(ctx context.Context) tracer.Span {
log.Error("appsec: could not access the root span")
return nil
}

func getSessionID(opts ...tracer.UserMonitoringOption) string {
cfg := &tracer.UserMonitoringConfig{
Metadata: make(map[string]string),
}
for _, opt := range opts {
opt(cfg)
}
return cfg.SessionID
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.22.0
require (
cloud.google.com/go/pubsub v1.33.0
github.com/99designs/gqlgen v0.17.36
github.com/DataDog/appsec-internal-go v1.7.0
github.com/DataDog/appsec-internal-go v1.8.0
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0
github.com/DataDog/datadog-go/v5 v5.3.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -625,8 +625,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9s
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/appsec-internal-go v1.7.0 h1:iKRNLih83dJeVya3IoUfK+6HLD/hQsIbyBlfvLmAeb0=
github.com/DataDog/appsec-internal-go v1.7.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g=
github.com/DataDog/appsec-internal-go v1.8.0 h1:1Tfn3LEogntRqZtf88twSApOCAAO3V+NILYhuQIo4J4=
github.com/DataDog/appsec-internal-go v1.8.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo=
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 h1:LplNAmMgZvGU7kKA0+4c1xWOjz828xweW5TCi8Mw9Q0=
Expand Down
2 changes: 1 addition & 1 deletion internal/apps/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
)

require (
github.com/DataDog/appsec-internal-go v1.7.0 // indirect
github.com/DataDog/appsec-internal-go v1.8.0 // indirect
github.com/DataDog/go-libddwaf/v3 v3.4.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
Expand Down
4 changes: 2 additions & 2 deletions internal/apps/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/DataDog/appsec-internal-go v1.7.0 h1:iKRNLih83dJeVya3IoUfK+6HLD/hQsIbyBlfvLmAeb0=
github.com/DataDog/appsec-internal-go v1.7.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g=
github.com/DataDog/appsec-internal-go v1.8.0 h1:1Tfn3LEogntRqZtf88twSApOCAAO3V+NILYhuQIo4J4=
github.com/DataDog/appsec-internal-go v1.8.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo=
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 h1:LplNAmMgZvGU7kKA0+4c1xWOjz828xweW5TCi8Mw9Q0=
Expand Down
49 changes: 23 additions & 26 deletions internal/appsec/emitter/usersec/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,42 @@ import (

"gopkg.in/DataDog/dd-trace-go.v1/appsec/events"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
)

const errorLog = `
appsec: user login monitoring ignored: could not find the http handler instrumentation metadata in the request context:
the request handler is not being monitored by a middleware function or the provided context is not the expected request context
`

type (
// UserIDOperation type representing a call to appsec.SetUser(). It gets both created and destroyed in a single
// UserLoginOperation type representing a call to appsec.SetUser(). It gets both created and destroyed in a single
// call to ExecuteUserIDOperation
UserIDOperation struct {
UserLoginOperation struct {
dyngo.Operation
}
// UserIDOperationArgs is the user ID operation arguments.
UserIDOperationArgs struct {
UserID string
// UserLoginOperationArgs is the user ID operation arguments.
UserLoginOperationArgs struct{}

// UserLoginOperationRes is the user ID operation results.
UserLoginOperationRes struct {
UserID string
SessionID string
Success bool
}
// UserIDOperationRes is the user ID operation results.
UserIDOperationRes struct{}
)

// ExecuteUserIDOperation starts and finishes the UserID operation by emitting a dyngo start and finish events
// An error is returned if the user associated to that operation must be blocked
func ExecuteUserIDOperation(parent dyngo.Operation, args UserIDOperationArgs) error {
func StartUserLoginOperation(ctx context.Context, args UserLoginOperationArgs) (*UserLoginOperation, *error) {
parent, _ := dyngo.FromContext(ctx)
op := &UserLoginOperation{Operation: dyngo.NewOperation(parent)}
var err error
op := &UserIDOperation{Operation: dyngo.NewOperation(parent)}
dyngo.OnData(op, func(e *events.BlockingSecurityEvent) { err = e })
dyngo.StartOperation(op, args)
dyngo.FinishOperation(op, UserIDOperationRes{})
return err
return op, &err
}

// MonitorUser starts and finishes a UserID operation.
// A call to the WAF is made to check the user ID and an error is returned if the
// user should be blocked. The return value is nil otherwise.
func MonitorUser(ctx context.Context, userID string) error {
if parent, ok := dyngo.FromContext(ctx); ok {
return ExecuteUserIDOperation(parent, UserIDOperationArgs{UserID: userID})
}
log.Error("appsec: user ID monitoring ignored: could not find the http handler instrumentation metadata in the request context: the request handler is not being monitored by a middleware function or the provided context is not the expected request context")
return nil

func (op *UserLoginOperation) Finish(args UserLoginOperationRes) {
dyngo.FinishOperation(op, args)
}

func (UserIDOperationArgs) IsArgOf(*UserIDOperation) {}
func (UserIDOperationRes) IsResultOf(*UserIDOperation) {}
func (UserLoginOperationArgs) IsArgOf(*UserLoginOperation) {}
func (UserLoginOperationRes) IsResultOf(*UserLoginOperation) {}
6 changes: 5 additions & 1 deletion internal/appsec/emitter/waf/addresses/addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ const (
ServerResponseHeadersNoCookiesAddr = "server.response.headers.no_cookies"

ClientIPAddr = "http.client_ip"
UserIDAddr = "usr.id"

UserIDAddr = "usr.id"
UserSessionIDAddr = "usr.session_id"
UserLoginSuccessAddr = "server.business_logic.users.login.success"
UserLoginFailureAddr = "server.business_logic.users.login.failure"

ServerIoNetURLAddr = "server.io.net.url"
ServerIOFSFileAddr = "server.io.fs.file"
Expand Down
31 changes: 22 additions & 9 deletions internal/appsec/emitter/waf/addresses/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,40 +28,34 @@ func NewAddressesBuilder() *RunAddressDataBuilder {
}

func (b *RunAddressDataBuilder) WithMethod(method string) *RunAddressDataBuilder {
if method == "" {
return b
}
b.Persistent[ServerRequestMethodAddr] = method
return b
}

func (b *RunAddressDataBuilder) WithRawURI(uri string) *RunAddressDataBuilder {
if uri == "" {
return b
}
b.Persistent[ServerRequestRawURIAddr] = uri
return b
}

func (b *RunAddressDataBuilder) WithHeadersNoCookies(headers map[string][]string) *RunAddressDataBuilder {
if len(headers) == 0 {
return b
headers = nil
}
b.Persistent[ServerRequestHeadersNoCookiesAddr] = headers
return b
}

func (b *RunAddressDataBuilder) WithCookies(cookies map[string][]string) *RunAddressDataBuilder {
if len(cookies) == 0 {
return b
cookies = nil
}
b.Persistent[ServerRequestCookiesAddr] = cookies
return b
}

func (b *RunAddressDataBuilder) WithQuery(query map[string][]string) *RunAddressDataBuilder {
if len(query) == 0 {
return b
query = nil
}
b.Persistent[ServerRequestQueryAddr] = query
return b
Expand Down Expand Up @@ -115,6 +109,25 @@ func (b *RunAddressDataBuilder) WithUserID(id string) *RunAddressDataBuilder {
return b
}

func (b *RunAddressDataBuilder) WithUserSessionID(id string) *RunAddressDataBuilder {
if id == "" {
return b
}
b.Persistent[UserSessionIDAddr] = id
return b

}

func (b *RunAddressDataBuilder) WithUserLoginSuccess() *RunAddressDataBuilder {
b.Persistent[UserLoginSuccessAddr] = nil
return b
}

func (b *RunAddressDataBuilder) WithUserLoginFailure() *RunAddressDataBuilder {
b.Persistent[UserLoginFailureAddr] = nil
return b
}

func (b *RunAddressDataBuilder) WithFilePath(file string) *RunAddressDataBuilder {
if file == "" {
return b
Expand Down
26 changes: 19 additions & 7 deletions internal/appsec/listener/usersec/usec.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,32 @@ func (*Feature) String() string {
func (*Feature) Stop() {}

func NewUserSecFeature(cfg *config.Config, rootOp dyngo.Operation) (listener.Feature, error) {
if !cfg.RASP || !cfg.SupportedAddresses.AnyOf(addresses.UserIDAddr) {
if !cfg.SupportedAddresses.AnyOf(
addresses.UserIDAddr,
addresses.UserSessionIDAddr,
addresses.UserLoginSuccessAddr,
addresses.UserLoginFailureAddr) {
return nil, nil
}

feature := &Feature{}
dyngo.On(rootOp, feature.OnStart)
dyngo.OnFinish(rootOp, feature.OnFinish)
return feature, nil
}

func (*Feature) OnStart(op *usersec.UserIDOperation, args usersec.UserIDOperationArgs) {
func (*Feature) OnFinish(op *usersec.UserLoginOperation, res usersec.UserLoginOperationRes) {
builder := addresses.NewAddressesBuilder().
WithUserID(res.UserID).
WithUserSessionID(res.SessionID)

if res.Success {
builder = builder.WithUserLoginSuccess()
} else {
builder = builder.WithUserLoginFailure()
}

dyngo.EmitData(op, waf.RunEvent{
Operation: op,
RunAddressData: addresses.NewAddressesBuilder().
WithUserID(args.UserID).
Build(),
Operation: op,
RunAddressData: builder.Build(),
})
}
4 changes: 4 additions & 0 deletions internal/appsec/remoteconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,10 @@ var blockingCapabilities = [...]remoteconfig.Capability{
remoteconfig.ASMCustomBlockingResponse,
remoteconfig.ASMTrustedIPs,
remoteconfig.ASMExclusionData,
remoteconfig.ASMEndpointFingerprinting,
remoteconfig.ASMSessionFingerprinting,
remoteconfig.ASMNetworkFingerprinting,
remoteconfig.ASMHeaderFingerprinting,
}

func (a *appsec) enableRCBlocking() {
Expand Down
Loading
Loading