Question
Why doesn't the sanitizer or security control I am using show up in the Contrast data flow?
Answer
This is expected behavior. To understand why, we need to understand what the Contrast vulnerability data flow shows. Consider this data flow from an XXE vulnerability
In most data flows, there are many "code events", but in this sample example, there are only three code events:
- Source Event (HTTP REQUEST BODY RETRIEVED): the application accessed attacker controlled data. In this case, the application accessed the request body as a stream.
- Propagation Event (DATA FLOWED FROM PARAMETER TO OBJECT): the application transferred the attacker controlled data from one Java object to another. In this case, the application created a new
org.xml.sax.InputSource
from the request body stream. - Sink Event (RULE VIOLATION DETECTED): the application sent attacker controlled data to a sensitive API without sanitization. In this case, the application passed the
org.xml.sax.InputSource
to a vulnerable XML parser'sparse
method.
Every time the Contrast agent detects one of these code events, it stores information about the event in order to create this data flow on TeamServer. One of the pieces of information that Contrast captures is the state of the stack in the current execution context at the time the code event occurs. We can see this stack trace by expanding one of the code events in the data flow:
Users need to be able to interpret this stack trace. Like any stack trace, it is a list of stack frames in the current execution context: the top-most frame is the currently executing function and the each subsequent frame is the function that called it. For a high-level, Java-specific explanation of how Assess data flow works under the hood, see this JUG talk.
With this understanding of Contrast vulnerability data flows, let's consider the original question in the context of this XXE vulnerability example. Let's assume that the user in this case has reported this vulnerability to be a false positive, because they are using a security control that Contrast does not recognize. They want to know why the security control doesn't show up in the data flow. Imagine the code looks like this (don't try to match this up exactly with the data flow in the screen shot, I've changed the code a bit for the sake of this example):
@Override
public void service(Request request, Response response) throws Exception {
InputStream is = request.getInputStream(); // source event generated by this call
String string = read(is); // propagation event generated by this call
verifyDoesNotContainExternalEntity(string); // this is our custom security control
final Book book = parser.parse(string); // propagation and sink events generated in this call
response.setStatus(204);
response.finish();
}
In this HTTP handler code, the custom security control verifyDoesNotContainExternalEntity(String)
throws an exception if the given String is XML that contains an external entity. In Contrast terminology, this is a validator and the user should add it as a custom security control so that Contrast knows that data analyzed by this method is safe from XXE. Without adding the custom security control, Contrast will not generate a code event from this method, so it will not show up in the data flow. However, this method will show up in the stack trace of another code event only if this method invokes an API that would cause Contrast to generate a code event for this data flow.
Let's consider two different implementations of the verifyDoesNotContainExternalEntity(String)
method. First, we'll consider an implementation that calls an API that generates a code event and therefore would cause the verifyDoesNotContainExternalEntity(String)
method to show up in a stack trace. The Java agent specifically (I don't think other agents do this) generates "text comparison" code events when the application performs some sort of interesting text comparison. These text comparison code events are inconsequential to detecting vulnerabilities; they exist to help users detect where they might need a custom security control (just like we're discussing here). The java.util.regex.Pattern.matcher(java.lang.CharSequence)
method is one of the APIs that will generate a text comparison code event. If the verifyDoesNotContainExternalEntity(String)
method call java.util.regex.Pattern.matcher(java.lang.CharSequence)
, then we will see a text comparison data flow event. The text comparison event contains the verifyDoesNotContainExternalEntity(String)
method in its stack trace, because verifyDoesNotContainExternalEntity(String)
called java.util.regex.Pattern.matcher(java.lang.CharSequence)
:
public static void verifyDoesNotContainExternalEntity(String string) {
if (XXE_VALIDATOR_REGEX.matcher(string).matches()) {
throw new SecurityException("Unsafe input detected");
}
}
The stack frames in the stack trace for the text comparison code event will look like:
java.base/java.lang.String.contains(String.java)
com.contrastsecurity.testapp.xxe.CustomSecurityControl.verifyDoesNotContainExternalEntity(CustomSecurityControl.java:104)
com.contrastsecurity.testapp.xxe.ParseBookHandler.service(ParseBookHandler.java:27)
org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:222)
org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:565)
org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:545)
java.base/java.lang.Thread.run()
And the data flow event will look like:
Now let's consider an alternative implementation of the verifyDoesNotContainExternalEntity(String)
security control which does not cause a code event to be generated for this data flow. In this implementation, we're going to perform the same text comparison, but this implementation only considers the first 500 characters of the string as a performance optimization:
public static void verifyDoesNotContainExternalEntity(String string) {
int limit = Math.min(string.length(), 500); // attacker data propagates to substring, because substring is a new object
String substring = string.substring(0, limit - 1);
if (XXE_VALIDATOR_REGEX.matcher(substring).matches()) {
throw new SecurityException("Unsafe input detected");
}
} // substring is garbage collected after the method concludes: it doesn't cause a security vulnerability and it's not part of the data flow reported for this XXE vulnerability
The text comparison happens on the object generated by substring
and this object is not part of the vulnerable data flow, so it does not end up in the XXE vulnerability data flow. In this example, there will not be a code event that shows the verifyDoesNotContainExternalEntity(String)
method in its stack trace.