Skip to content

Commit 15049ac

Browse files
committed
WIP
1 parent f50c161 commit 15049ac

File tree

9 files changed

+487
-13
lines changed

9 files changed

+487
-13
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/blocking/BlockingActionHelper.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,27 @@ public static TemplateType determineTemplateType(
118118
}
119119

120120
public static byte[] getTemplate(TemplateType type) {
121+
return getTemplate(type, null);
122+
}
123+
124+
public static byte[] getTemplate(TemplateType type, String blockId) {
125+
byte[] template;
121126
if (type == TemplateType.JSON) {
122-
return TEMPLATE_JSON;
127+
template = TEMPLATE_JSON;
123128
} else if (type == TemplateType.HTML) {
124-
return TEMPLATE_HTML;
129+
template = TEMPLATE_HTML;
130+
} else {
131+
return null;
125132
}
126-
return null;
133+
134+
if (blockId == null || blockId.isEmpty()) {
135+
return template;
136+
}
137+
138+
// Perform placeholder replacement for {block_id}
139+
String templateString = new String(template, java.nio.charset.StandardCharsets.UTF_8);
140+
String replacedTemplate = templateString.replace("{block_id}", blockId);
141+
return replacedTemplate.getBytes(java.nio.charset.StandardCharsets.UTF_8);
127142
}
128143

129144
public static String getContentType(TemplateType type) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You've been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank">Datadog</a></p></footer></body></html>
1+
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You've been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}.block-id{font-size:14px;color:#999;margin-top:20px;font-family:monospace}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p><p class="block-id">Event ID: {block_id}</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank">Datadog</a></p></footer></body></html>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}
1+
{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}],"block_id":"{block_id}"}

dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/blocking/BlockingActionHelperSpecification.groovy

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,106 @@ class BlockingActionHelperSpecification extends DDSpecification {
167167
BlockingActionHelper.reset(Config.get())
168168
tempDir.deleteDir()
169169
}
170+
171+
void 'getTemplate with block_id replaces placeholder in HTML template'() {
172+
given:
173+
def blockId = '12345678-1234-1234-1234-123456789abc'
174+
175+
when:
176+
def template = BlockingActionHelper.getTemplate(HTML, blockId)
177+
def templateStr = new String(template, StandardCharsets.UTF_8)
178+
179+
then:
180+
templateStr.contains("Event ID: ${blockId}")
181+
!templateStr.contains('{block_id}')
182+
}
183+
184+
void 'getTemplate with block_id replaces placeholder in JSON template'() {
185+
given:
186+
def blockId = '12345678-1234-1234-1234-123456789abc'
187+
188+
when:
189+
def template = BlockingActionHelper.getTemplate(JSON, blockId)
190+
def templateStr = new String(template, StandardCharsets.UTF_8)
191+
192+
then:
193+
templateStr.contains("\"block_id\":\"${blockId}\"")
194+
!templateStr.contains('{block_id}')
195+
}
196+
197+
void 'getTemplate without block_id keeps placeholder in HTML template'() {
198+
when:
199+
def template = BlockingActionHelper.getTemplate(HTML, null)
200+
def templateStr = new String(template, StandardCharsets.UTF_8)
201+
202+
then:
203+
templateStr.contains('{block_id}')
204+
}
205+
206+
void 'getTemplate without block_id keeps placeholder in JSON template'() {
207+
when:
208+
def template = BlockingActionHelper.getTemplate(JSON, null)
209+
def templateStr = new String(template, StandardCharsets.UTF_8)
210+
211+
then:
212+
templateStr.contains('{block_id}')
213+
}
214+
215+
void 'getTemplate with empty block_id keeps placeholder'() {
216+
when:
217+
def htmlTemplate = BlockingActionHelper.getTemplate(HTML, '')
218+
def jsonTemplate = BlockingActionHelper.getTemplate(JSON, '')
219+
220+
then:
221+
new String(htmlTemplate, StandardCharsets.UTF_8).contains('{block_id}')
222+
new String(jsonTemplate, StandardCharsets.UTF_8).contains('{block_id}')
223+
}
224+
225+
void 'getTemplate with block_id works with custom HTML template'() {
226+
setup:
227+
File tempDir = File.createTempDir('testTempDir-', '')
228+
Config config = Mock(Config)
229+
File tempFile = new File(tempDir, 'template.html')
230+
tempFile << '<body>Custom template with block_id: {block_id}</body>'
231+
def blockId = 'test-block-id-123'
232+
233+
when:
234+
BlockingActionHelper.reset(config)
235+
def template = BlockingActionHelper.getTemplate(HTML, blockId)
236+
def templateStr = new String(template, StandardCharsets.UTF_8)
237+
238+
then:
239+
1 * config.getAppSecHttpBlockedTemplateHtml() >> tempFile.toString()
240+
1 * config.getAppSecHttpBlockedTemplateJson() >> null
241+
templateStr.contains("Custom template with block_id: ${blockId}")
242+
!templateStr.contains('{block_id}')
243+
244+
cleanup:
245+
BlockingActionHelper.reset(Config.get())
246+
tempDir.deleteDir()
247+
}
248+
249+
void 'getTemplate with block_id works with custom JSON template'() {
250+
setup:
251+
File tempDir = File.createTempDir('testTempDir-', '')
252+
Config config = Mock(Config)
253+
File tempFile = new File(tempDir, 'template.json')
254+
tempFile << '{"error":"blocked","id":"{block_id}"}'
255+
def blockId = 'test-block-id-456'
256+
257+
when:
258+
BlockingActionHelper.reset(config)
259+
def template = BlockingActionHelper.getTemplate(JSON, blockId)
260+
def templateStr = new String(template, StandardCharsets.UTF_8)
261+
262+
then:
263+
1 * config.getAppSecHttpBlockedTemplateHtml() >> null
264+
1 * config.getAppSecHttpBlockedTemplateJson() >> tempFile.toString()
265+
templateStr.contains("\"error\":\"blocked\",\"id\":\"${blockId}\"")
266+
!templateStr.contains('{block_id}')
267+
268+
cleanup:
269+
BlockingActionHelper.reset(Config.get())
270+
tempDir.deleteDir()
271+
}
170272
}

dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,9 @@ private Flow.Action.RequestBlockingAction createBlockRequestAction(
477477
} catch (IllegalArgumentException iae) {
478478
log.warn("Unknown content type: {}; using auto", contentType);
479479
}
480-
return new Flow.Action.RequestBlockingAction(statusCode, blockingContentType);
480+
String blockId = (String) actionInfo.parameters.get("block_id");
481+
return new Flow.Action.RequestBlockingAction(
482+
statusCode, blockingContentType, Collections.emptyMap(), blockId);
481483
} catch (RuntimeException cce) {
482484
log.warn("Invalid blocking action data", cce);
483485
if (!isRasp) {
@@ -506,7 +508,13 @@ private Flow.Action.RequestBlockingAction createRedirectRequestAction(
506508
if (location == null) {
507509
throw new RuntimeException("redirect_request action has no location");
508510
}
509-
return Flow.Action.RequestBlockingAction.forRedirect(statusCode, location);
511+
String blockId = (String) actionInfo.parameters.get("block_id");
512+
if (blockId != null) {
513+
// Append block_id as URL parameter to redirect location
514+
String separator = location.contains("?") ? "&" : "?";
515+
location = location + separator + "block_id=" + blockId;
516+
}
517+
return Flow.Action.RequestBlockingAction.forRedirect(statusCode, location, blockId);
510518
} catch (RuntimeException cce) {
511519
log.warn("Invalid blocking action data", cce);
512520
if (!isRasp) {

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,13 @@ class WAFModuleSpecification extends DDSpecification {
608608
flow.action instanceof Flow.Action.RequestBlockingAction
609609
with(flow.action as Flow.Action.RequestBlockingAction) {
610610
assert it.statusCode == statusCode
611-
assert it.extraHeaders == [Location: "https://example${variant}.com/"]
611+
// Location header may include block_id parameter from libddwaf
612+
def location = it.extraHeaders['Location']
613+
assert location.startsWith("https://example${variant}.com/")
614+
// If block_id is present, it should be a valid UUID
615+
if (location.contains('block_id=')) {
616+
assert location.matches(".*block_id=[0-9a-f-]{36}.*")
617+
}
612618
}
613619

614620
where:
@@ -2047,6 +2053,82 @@ class WAFModuleSpecification extends DDSpecification {
20472053
!flow.blocking // Should not block since keep: false
20482054
}
20492055
2056+
void 'block_id is extracted from blocking action and included in RequestBlockingAction'() {
2057+
setup:
2058+
def rulesConfig = [
2059+
version: '2.1',
2060+
metadata: [
2061+
rules_version: '1.0.0'
2062+
],
2063+
actions: [
2064+
[
2065+
id: 'block',
2066+
type: 'block_request',
2067+
parameters: [
2068+
status_code: 403,
2069+
type: 'json'
2070+
]
2071+
]
2072+
],
2073+
rules: [
2074+
[
2075+
id: 'test-rule',
2076+
name: 'Test blocking rule',
2077+
tags: [
2078+
type: 'security_scanner',
2079+
category: 'attack_attempt'
2080+
],
2081+
conditions: [
2082+
[
2083+
parameters: [
2084+
inputs: [
2085+
[
2086+
address: 'server.request.headers.no_cookies',
2087+
key_path: ['user-agent']
2088+
]
2089+
],
2090+
regex: '^BlockTest'
2091+
],
2092+
operator: 'match_regex'
2093+
]
2094+
],
2095+
on_match: ['block']
2096+
]
2097+
]
2098+
]
2099+
2100+
when:
2101+
initialRuleAddWithMap(rulesConfig)
2102+
wafModule.applyConfig(reconf)
2103+
def bundle = MapDataBundle.of(KnownAddresses.HEADERS_NO_COOKIES,
2104+
new CaseInsensitiveMap<List<String>>(['user-agent': 'BlockTest']))
2105+
def flow = new ChangeableFlow()
2106+
dataListener.onDataAvailable(flow, ctx, bundle, gwCtx)
2107+
ctx.closeWafContext()
2108+
2109+
then:
2110+
1 * ctx.getOrCreateWafContext(_, true, false)
2111+
2 * ctx.getWafMetrics() >> metrics
2112+
1 * ctx.isWafContextClosed() >> false
2113+
1 * ctx.closeWafContext()
2114+
1 * ctx.reportEvents(_)
2115+
1 * ctx.setWafBlocked()
2116+
1 * ctx.isThrottled(null)
2117+
1 * ctx.setManuallyKept(true)
2118+
0 * ctx._(*_)
2119+
flow.blocking
2120+
flow.action instanceof Flow.Action.RequestBlockingAction
2121+
with(flow.action as Flow.Action.RequestBlockingAction) {
2122+
assert it.statusCode == 403
2123+
assert it.blockingContentType == BlockingContentType.JSON
2124+
// blockId should be extracted from libddwaf (or null if not present)
2125+
// With libddwaf v18.0.0, block_id is automatically generated
2126+
// We just verify the field is accessible
2127+
def blockId = it.blockId
2128+
assert blockId == null || blockId.matches('[0-9a-f-]{36}')
2129+
}
2130+
}
2131+
20502132
private static class BadConfig implements Map<String, Object> {
20512133
@Delegate
20522134
private Map<String, Object> delegate

dd-java-agent/instrumentation/servlet/jakarta-servlet-5.0/src/main/java/datadog/trace/instrumentation/servlet5/JakartaServletBlockingHelper.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ public static void commitBlockingResponse(
2323
int statusCode_,
2424
BlockingContentType bct,
2525
Map<String, String> extraHeaders) {
26+
commitBlockingResponse(segment, httpServletRequest, resp, statusCode_, bct, extraHeaders, null);
27+
}
28+
29+
public static void commitBlockingResponse(
30+
TraceSegment segment,
31+
HttpServletRequest httpServletRequest,
32+
HttpServletResponse resp,
33+
int statusCode_,
34+
BlockingContentType bct,
35+
Map<String, String> extraHeaders,
36+
String blockId) {
2637
int statusCode = BlockingActionHelper.getHttpCode(statusCode_);
2738
if (!start(resp, statusCode)) {
2839
return;
@@ -37,7 +48,7 @@ public static void commitBlockingResponse(
3748
String acceptHeader = httpServletRequest.getHeader("Accept");
3849
BlockingActionHelper.TemplateType type =
3950
BlockingActionHelper.determineTemplateType(bct, acceptHeader);
40-
template = BlockingActionHelper.getTemplate(type);
51+
template = BlockingActionHelper.getTemplate(type, blockId);
4152
String contentType = BlockingActionHelper.getContentType(type);
4253

4354
resp.setHeader("Content-length", Integer.toString(template.length));
@@ -68,7 +79,8 @@ public static void commitBlockingResponse(
6879
resp,
6980
rba.getStatusCode(),
7081
rba.getBlockingContentType(),
71-
rba.getExtraHeaders());
82+
rba.getExtraHeaders(),
83+
rba.getBlockId());
7284
}
7385

7486
private static boolean start(HttpServletResponse resp, int statusCode) {

0 commit comments

Comments
 (0)