Skip to content

Commit 8b2bb8e

Browse files
authored
IAST security controls: handling custom validation and sanitization methods (#7997)
What Does This Do Add secure marks to custom input validators and sanitisers via env variable RFC (Milestone 0) Motivation IAST frequently identifies vulnerabilities that are reported despite the fact that the application executes custom validation mechanisms before reaching those points, effectively preventing any exploitation. As a result, security teams receive unnecessary alerts about potential vulnerabilities that are not actually exploitable. There is a need for a mechanism to recognise when these custom validation methods are in place, to avoid false positives.
1 parent 9e342ef commit 8b2bb8e

File tree

28 files changed

+1297
-11
lines changed

28 files changed

+1297
-11
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ public void execute() {
529529

530530
installDatadogTracer(initTelemetry, scoClass, sco);
531531
maybeStartAppSec(scoClass, sco);
532-
maybeStartIast(scoClass, sco);
532+
maybeStartIast(instrumentation, scoClass, sco);
533533
maybeStartCiVisibility(instrumentation, scoClass, sco);
534534
maybeStartLogsIntake(scoClass, sco);
535535
// start debugger before remote config to subscribe to it before starting to poll
@@ -820,14 +820,14 @@ private static boolean isSupportedAppSecArch() {
820820
return true;
821821
}
822822

823-
private static void maybeStartIast(Class<?> scoClass, Object o) {
823+
private static void maybeStartIast(Instrumentation instrumentation, Class<?> scoClass, Object o) {
824824
if (iastEnabled || !iastFullyDisabled) {
825825

826826
StaticEventLogger.begin("IAST");
827827

828828
try {
829829
SubscriptionService ss = AgentTracer.get().getSubscriptionService(RequestContextSlot.IAST);
830-
startIast(ss, scoClass, o);
830+
startIast(instrumentation, ss, scoClass, o);
831831
} catch (Exception e) {
832832
log.error("Error starting IAST subsystem", e);
833833
}
@@ -836,12 +836,13 @@ private static void maybeStartIast(Class<?> scoClass, Object o) {
836836
}
837837
}
838838

839-
private static void startIast(SubscriptionService ss, Class<?> scoClass, Object sco) {
839+
private static void startIast(
840+
Instrumentation instrumentation, SubscriptionService ss, Class<?> scoClass, Object sco) {
840841
try {
841842
final Class<?> appSecSysClass = AGENT_CLASSLOADER.loadClass("com.datadog.iast.IastSystem");
842843
final Method iastInstallerMethod =
843-
appSecSysClass.getMethod("start", SubscriptionService.class);
844-
iastInstallerMethod.invoke(null, ss);
844+
appSecSysClass.getMethod("start", Instrumentation.class, SubscriptionService.class);
845+
iastInstallerMethod.invoke(null, instrumentation, ss);
845846
} catch (final Throwable e) {
846847
log.warn("Not starting IAST subsystem", e);
847848
}

dd-java-agent/agent-iast/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ dependencies {
5050
implementation project(':internal-api')
5151
implementation project(':internal-api:internal-api-9')
5252
implementation libs.moshi
53+
implementation libs.bundles.asm
5354

5455
testFixturesApi project(':dd-java-agent:testing')
5556
testFixturesApi project(':utils:test-utils')

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.datadog.iast.propagation.CodecModuleImpl;
99
import com.datadog.iast.propagation.PropagationModuleImpl;
1010
import com.datadog.iast.propagation.StringModuleImpl;
11+
import com.datadog.iast.securitycontrol.IastSecurityControlTransformer;
1112
import com.datadog.iast.sink.ApplicationModuleImpl;
1213
import com.datadog.iast.sink.CommandInjectionModuleImpl;
1314
import com.datadog.iast.sink.HardcodedSecretModuleImpl;
@@ -47,12 +48,17 @@
4748
import datadog.trace.api.iast.IastContext;
4849
import datadog.trace.api.iast.IastModule;
4950
import datadog.trace.api.iast.InstrumentationBridge;
51+
import datadog.trace.api.iast.securitycontrol.SecurityControl;
52+
import datadog.trace.api.iast.securitycontrol.SecurityControlFormatter;
5053
import datadog.trace.api.iast.telemetry.IastMetricCollector;
5154
import datadog.trace.api.iast.telemetry.Verbosity;
5255
import datadog.trace.util.AgentTaskScheduler;
5356
import datadog.trace.util.stacktrace.StackWalkerFactory;
57+
import java.lang.instrument.Instrumentation;
5458
import java.lang.reflect.Constructor;
5559
import java.lang.reflect.UndeclaredThrowableException;
60+
import java.util.List;
61+
import java.util.Map;
5662
import java.util.function.BiFunction;
5763
import java.util.function.Supplier;
5864
import java.util.stream.Stream;
@@ -67,11 +73,21 @@ public class IastSystem {
6773
public static Verbosity VERBOSITY = Verbosity.OFF;
6874

6975
public static void start(final SubscriptionService ss) {
70-
start(ss, null);
76+
start(null, ss, null);
77+
}
78+
79+
public static void start(final SubscriptionService ss, OverheadController overheadController) {
80+
start(null, ss, overheadController);
81+
}
82+
83+
public static void start(final Instrumentation instrumentation, final SubscriptionService ss) {
84+
start(instrumentation, ss, null);
7185
}
7286

7387
public static void start(
74-
final SubscriptionService ss, @Nullable OverheadController overheadController) {
88+
@Nullable final Instrumentation instrumentation,
89+
final SubscriptionService ss,
90+
@Nullable OverheadController overheadController) {
7591
final Config config = Config.get();
7692
final ProductActivation iast = config.getIastActivation();
7793
final ProductActivation appSec = config.getAppSecActivation();
@@ -106,9 +122,23 @@ public static void start(
106122
registerRequestEndedCallback(ss, addTelemetry, dependencies);
107123
registerHeadersCallback(ss);
108124
registerGrpcServerRequestMessageCallback(ss);
125+
maybeApplySecurityControls(instrumentation);
109126
LOGGER.debug("IAST started");
110127
}
111128

129+
private static void maybeApplySecurityControls(@Nullable Instrumentation instrumentation) {
130+
if (Config.get().getIastSecurityControlsConfiguration() == null || instrumentation == null) {
131+
return;
132+
}
133+
Map<String, List<SecurityControl>> securityControls =
134+
SecurityControlFormatter.format(Config.get().getIastSecurityControlsConfiguration());
135+
if (securityControls == null) {
136+
LOGGER.warn("No security controls to apply");
137+
return;
138+
}
139+
instrumentation.addTransformer(new IastSecurityControlTransformer(securityControls), true);
140+
}
141+
112142
private static IastContext.Provider contextProvider(
113143
final ProductActivation iast, final boolean global) {
114144
if (iast != FULLY_ENABLED) {

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/PropagationModuleImpl.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,22 @@ public Taintable.Source findSource(
652652
return highestPrioritySource(to, target);
653653
}
654654

655+
@Override
656+
public void markIfTainted(@Nullable Object target, int mark) {
657+
if (target == null) {
658+
return;
659+
}
660+
final IastContext ctx = IastContext.Provider.get();
661+
if (ctx == null) {
662+
return;
663+
}
664+
TaintedObjects taintedObjects = ctx.getTaintedObjects();
665+
TaintedObject taintedObject = taintedObjects.get(target);
666+
if (taintedObject != null) {
667+
taintedObject.setRanges(markRanges(taintedObject.getRanges(), mark));
668+
}
669+
}
670+
655671
@Override
656672
public boolean isTainted(@Nullable final Object target) {
657673
if (target == null) {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.datadog.iast.securitycontrol;
2+
3+
import datadog.trace.api.iast.securitycontrol.SecurityControl;
4+
import org.objectweb.asm.MethodVisitor;
5+
import org.objectweb.asm.Opcodes;
6+
import org.objectweb.asm.Type;
7+
8+
public class AbstractMethodAdapter extends MethodVisitor {
9+
10+
private static final String HELPER =
11+
"datadog/trace/api/iast/securitycontrol/SecurityControlHelper";
12+
private static final String METHOD = "setSecureMarks";
13+
private static final String DESCRIPTOR = "(Ljava/lang/Object;I)V";
14+
protected final MethodVisitor mv;
15+
protected final SecurityControl securityControl;
16+
protected final int accessFlags;
17+
protected final Type method;
18+
19+
public AbstractMethodAdapter(
20+
final MethodVisitor mv,
21+
final SecurityControl securityControl,
22+
final int accessFlags,
23+
final Type method) {
24+
super(Opcodes.ASM9, mv);
25+
this.mv = mv;
26+
this.securityControl = securityControl;
27+
this.accessFlags = accessFlags;
28+
this.method = method;
29+
}
30+
31+
protected boolean isStatic() {
32+
return (accessFlags & Opcodes.ACC_STATIC) > 0;
33+
}
34+
35+
/**
36+
* This method loads the current secure marks defined in the control and then calls the helper
37+
* method
38+
*/
39+
protected void loadMarksAndCallHelper() {
40+
// Load the marks from securityControl onto the stack
41+
mv.visitLdcInsn(securityControl.getMarks());
42+
// Insert the call to setSecureMarks with the return value and marks as parameters
43+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, HELPER, METHOD, DESCRIPTOR, false);
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.datadog.iast.securitycontrol;
2+
3+
import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
4+
import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
5+
6+
import datadog.trace.api.iast.securitycontrol.SecurityControl;
7+
import java.lang.instrument.ClassFileTransformer;
8+
import java.util.List;
9+
import java.util.Map;
10+
import javax.annotation.Nullable;
11+
import org.objectweb.asm.ClassReader;
12+
import org.objectweb.asm.ClassVisitor;
13+
import org.objectweb.asm.ClassWriter;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
17+
public class IastSecurityControlTransformer implements ClassFileTransformer {
18+
19+
private static final Logger LOGGER =
20+
LoggerFactory.getLogger(IastSecurityControlTransformer.class);
21+
22+
private final Map<String, List<SecurityControl>> securityControls;
23+
24+
public IastSecurityControlTransformer(Map<String, List<SecurityControl>> securityControls) {
25+
this.securityControls = securityControls;
26+
}
27+
28+
@Override
29+
@Nullable
30+
public byte[] transform(
31+
ClassLoader loader,
32+
String className,
33+
Class<?> classBeingRedefined,
34+
java.security.ProtectionDomain protectionDomain,
35+
byte[] classfileBuffer) {
36+
List<SecurityControl> match = securityControls.get(className);
37+
if (match == null || match.isEmpty()) {
38+
return null; // Do not transform classes that do not have a security control
39+
}
40+
try {
41+
ClassReader cr = new ClassReader(classfileBuffer);
42+
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
43+
ClassVisitor cv = new SecurityControlMethodClassVisitor(cw, match);
44+
cr.accept(cv, SKIP_DEBUG | SKIP_FRAMES);
45+
return cw.toByteArray();
46+
} catch (Throwable e) {
47+
LOGGER.warn("Failed to transform class: {}", className, e);
48+
return null;
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.datadog.iast.securitycontrol;
2+
3+
import static com.datadog.iast.securitycontrol.SecurityControlMethodClassVisitor.LOGGER;
4+
import static com.datadog.iast.securitycontrol.SecurityControlMethodClassVisitor.isPrimitive;
5+
6+
import datadog.trace.api.iast.securitycontrol.SecurityControl;
7+
import org.objectweb.asm.MethodVisitor;
8+
import org.objectweb.asm.Opcodes;
9+
import org.objectweb.asm.Type;
10+
11+
public class InputValidatorMethodAdapter extends AbstractMethodAdapter {
12+
13+
private final boolean isStatic;
14+
15+
public InputValidatorMethodAdapter(
16+
final MethodVisitor mv,
17+
final SecurityControl securityControl,
18+
final int accessFlags,
19+
final Type method) {
20+
super(mv, securityControl, accessFlags, method);
21+
isStatic = isStatic();
22+
}
23+
24+
@Override
25+
public void visitCode() {
26+
super.visitCode();
27+
processInputValidator();
28+
}
29+
30+
private void processInputValidator() {
31+
boolean allParameters = securityControl.getParametersToMark() == null;
32+
Type[] types = method.getArgumentTypes();
33+
int parametersCount = 0;
34+
for (int i = 0; i < types.length; i++) {
35+
Type type = types[i];
36+
boolean isPrimitive = isPrimitive(type);
37+
if (allParameters) {
38+
if (!isPrimitive) {
39+
callInputValidation(parametersCount);
40+
}
41+
} else if (securityControl.getParametersToMark().get(i)) {
42+
if (isPrimitive) {
43+
LOGGER.warn(
44+
"Input validators should not be used on primitive types. Parameter {} with type {} .Security control: {}",
45+
i,
46+
type.getClassName(),
47+
securityControl);
48+
} else {
49+
callInputValidation(parametersCount);
50+
}
51+
}
52+
parametersCount += type.getSize();
53+
}
54+
}
55+
56+
private void callInputValidation(int i) {
57+
// Load the parameter onto the stack
58+
mv.visitVarInsn(
59+
Opcodes.ALOAD,
60+
isStatic ? i : i + 1); // instance methods have this as first element in the stack
61+
loadMarksAndCallHelper();
62+
}
63+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.datadog.iast.securitycontrol;
2+
3+
import datadog.trace.api.iast.securitycontrol.SecurityControl;
4+
import org.objectweb.asm.MethodVisitor;
5+
import org.objectweb.asm.Opcodes;
6+
import org.objectweb.asm.Type;
7+
8+
public class SanitizerMethodAdapter extends AbstractMethodAdapter {
9+
10+
public SanitizerMethodAdapter(
11+
final MethodVisitor mv,
12+
final SecurityControl securityControl,
13+
final int accessFlags,
14+
final Type method) {
15+
super(mv, securityControl, accessFlags, method);
16+
}
17+
18+
@Override
19+
public void visitInsn(int opcode) {
20+
if (opcode == Opcodes.ARETURN) {
21+
processSanitizer();
22+
}
23+
super.visitInsn(opcode);
24+
}
25+
26+
private void processSanitizer() {
27+
// Duplicate the return value on the stack
28+
mv.visitInsn(Opcodes.DUP);
29+
loadMarksAndCallHelper();
30+
}
31+
}

0 commit comments

Comments
 (0)