Skip to content

Commit

Permalink
[ASM] Add support for attacker fingerprinting (#4698)
Browse files Browse the repository at this point in the history
* Report WAF fingerprints

* WAF fingerprint RC capabilities

* Linting

* Remove useless file

* Add blank line

* Remove unused capability

* Generate fingerprint on user login events

* Fix linting

* Add passport plugin test to GHA

* Add business logic addressses

* Add body-parser dep to passport plugin test

* Reformat test

* Refactor report derivatives

* Move method to its right place

* Unify reportSchemas and reportFingerprint test in one suite

* Unify reportSchemas and reportFingerprint test in one suite
  • Loading branch information
CarlesDD authored and bengl committed Oct 16, 2024
1 parent 1138717 commit 9a4438a
Show file tree
Hide file tree
Showing 17 changed files with 724 additions and 15 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/appsec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,17 @@ jobs:
- run: yarn test:integration:appsec
- uses: ./.github/actions/node/latest
- run: yarn test:integration:appsec

passport:
runs-on: ubuntu-latest
env:
PLUGINS: passport-local|passport-http
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/node/setup
- uses: ./.github/actions/install
- uses: ./.github/actions/node/oldest
- run: yarn test:appsec:plugins:ci
- uses: ./.github/actions/node/latest
- run: yarn test:appsec:plugins:ci
- uses: codecov/codecov-action@v3
5 changes: 4 additions & 1 deletion packages/dd-trace/src/appsec/addresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ module.exports = {
FS_OPERATION_PATH: 'server.io.fs.file',

DB_STATEMENT: 'server.db.statement',
DB_SYSTEM: 'server.db.system'
DB_SYSTEM: 'server.db.system',

LOGIN_SUCCESS: 'server.business_logic.users.login.success',
LOGIN_FAILURE: 'server.business_logic.users.login.failure'
}
5 changes: 4 additions & 1 deletion packages/dd-trace/src/appsec/remote_config/capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,8 @@ module.exports = {
ASM_RASP_SQLI: 1n << 21n,
ASM_RASP_SSRF: 1n << 23n,
ASM_RASP_LFI: 1n << 24n,
APM_TRACING_SAMPLE_RULES: 1n << 29n
APM_TRACING_SAMPLE_RULES: 1n << 29n,
ASM_ENDPOINT_FINGERPRINT: 1n << 32n,
ASM_NETWORK_FINGERPRINT: 1n << 34n,
ASM_HEADER_FINGERPRINT: 1n << 35n
}
6 changes: 6 additions & 0 deletions packages/dd-trace/src/appsec/remote_config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ function enableWafUpdate (appsecConfig) {
rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_RULES, true)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, true)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, true)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_ENDPOINT_FINGERPRINT, true)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_NETWORK_FINGERPRINT, true)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_HEADER_FINGERPRINT, true)

if (appsecConfig.rasp?.enabled) {
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SQLI, true)
Expand Down Expand Up @@ -104,6 +107,9 @@ function disableWafUpdate () {
rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_RULES, false)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, false)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, false)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_ENDPOINT_FINGERPRINT, false)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_NETWORK_FINGERPRINT, false)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_HEADER_FINGERPRINT, false)

rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SQLI, false)
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SSRF, false)
Expand Down
17 changes: 12 additions & 5 deletions packages/dd-trace/src/appsec/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ function reportAttack (attackData) {
rootSpan.addTags(newTags)
}

function reportSchemas (derivatives) {
function isFingerprintDerivative (derivative) {
return derivative.startsWith('_dd.appsec.fp')
}

function reportDerivatives (derivatives) {
if (!derivatives) return

const req = storage.getStore()?.req
Expand All @@ -162,9 +166,12 @@ function reportSchemas (derivatives) {
if (!rootSpan) return

const tags = {}
for (const [address, value] of Object.entries(derivatives)) {
const gzippedValue = zlib.gzipSync(JSON.stringify(value))
tags[address] = gzippedValue.toString('base64')
for (let [tag, value] of Object.entries(derivatives)) {
if (!isFingerprintDerivative(tag)) {
const gzippedValue = zlib.gzipSync(JSON.stringify(value))
value = gzippedValue.toString('base64')
}
tags[tag] = value
}

rootSpan.addTags(tags)
Expand Down Expand Up @@ -248,7 +255,7 @@ module.exports = {
reportMetrics,
reportAttack,
reportWafUpdate: incrementWafUpdatesMetric,
reportSchemas,
reportDerivatives,
finishRequest,
setRateLimit,
mapHeaderAndTags
Expand Down
5 changes: 5 additions & 0 deletions packages/dd-trace/src/appsec/sdk/track_event.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { getRootSpan } = require('./utils')
const { MANUAL_KEEP } = require('../../../../../ext/tags')
const { setUserTags } = require('./set_user')
const standalone = require('../standalone')
const waf = require('../waf')

function trackUserLoginSuccessEvent (tracer, user, metadata) {
// TODO: better user check here and in _setUser() ?
Expand Down Expand Up @@ -76,6 +77,10 @@ function trackEvent (eventName, fields, sdkMethodName, rootSpan, mode) {
rootSpan.addTags(tags)

standalone.sample(rootSpan)

if (['users.login.success', 'users.login.failure'].includes(eventName)) {
waf.run({ persistent: { [`server.business_logic.${eventName}`]: null } })
}
}

module.exports = {
Expand Down
2 changes: 1 addition & 1 deletion packages/dd-trace/src/appsec/waf/waf_context_wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class WAFContextWrapper {
Reporter.reportAttack(JSON.stringify(result.events))
}

Reporter.reportSchemas(result.derivatives)
Reporter.reportDerivatives(result.derivatives)

if (wafRunFinished.hasSubscribers) {
wafRunFinished.publish({ payload })
Expand Down
204 changes: 204 additions & 0 deletions packages/dd-trace/test/appsec/attacker-fingerprinting-rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
{
"version": "2.2",
"metadata": {
"rules_version": "1.5.0"
},
"rules": [
{
"id": "tst-000-001-",
"name": "rule to test fingerprint",
"tags": {
"type": "attack_tool",
"category": "attack_attempt",
"confidence": "1"
},
"conditions": [
{
"parameters": {
"inputs": [
{
"address": "server.request.query"
}
],
"list": [
"testattack"
]
},
"operator": "phrase_match"
}
],
"transformers": []
}
],
"processors": [
{
"id": "http-endpoint-fingerprint",
"generator": "http_endpoint_fingerprint",
"conditions": [
{
"operator": "exists",
"parameters": {
"inputs": [
{
"address": "waf.context.event"
},
{
"address": "server.business_logic.users.login.failure"
},
{
"address": "server.business_logic.users.login.success"
}
]
}
}
],
"parameters": {
"mappings": [
{
"method": [
{
"address": "server.request.method"
}
],
"uri_raw": [
{
"address": "server.request.uri.raw"
}
],
"body": [
{
"address": "server.request.body"
}
],
"query": [
{
"address": "server.request.query"
}
],
"output": "_dd.appsec.fp.http.endpoint"
}
]
},
"evaluate": false,
"output": true
},
{
"id": "http-header-fingerprint",
"generator": "http_header_fingerprint",
"conditions": [
{
"operator": "exists",
"parameters": {
"inputs": [
{
"address": "waf.context.event"
},
{
"address": "server.business_logic.users.login.failure"
},
{
"address": "server.business_logic.users.login.success"
}
]
}
}
],
"parameters": {
"mappings": [
{
"headers": [
{
"address": "server.request.headers.no_cookies"
}
],
"output": "_dd.appsec.fp.http.header"
}
]
},
"evaluate": false,
"output": true
},
{
"id": "http-network-fingerprint",
"generator": "http_network_fingerprint",
"conditions": [
{
"operator": "exists",
"parameters": {
"inputs": [
{
"address": "waf.context.event"
},
{
"address": "server.business_logic.users.login.failure"
},
{
"address": "server.business_logic.users.login.success"
}
]
}
}
],
"parameters": {
"mappings": [
{
"headers": [
{
"address": "server.request.headers.no_cookies"
}
],
"output": "_dd.appsec.fp.http.network"
}
]
},
"evaluate": false,
"output": true
},
{
"id": "session-fingerprint",
"generator": "session_fingerprint",
"conditions": [
{
"operator": "exists",
"parameters": {
"inputs": [
{
"address": "waf.context.event"
},
{
"address": "server.business_logic.users.login.failure"
},
{
"address": "server.business_logic.users.login.success"
}
]
}
}
],
"parameters": {
"mappings": [
{
"cookies": [
{
"address": "server.request.cookies"
}
],
"session_id": [
{
"address": "usr.session_id"
}
],
"user_id": [
{
"address": "usr.id"
}
],
"output": "_dd.appsec.fp.session"
}
]
},
"evaluate": false,
"output": true
}
]
}
Loading

0 comments on commit 9a4438a

Please sign in to comment.