Skip to content

Commit 3b32ad1

Browse files
committed
Enable WAF generate_stack action by default
1 parent e0a335c commit 3b32ad1

8 files changed

Lines changed: 153 additions & 19 deletions

File tree

dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFModule.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -477,11 +477,14 @@ public void onDataAvailable(
477477
} else if ("redirect_request".equals(actionInfo.type)) {
478478
Flow.Action.RequestBlockingAction rba = createRedirectRequestAction(actionInfo);
479479
flow.setAction(rba);
480-
} else if ("generate_stack".equals(actionInfo.type)
481-
&& Config.get().isAppSecStackTraceEnabled()) {
482-
String stackId = (String) actionInfo.parameters.get("stack_id");
483-
StackTraceEvent stackTraceEvent = createExploitStackTraceEvent(stackId);
484-
reqCtx.reportStackTrace(stackTraceEvent);
480+
} else if ("generate_stack".equals(actionInfo.type)) {
481+
if (Config.get().isAppSecStackTraceEnabled()) {
482+
String stackId = (String) actionInfo.parameters.get("stack_id");
483+
StackTraceEvent stackTraceEvent = createExploitStackTraceEvent(stackId);
484+
reqCtx.reportStackTrace(stackTraceEvent);
485+
} else {
486+
log.debug("Ignoring action with type generate_stack (disabled by config)");
487+
}
485488
} else {
486489
log.info("Ignoring action with type {}", actionInfo.type);
487490
}

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFModuleSpecification.groovy

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ class PowerWAFModuleSpecification extends DDSpecification {
657657

658658
then:
659659
1 * segment.setTagTop('_dd.appsec.waf.version', _ as String)
660-
1 * segment.setTagTop('_dd.appsec.event_rules.loaded', 115)
660+
1 * segment.setTagTop('_dd.appsec.event_rules.loaded', 116)
661661
1 * segment.setTagTop('_dd.appsec.event_rules.error_count', 1)
662662
1 * segment.setTagTop('_dd.appsec.event_rules.errors', { it =~ /\{"[^"]+":\["bad rule"\]\}/})
663663
1 * segment.setTagTop('asm.keep', true)
@@ -777,11 +777,12 @@ class PowerWAFModuleSpecification extends DDSpecification {
777777
setupWithStubConfigService()
778778
AppSecEvent event
779779
StackTraceEvent stackTrace
780-
injectSysConfig('appsec.stacktrace.enabled', 'true')
781780
pwafModule = new PowerWAFModule() // replace the one created too soon
781+
def attackBundle = MapDataBundle.of(KnownAddresses.HEADERS_NO_COOKIES,
782+
new CaseInsensitiveMap<List<String>>(['user-agent': 'Arachni/generate-stacktrace']))
782783

783784
when:
784-
dataListener.onDataAvailable(Stub(ChangeableFlow), ctx, ATTACK_BUNDLE, gwCtx)
785+
dataListener.onDataAvailable(Stub(ChangeableFlow), ctx, attackBundle, gwCtx)
785786
ctx.closeAdditive()
786787

787788
then:
@@ -791,16 +792,16 @@ class PowerWAFModuleSpecification extends DDSpecification {
791792
ctx.reportEvents(_ as Collection<AppSecEvent>) >> { event = it[0].iterator().next() }
792793
ctx.reportStackTrace(_ as StackTraceEvent) >> { stackTrace = it[0] }
793794

794-
event.rule.id == 'ua0-600-12x'
795+
event.rule.id == 'generate-stacktrace-on-scanner'
795796
event.rule.name == 'Arachni'
796797
event.rule.tags == [type: 'security_scanner', category: 'attack_attempt']
797798

798799
event.ruleMatches[0].operator == 'match_regex'
799-
event.ruleMatches[0].operator_value == '^Arachni\\/v'
800+
event.ruleMatches[0].operator_value == '^Arachni\\/generate-stacktrace'
800801
event.ruleMatches[0].parameters[0].address == 'server.request.headers.no_cookies'
801-
event.ruleMatches[0].parameters[0].highlight == ['Arachni/v']
802+
event.ruleMatches[0].parameters[0].highlight == ['Arachni/generate-stacktrace']
802803
event.ruleMatches[0].parameters[0].key_path == ['user-agent']
803-
event.ruleMatches[0].parameters[0].value == 'Arachni/v0'
804+
event.ruleMatches[0].parameters[0].value == 'Arachni/generate-stacktrace'
804805

805806
event.spanId == 777
806807

dd-java-agent/appsec/src/test/resources/test_multi_config.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3803,7 +3803,33 @@
38033803
}
38043804
],
38053805
"transformers": [],
3806-
"on_match": ["block", "stack_trace"]
3806+
"on_match": ["block"]
3807+
},
3808+
{
3809+
"id": "generate-stacktrace-on-scanner",
3810+
"name": "Arachni",
3811+
"tags": {
3812+
"type": "security_scanner",
3813+
"category": "attack_attempt"
3814+
},
3815+
"conditions": [
3816+
{
3817+
"parameters": {
3818+
"inputs": [
3819+
{
3820+
"address": "server.request.headers.no_cookies",
3821+
"key_path": [
3822+
"user-agent"
3823+
]
3824+
}
3825+
],
3826+
"regex": "^Arachni\\/generate-stacktrace"
3827+
},
3828+
"operator": "match_regex"
3829+
}
3830+
],
3831+
"transformers": [],
3832+
"on_match": ["stack_trace"]
38073833
},
38083834
{
38093835
"id": "ua0-600-13x",

dd-smoke-tests/appsec/springboot/src/test/groovy/datadog/smoketest/appsec/SpringBootSmokeTest.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class SpringBootSmokeTest extends AbstractAppSecServerSmokeTest {
295295
}
296296
}
297297
assert trigger != null, 'test trigger not found'
298+
rootSpan.span.metaStruct != null
299+
def stack = rootSpan.span.metaStruct.get('_dd.stack')
300+
assert stack != null, 'stack is not set'
298301
}
299302

300303
void 'rasp blocks on sql injection'() {

dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public final class ConfigDefaults {
103103
static final boolean DEFAULT_API_SECURITY_ENABLED = false;
104104
static final float DEFAULT_API_SECURITY_REQUEST_SAMPLE_RATE = 0.1f; // 10 %
105105
static final boolean DEFAULT_APPSEC_RASP_ENABLED = false;
106-
static final boolean DEFAULT_APPSEC_STACK_TRACE_ENABLED = false;
106+
static final boolean DEFAULT_APPSEC_STACK_TRACE_ENABLED = true;
107107
static final int DEFAULT_APPSEC_MAX_STACK_TRACES = 2;
108108
static final int DEFAULT_APPSEC_MAX_STACK_TRACE_DEPTH = 32;
109109
static final String DEFAULT_IAST_ENABLED = "false";

utils/test-agent-utils/decoder/src/main/java/datadog/trace/test/agent/decoder/DecodedSpan.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public interface DecodedSpan {
2323

2424
Map<String, String> getMeta();
2525

26+
Map<String, Object> getMetaStruct();
27+
2628
Map<String, Number> getMetrics();
2729

2830
String getType();

utils/test-agent-utils/decoder/src/main/java/datadog/trace/test/agent/decoder/v04/raw/SpanV04.java

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
import datadog.trace.test.agent.decoder.DecodedSpan;
44
import java.io.IOException;
5+
import java.util.ArrayList;
56
import java.util.Collections;
67
import java.util.HashMap;
8+
import java.util.List;
79
import java.util.Map;
810
import org.msgpack.core.MessageIntegerOverflowException;
11+
import org.msgpack.core.MessagePack;
912
import org.msgpack.core.MessageUnpacker;
13+
import org.msgpack.value.Value;
1014
import org.msgpack.value.ValueType;
1115

1216
public class SpanV04 implements DecodedSpan {
@@ -33,9 +37,11 @@ static DecodedSpan[] unpackSpans(MessageUnpacker unpacker) {
3337
static SpanV04 unpack(MessageUnpacker unpacker) {
3438
try {
3539
int size = unpacker.unpackMapHeader();
36-
if (size != 12) {
40+
if (size != 12 && size != 13) {
3741
throw new IllegalArgumentException(
38-
"Wrong span element map size " + size + ". Expected 12.");
42+
"Wrong span element map size "
43+
+ size
44+
+ ". Expected 12 (plain) or 13 (with meta_struct).");
3945
}
4046

4147
String service = unpackString("service", unpacker);
@@ -55,9 +61,26 @@ static SpanV04 unpack(MessageUnpacker unpacker) {
5561
unpackKey("meta", unpacker);
5662
Map<String, String> meta = unpackMeta(unpacker, spanId);
5763

64+
Map<String, Object> metaStruct = null;
65+
if (size > 12) {
66+
unpackKey("meta_struct", unpacker);
67+
metaStruct = unpackMetaStruct(unpacker, spanId);
68+
}
69+
5870
return new SpanV04(
59-
service, name, resource, traceId, spanId, parentId, start, duration, error, type, metrics,
60-
meta);
71+
service,
72+
name,
73+
resource,
74+
traceId,
75+
spanId,
76+
parentId,
77+
start,
78+
duration,
79+
error,
80+
type,
81+
metrics,
82+
meta,
83+
metaStruct);
6184
} catch (Throwable t) {
6285
if (t instanceof RuntimeException) {
6386
throw (RuntimeException) t;
@@ -95,6 +118,23 @@ private static Map<String, String> unpackMeta(MessageUnpacker unpacker, long spa
95118
return meta;
96119
}
97120

121+
private static Map<String, Object> unpackMetaStruct(MessageUnpacker unpacker, long spanId)
122+
throws IOException {
123+
int metaSize = unpacker.unpackMapHeader();
124+
if (metaSize < 0) {
125+
throw new IllegalArgumentException(
126+
"Negative meta map size " + metaSize + " for span " + spanId);
127+
}
128+
Map<String, Object> result = new HashMap<>(metaSize);
129+
for (int i = 0; i < metaSize; i++) {
130+
final String key = unpacker.unpackString();
131+
final int payloadSize = unpacker.unpackBinaryHeader();
132+
final byte[] payload = unpacker.readPayload(payloadSize);
133+
result.put(key, decodeObject(payload));
134+
}
135+
return result;
136+
}
137+
98138
private static String unpackString(String expectedKey, MessageUnpacker unpacker)
99139
throws IOException {
100140
unpackKey(expectedKey, unpacker);
@@ -143,6 +183,51 @@ static Number unpackNumber(MessageUnpacker unpacker) {
143183
return result;
144184
}
145185

186+
private static Object decodeObject(final byte[] input) {
187+
MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(input);
188+
try {
189+
final Value value = unpacker.unpackValue();
190+
return convertValueToObject(value);
191+
} catch (IOException e) {
192+
throw new IllegalArgumentException("Failed to decode object.", e);
193+
}
194+
}
195+
196+
private static Object convertValueToObject(final Value value) {
197+
switch (value.getValueType()) {
198+
case NIL:
199+
return null;
200+
case BOOLEAN:
201+
return value.asBooleanValue().getBoolean();
202+
case INTEGER:
203+
return value.asIntegerValue().toLong();
204+
case FLOAT:
205+
return value.asFloatValue().toFloat();
206+
case BINARY:
207+
return value.asBinaryValue().asByteArray();
208+
case STRING:
209+
return value.asStringValue().asString();
210+
case ARRAY:
211+
final List<Value> array = value.asArrayValue().list();
212+
final List<Object> result = new ArrayList<>(array.size());
213+
for (final Value element : array) {
214+
result.add(convertValueToObject(element));
215+
}
216+
return result;
217+
case MAP:
218+
final Map<Value, Value> map = value.asMapValue().map();
219+
final Map<String, Object> resultMap = new HashMap<>(map.size());
220+
for (final Map.Entry<Value, Value> entry : map.entrySet()) {
221+
resultMap.put(
222+
entry.getKey().asStringValue().asString(), convertValueToObject(entry.getValue()));
223+
}
224+
return resultMap;
225+
default:
226+
throw new IllegalArgumentException(
227+
"Failed to convert value to object. Unexpected value type " + value.getValueType());
228+
}
229+
}
230+
146231
private final String service;
147232
private final String name;
148233
private final String resource;
@@ -153,6 +238,7 @@ static Number unpackNumber(MessageUnpacker unpacker) {
153238
private final long duration;
154239
private final int error;
155240
private final Map<String, String> meta;
241+
private final Map<String, Object> metaStruct;
156242
private final Map<String, Number> metrics;
157243
private final String type;
158244

@@ -168,7 +254,8 @@ public SpanV04(
168254
int error,
169255
String type,
170256
Map<String, Number> metrics,
171-
Map<String, String> meta) {
257+
Map<String, String> meta,
258+
Map<String, Object> metaStruct) {
172259
this.service = service;
173260
this.name = name;
174261
this.resource = resource;
@@ -179,6 +266,7 @@ public SpanV04(
179266
this.duration = duration;
180267
this.error = error;
181268
this.meta = Collections.unmodifiableMap(meta);
269+
this.metaStruct = metaStruct == null ? null : Collections.unmodifiableMap(metaStruct);
182270
this.metrics = Collections.unmodifiableMap(metrics);
183271
this.type = type;
184272
}
@@ -223,6 +311,10 @@ public Map<String, String> getMeta() {
223311
return meta;
224312
}
225313

314+
public Map<String, Object> getMetaStruct() {
315+
return metaStruct;
316+
}
317+
226318
public Map<String, Number> getMetrics() {
227319
return metrics;
228320
}
@@ -257,6 +349,8 @@ public String toString() {
257349
+ error
258350
+ ", meta="
259351
+ meta
352+
+ ", metaStruct="
353+
+ metaStruct
260354
+ ", metrics="
261355
+ metrics
262356
+ ", type='"

utils/test-agent-utils/decoder/src/main/java/datadog/trace/test/agent/decoder/v05/raw/SpanV05.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ public Map<String, String> getMeta() {
188188
return meta;
189189
}
190190

191+
public Map<String, Object> getMetaStruct() {
192+
// XXX: meta_struct is not supported in v0.5.
193+
return null;
194+
}
195+
191196
public Map<String, Number> getMetrics() {
192197
return metrics;
193198
}

0 commit comments

Comments
 (0)