Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ class Jersey2JettyTest extends HttpServerTest<JettyServer> {
true
}

@Override
boolean testBodyFilenames() {
true
}

@Override
boolean testBodyJson() {
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class Jersey3JettyTest extends HttpServerTest<JettyServer> {
true
}

@Override
boolean testBodyFilenames() {
true
}

@Override
boolean testBodyJson() {
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import net.bytebuddy.asm.Advice;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.message.internal.MediaTypes;

Expand Down Expand Up @@ -72,42 +73,78 @@ static void after(
CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
BiFunction<RequestContext, Object, Flow<Void>> callback =
cbp.getCallback(EVENTS.requestBodyProcessed());
if (callback == null) {
BiFunction<RequestContext, List<String>, Flow<Void>> filenamesCallback =
cbp.getCallback(EVENTS.requestFilesFilenames());
if (callback == null && filenamesCallback == null) {
return;
}

Map<String, List<String>> map = new HashMap<>();
for (BodyPart bodyPart : ret.getBodyParts()) {
if (!(bodyPart instanceof FormDataBodyPart)) {
continue;
}
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
if (!MediaTypes.typeEqual(MediaType.TEXT_PLAIN_TYPE, dataBodyPart.getMediaType())) {
continue;
}
// if the type of dataBodyPart.getEntity() is BodyPartEntity, it is safe to read the part
// more than once. So we're not depriving the application of the data by consuming it here
String v = dataBodyPart.getValue();
if (callback != null) {
Map<String, List<String>> map = new HashMap<>();
for (BodyPart bodyPart : ret.getBodyParts()) {
if (!(bodyPart instanceof FormDataBodyPart)) {
continue;
}
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
if (!MediaTypes.typeEqual(MediaType.TEXT_PLAIN_TYPE, dataBodyPart.getMediaType())) {
continue;
}
// if the type of dataBodyPart.getEntity() is BodyPartEntity, it is safe to read the part
// more than once. So we're not depriving the application of the data by consuming it here
String v = dataBodyPart.getValue();

String name = dataBodyPart.getName();
List<String> values = map.get(name);
if (values == null) {
values = new ArrayList<>();
map.put(name, values);
}

String name = dataBodyPart.getName();
List<String> values = map.get(name);
if (values == null) {
values = new ArrayList<>();
map.put(name, values);
values.add(v);
}

values.add(v);
Flow<Void> flow = callback.apply(reqCtx, map);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (for MultiPartReaderClientSide/readFrom)");
reqCtx.getTraceSegment().effectivelyBlocked();
}
}
}

Flow<Void> flow = callback.apply(reqCtx, map);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (for MultiPartReaderClientSide/readFrom)");
reqCtx.getTraceSegment().effectivelyBlocked();
if (filenamesCallback != null) {
List<String> filenames = new ArrayList<>();
for (BodyPart bodyPart : ret.getBodyParts()) {
if (!(bodyPart instanceof FormDataBodyPart)) {
continue;
}
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
FormDataContentDisposition cd = dataBodyPart.getFormDataContentDisposition();
if (cd == null) {
continue;
}
String filename = cd.getFileName();
if (filename != null && !filename.isEmpty()) {
filenames.add(filename);
}
}
if (!filenames.isEmpty()) {
Flow<Void> filenamesFlow = filenamesCallback.apply(reqCtx, filenames);
Flow.Action filenamesAction = filenamesFlow.getAction();
if (t == null && filenamesAction instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba =
(Flow.Action.RequestBlockingAction) filenamesAction;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (multipart file upload)");
reqCtx.getTraceSegment().effectivelyBlocked();
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import net.bytebuddy.asm.Advice;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.message.internal.MediaTypes;

Expand Down Expand Up @@ -72,42 +73,78 @@ static void after(
CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
BiFunction<RequestContext, Object, Flow<Void>> callback =
cbp.getCallback(EVENTS.requestBodyProcessed());
if (callback == null) {
BiFunction<RequestContext, List<String>, Flow<Void>> filenamesCallback =
cbp.getCallback(EVENTS.requestFilesFilenames());
if (callback == null && filenamesCallback == null) {
return;
}

Map<String, List<String>> map = new HashMap<>();
for (BodyPart bodyPart : ret.getBodyParts()) {
if (!(bodyPart instanceof FormDataBodyPart)) {
continue;
}
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
if (!MediaTypes.typeEqual(MediaType.TEXT_PLAIN_TYPE, dataBodyPart.getMediaType())) {
continue;
}
// if the type of dataBodyPart.getEntity() is BodyPartEntity, it is safe to read the part
// more than once. So we're not depriving the application of the data by consuming it here
String v = dataBodyPart.getValue();
if (callback != null) {
Map<String, List<String>> map = new HashMap<>();
for (BodyPart bodyPart : ret.getBodyParts()) {
if (!(bodyPart instanceof FormDataBodyPart)) {
continue;
}
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
if (!MediaTypes.typeEqual(MediaType.TEXT_PLAIN_TYPE, dataBodyPart.getMediaType())) {
continue;
}
// if the type of dataBodyPart.getEntity() is BodyPartEntity, it is safe to read the part
// more than once. So we're not depriving the application of the data by consuming it here
String v = dataBodyPart.getValue();

String name = dataBodyPart.getName();
List<String> values = map.get(name);
if (values == null) {
values = new ArrayList<>();
map.put(name, values);
}

String name = dataBodyPart.getName();
List<String> values = map.get(name);
if (values == null) {
values = new ArrayList<>();
map.put(name, values);
values.add(v);
}

values.add(v);
Flow<Void> flow = callback.apply(reqCtx, map);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (for MultiPartReaderClientSide/readFrom)");
reqCtx.getTraceSegment().effectivelyBlocked();
}
}
}

Flow<Void> flow = callback.apply(reqCtx, map);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (for MultiPartReaderClientSide/readFrom)");
reqCtx.getTraceSegment().effectivelyBlocked();
if (filenamesCallback != null) {
List<String> filenames = new ArrayList<>();
for (BodyPart bodyPart : ret.getBodyParts()) {
if (!(bodyPart instanceof FormDataBodyPart)) {
continue;
}
FormDataBodyPart dataBodyPart = (FormDataBodyPart) bodyPart;
FormDataContentDisposition cd = dataBodyPart.getFormDataContentDisposition();
if (cd == null) {
continue;
}
String filename = cd.getFileName();
if (filename != null && !filename.isEmpty()) {
filenames.add(filename);
}
}
if (!filenames.isEmpty()) {
Flow<Void> filenamesFlow = filenamesCallback.apply(reqCtx, filenames);
Flow.Action filenamesAction = filenamesFlow.getAction();
if (t == null && filenamesAction instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba =
(Flow.Action.RequestBlockingAction) filenamesAction;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (multipart file upload)");
reqCtx.getTraceSegment().effectivelyBlocked();
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import datadog.trace.api.gateway.RequestContextSlot;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -72,28 +73,88 @@ static void after(
CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
BiFunction<RequestContext, Object, Flow<Void>> callback =
cbp.getCallback(EVENTS.requestBodyProcessed());
if (callback == null) {
BiFunction<RequestContext, List<String>, Flow<Void>> filenamesCallback =
cbp.getCallback(EVENTS.requestFilesFilenames());
if (callback == null && filenamesCallback == null) {
return;
}

Map<String, List<String>> m = new HashMap<>();
for (Map.Entry<String, List<InputPart>> e : ret.getFormDataMap().entrySet()) {
List<String> strings = new ArrayList<>();
m.put(e.getKey(), strings);
for (InputPart inputPart : e.getValue()) {
strings.add(inputPart.getBodyAsString());
if (callback != null) {
Map<String, List<String>> m = new HashMap<>();
for (Map.Entry<String, List<InputPart>> e : ret.getFormDataMap().entrySet()) {
List<String> strings = new ArrayList<>();
m.put(e.getKey(), strings);
for (InputPart inputPart : e.getValue()) {
strings.add(inputPart.getBodyAsString());
}
}

Flow<Void> flow = callback.apply(reqCtx, m);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (for MultipartFormDataInput/readFrom)");
reqCtx.getTraceSegment().effectivelyBlocked();
}
}
}

Flow<Void> flow = callback.apply(reqCtx, m);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (for MultipartFormDataInput/readFrom)");
reqCtx.getTraceSegment().effectivelyBlocked();
if (filenamesCallback != null) {
List<String> filenames = new ArrayList<>();
// Reflection avoids a bytecode ref to MultivaluedMap (javax→jakarta in RESTEasy 6)
Method getHeadersMethod = null;
try {
getHeadersMethod = InputPart.class.getMethod("getHeaders");
} catch (NoSuchMethodException ignored) {
}
if (getHeadersMethod != null) {
for (Map.Entry<String, List<InputPart>> e : ret.getFormDataMap().entrySet()) {
for (InputPart inputPart : e.getValue()) {
List<String> cdHeaders;
try {
@SuppressWarnings("unchecked")
Map<String, List<String>> headers =
(Map<String, List<String>>) getHeadersMethod.invoke(inputPart);
cdHeaders = headers != null ? headers.get("Content-Disposition") : null;
} catch (Exception ignored) {
continue;
}
if (cdHeaders == null || cdHeaders.isEmpty()) {
continue;
}
String cd = cdHeaders.get(0);
for (String token : cd.split(";")) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse quoted Content-Disposition filenames

For RESTEasy uploads where the client sends a valid quoted filename containing a semicolon, for example filename="report;.php", this split treats the semicolon inside the quoted string as a parameter separator and reports only "report to server.request.body.filenames. That corrupts the filename signal and can make filename-based AppSec rules miss the actual uploaded name; parse the Content-Disposition parameters while respecting quoted strings instead of splitting the raw header on every semicolon.

Useful? React with 👍 / 👎.

token = token.trim();
if (token.startsWith("filename=")) {
String filename = token.substring(9).trim();
if (filename.startsWith("\"") && filename.endsWith("\"")) {
filename = filename.substring(1, filename.length() - 1);
}
if (!filename.isEmpty()) {
filenames.add(filename);
}
break;
}
}
}
}
}
if (!filenames.isEmpty()) {
Flow<Void> filenamesFlow = filenamesCallback.apply(reqCtx, filenames);
Flow.Action filenamesAction = filenamesFlow.getAction();
if (t == null && filenamesAction instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba =
(Flow.Action.RequestBlockingAction) filenamesAction;
BlockResponseFunction blockResponseFunction = reqCtx.getBlockResponseFunction();
if (blockResponseFunction != null) {
blockResponseFunction.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba);
t = new BlockingException("Blocked request (multipart file upload)");
reqCtx.getTraceSegment().effectivelyBlocked();
}
}
}
}
}
Expand Down
Loading