Skip to content

Commit 57d9846

Browse files
authored
Add LFI exploit prevention support (#7487)
Gives support to LFI exploit prevention by updating IAST Path traversal callsites
1 parent 3f5051e commit 57d9846

File tree

25 files changed

+737
-97
lines changed

25 files changed

+737
-97
lines changed

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ dd-java-agent/instrumentation/spring-security-5/ @DataDog/asm-java
5555
**/iast/ @DataDog/asm-java
5656
**/Iast*.java @DataDog/asm-java
5757
**/Iast*.groovy @DataDog/asm-java
58+
**/rasp/ @Datadog/asm-java
59+
**/*Rasp*.java @DataDog/asm-java
60+
**/*Rasp*.groovy @DataDog/asm-java
5861

5962
# @DataDog/data-jobs-monitoring
6063
dd-java-agent/instrumentation/spark/ @DataDog/data-jobs-monitoring

dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecConfigServiceImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_HEADER_FINGERPRINT;
1313
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_IP_BLOCKING;
1414
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_NETWORK_FINGERPRINT;
15+
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_RASP_LFI;
1516
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_RASP_SQLI;
1617
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_RASP_SSRF;
1718
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_REQUEST_BLOCKING;
@@ -116,6 +117,7 @@ private void subscribeConfigurationPoller() {
116117
if (tracerConfig.isAppSecRaspEnabled()) {
117118
capabilities |= CAPABILITY_ASM_RASP_SQLI;
118119
capabilities |= CAPABILITY_ASM_RASP_SSRF;
120+
capabilities |= CAPABILITY_ASM_RASP_LFI;
119121
}
120122
this.configurationPoller.addCapabilities(capabilities);
121123
}
@@ -359,6 +361,7 @@ public void close() {
359361
| CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE
360362
| CAPABILITY_ASM_RASP_SQLI
361363
| CAPABILITY_ASM_RASP_SSRF
364+
| CAPABILITY_ASM_RASP_LFI
362365
| CAPABILITY_ASM_AUTO_USER_INSTRUM_MODE
363366
| CAPABILITY_ENDPOINT_FINGERPRINT
364367
// TODO enable when usr.id and usr.session_id addresses are added

dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public class GatewayBridge {
8585
private volatile DataSubscriberInfo requestEndSubInfo;
8686
private volatile DataSubscriberInfo dbSqlQuerySubInfo;
8787
private volatile DataSubscriberInfo ioNetUrlSubInfo;
88+
private volatile DataSubscriberInfo ioFileSubInfo;
8889

8990
public GatewayBridge(
9091
SubscriptionService subscriptionService,
@@ -124,6 +125,7 @@ public void init() {
124125
subscriptionService.registerCallback(EVENTS.databaseConnection(), this::onDatabaseConnection);
125126
subscriptionService.registerCallback(EVENTS.databaseSqlQuery(), this::onDatabaseSqlQuery);
126127
subscriptionService.registerCallback(EVENTS.networkConnection(), this::onNetworkConnection);
128+
subscriptionService.registerCallback(EVENTS.fileLoaded(), this::onFileLoaded);
127129

128130
if (additionalIGEvents.contains(EVENTS.requestPathParams())) {
129131
subscriptionService.registerCallback(EVENTS.requestPathParams(), this::onRequestPathParams);
@@ -159,6 +161,31 @@ private Flow<Void> onNetworkConnection(RequestContext ctx_, String url) {
159161
}
160162
}
161163

164+
private Flow<Void> onFileLoaded(RequestContext ctx_, String path) {
165+
AppSecRequestContext ctx = ctx_.getData(RequestContextSlot.APPSEC);
166+
if (ctx == null) {
167+
return NoopFlow.INSTANCE;
168+
}
169+
while (true) {
170+
DataSubscriberInfo subInfo = ioFileSubInfo;
171+
if (subInfo == null) {
172+
subInfo = producerService.getDataSubscribers(KnownAddresses.IO_FS_FILE);
173+
ioFileSubInfo = subInfo;
174+
}
175+
if (subInfo == null || subInfo.isEmpty()) {
176+
return NoopFlow.INSTANCE;
177+
}
178+
DataBundle bundle =
179+
new MapDataBundle.Builder(CAPACITY_0_2).add(KnownAddresses.IO_FS_FILE, path).build();
180+
try {
181+
GatewayContext gwCtx = new GatewayContext(true, RuleType.LFI);
182+
return producerService.publishDataEvent(subInfo, ctx, bundle, gwCtx);
183+
} catch (ExpiredSubscriberInfoException e) {
184+
ioFileSubInfo = null;
185+
}
186+
}
187+
}
188+
162189
private Flow<Void> onDatabaseSqlQuery(RequestContext ctx_, String sql) {
163190
AppSecRequestContext ctx = ctx_.getData(RequestContextSlot.APPSEC);
164191
if (ctx == null) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ private static Collection<Address<?>> getUsedAddresses(PowerwafContext ctx) {
403403
addressList.add(KnownAddresses.DB_TYPE);
404404
addressList.add(KnownAddresses.DB_SQL_QUERY);
405405
addressList.add(KnownAddresses.IO_NET_URL);
406+
addressList.add(KnownAddresses.IO_FS_FILE);
406407

407408
return addressList;
408409
}

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/config/AppSecConfigServiceImplSpecification.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_EXCLUSION_DATA
2626
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_HEADER_FINGERPRINT
2727
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_IP_BLOCKING
2828
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_NETWORK_FINGERPRINT
29+
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_RASP_LFI
2930
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_RASP_SQLI
3031
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_RASP_SSRF
3132
import static datadog.remoteconfig.Capabilities.CAPABILITY_ASM_REQUEST_BLOCKING
@@ -269,6 +270,7 @@ class AppSecConfigServiceImplSpecification extends DDSpecification {
269270
| CAPABILITY_ASM_TRUSTED_IPS
270271
| CAPABILITY_ASM_RASP_SQLI
271272
| CAPABILITY_ASM_RASP_SSRF
273+
| CAPABILITY_ASM_RASP_LFI
272274
| CAPABILITY_ENDPOINT_FINGERPRINT
273275
// | CAPABILITY_ASM_SESSION_FINGERPRINT
274276
| CAPABILITY_ASM_NETWORK_FINGERPRINT
@@ -420,6 +422,7 @@ class AppSecConfigServiceImplSpecification extends DDSpecification {
420422
| CAPABILITY_ASM_TRUSTED_IPS
421423
| CAPABILITY_ASM_RASP_SQLI
422424
| CAPABILITY_ASM_RASP_SSRF
425+
| CAPABILITY_ASM_RASP_LFI
423426
| CAPABILITY_ENDPOINT_FINGERPRINT
424427
// | CAPABILITY_ASM_SESSION_FINGERPRINT
425428
| CAPABILITY_ASM_NETWORK_FINGERPRINT
@@ -492,6 +495,7 @@ class AppSecConfigServiceImplSpecification extends DDSpecification {
492495
| CAPABILITY_ASM_API_SECURITY_SAMPLE_RATE
493496
| CAPABILITY_ASM_RASP_SQLI
494497
| CAPABILITY_ASM_RASP_SSRF
498+
| CAPABILITY_ASM_RASP_LFI
495499
| CAPABILITY_ASM_AUTO_USER_INSTRUM_MODE
496500
| CAPABILITY_ENDPOINT_FINGERPRINT
497501
// | CAPABILITY_ASM_SESSION_FINGERPRINT

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class GatewayBridgeSpecification extends DDSpecification {
8282
BiConsumer<RequestContext, String> databaseConnectionCB
8383
BiFunction<RequestContext, String, Flow<Void>> databaseSqlQueryCB
8484
BiFunction<RequestContext, String, Flow<Void>> networkConnectionCB
85+
BiFunction<RequestContext, String, Flow<Void>> fileLoadedCB
8586

8687
void setup() {
8788
callInitAndCaptureCBs()
@@ -416,6 +417,7 @@ class GatewayBridgeSpecification extends DDSpecification {
416417
1 * ig.registerCallback(EVENTS.databaseConnection(), _) >> { databaseConnectionCB = it[1]; null }
417418
1 * ig.registerCallback(EVENTS.databaseSqlQuery(), _) >> { databaseSqlQueryCB = it[1]; null }
418419
1 * ig.registerCallback(EVENTS.networkConnection(), _) >> { networkConnectionCB = it[1]; null }
420+
1 * ig.registerCallback(EVENTS.fileLoaded(), _) >> { fileLoadedCB = it[1]; null }
419421
0 * ig.registerCallback(_, _)
420422

421423
bridge.init()
@@ -798,6 +800,26 @@ class GatewayBridgeSpecification extends DDSpecification {
798800
gatewayContext.isRasp == true
799801
}
800802

803+
void 'process file loaded'() {
804+
setup:
805+
final path = 'https://www.datadoghq.com/demo/file.txt'
806+
eventDispatcher.getDataSubscribers({ KnownAddresses.IO_FS_FILE in it }) >> nonEmptyDsInfo
807+
DataBundle bundle
808+
GatewayContext gatewayContext
809+
810+
when:
811+
Flow<?> flow = fileLoadedCB.apply(ctx, path)
812+
813+
then:
814+
1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >>
815+
{ a, b, db, gw -> bundle = db; gatewayContext = gw; NoopFlow.INSTANCE }
816+
bundle.get(KnownAddresses.IO_FS_FILE) == path
817+
flow.result == null
818+
flow.action == Flow.Action.Noop.INSTANCE
819+
gatewayContext.isTransient == true
820+
gatewayContext.isRasp == true
821+
}
822+
801823
void 'calls trace segment post processor'() {
802824
setup:
803825
AgentSpan span = Stub()
Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package datadog.trace.instrumentation.java.lang;
22

33
import datadog.trace.agent.tooling.csi.CallSite;
4+
import datadog.trace.api.appsec.RaspCallSites;
45
import datadog.trace.api.iast.IastCallSites;
56
import datadog.trace.api.iast.InstrumentationBridge;
67
import datadog.trace.api.iast.Sink;
@@ -11,20 +12,16 @@
1112
import javax.annotation.Nullable;
1213

1314
@Sink(VulnerabilityTypes.PATH_TRAVERSAL)
14-
@CallSite(spi = IastCallSites.class)
15+
@CallSite(
16+
spi = {IastCallSites.class, RaspCallSites.class},
17+
helpers = FileLoadedRaspHelper.class)
1518
public class FileCallSite {
1619

1720
@CallSite.Before("void java.io.File.<init>(java.lang.String)")
1821
public static void beforeConstructor(@CallSite.Argument @Nullable final String path) {
1922
if (path != null) { // new File(null) throws NPE
20-
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
21-
if (module != null) {
22-
try {
23-
module.onPathTraversal(path);
24-
} catch (final Throwable e) {
25-
module.onUnexpectedException("beforeConstructor threw", e);
26-
}
27-
}
23+
iastCallback(path);
24+
raspCallback(path);
2825
}
2926
}
3027

@@ -34,13 +31,8 @@ public static void beforeConstructor(
3431
@CallSite.Argument @Nullable final String child) {
3532
if (child != null) { // new File("abc", null) throws NPE
3633
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
37-
if (module != null) {
38-
try {
39-
module.onPathTraversal(parent, child);
40-
} catch (final Throwable e) {
41-
module.onUnexpectedException("beforeConstructor threw", e);
42-
}
43-
}
34+
iastCallback(parent, child);
35+
raspCallback(parent, child);
4436
}
4537
}
4638

@@ -50,27 +42,77 @@ public static void beforeConstructor(
5042
@CallSite.Argument @Nullable final String child) {
5143
if (child != null) { // new File(parent, null) throws NPE
5244
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
53-
if (module != null) {
54-
try {
55-
module.onPathTraversal(parent, child);
56-
} catch (final Throwable e) {
57-
module.onUnexpectedException("beforeConstructor threw", e);
58-
}
59-
}
45+
iastCallback(parent, child);
46+
raspCallback(parent, child);
6047
}
6148
}
6249

6350
@CallSite.Before("void java.io.File.<init>(java.net.URI)")
6451
public static void beforeConstructor(@CallSite.Argument @Nullable final URI uri) {
6552
if (uri != null) {
6653
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
67-
if (module != null) {
68-
try {
69-
module.onPathTraversal(uri);
70-
} catch (final Throwable e) {
71-
module.onUnexpectedException("beforeConstructor threw", e);
72-
}
54+
iastCallback(uri);
55+
raspCallback(uri);
56+
}
57+
}
58+
59+
private static void iastCallback(String path) {
60+
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
61+
if (module != null) {
62+
try {
63+
module.onPathTraversal(path);
64+
} catch (final Throwable e) {
65+
module.onUnexpectedException("beforeConstructor threw", e);
7366
}
7467
}
7568
}
69+
70+
private static void iastCallback(File parent, String child) {
71+
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
72+
if (module != null) {
73+
try {
74+
module.onPathTraversal(parent, child);
75+
} catch (final Throwable e) {
76+
module.onUnexpectedException("beforeConstructor threw", e);
77+
}
78+
}
79+
}
80+
81+
private static void iastCallback(String parent, String child) {
82+
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
83+
if (module != null) {
84+
try {
85+
module.onPathTraversal(parent, child);
86+
} catch (final Throwable e) {
87+
module.onUnexpectedException("beforeConstructor threw", e);
88+
}
89+
}
90+
}
91+
92+
private static void iastCallback(URI uri) {
93+
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
94+
if (module != null) {
95+
try {
96+
module.onPathTraversal(uri);
97+
} catch (final Throwable e) {
98+
module.onUnexpectedException("beforeConstructor threw", e);
99+
}
100+
}
101+
}
102+
103+
private static void raspCallback(File parent, String child) {
104+
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(parent, child);
105+
}
106+
107+
private static void raspCallback(String parent, String file) {
108+
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(parent, file);
109+
}
110+
111+
private static void raspCallback(String s) {
112+
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(s);
113+
}
114+
115+
private static void raspCallback(URI uri) {
116+
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(uri);
117+
}
76118
}
Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package datadog.trace.instrumentation.java.lang;
22

33
import datadog.trace.agent.tooling.csi.CallSite;
4+
import datadog.trace.api.appsec.RaspCallSites;
45
import datadog.trace.api.iast.IastCallSites;
56
import datadog.trace.api.iast.InstrumentationBridge;
67
import datadog.trace.api.iast.Sink;
@@ -9,20 +10,31 @@
910
import javax.annotation.Nullable;
1011

1112
@Sink(VulnerabilityTypes.PATH_TRAVERSAL)
12-
@CallSite(spi = IastCallSites.class)
13+
@CallSite(
14+
spi = {IastCallSites.class, RaspCallSites.class},
15+
helpers = FileLoadedRaspHelper.class)
1316
public class FileInputStreamCallSite {
1417

1518
@CallSite.Before("void java.io.FileInputStream.<init>(java.lang.String)")
1619
public static void beforeConstructor(@CallSite.Argument @Nullable final String path) {
1720
if (path != null) {
18-
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
19-
if (module != null) {
20-
try {
21-
module.onPathTraversal(path);
22-
} catch (final Throwable e) {
23-
module.onUnexpectedException("beforeConstructor threw", e);
24-
}
21+
iastCallback(path);
22+
raspCallback(path);
23+
}
24+
}
25+
26+
private static void iastCallback(String path) {
27+
final PathTraversalModule module = InstrumentationBridge.PATH_TRAVERSAL;
28+
if (module != null) {
29+
try {
30+
module.onPathTraversal(path);
31+
} catch (final Throwable e) {
32+
module.onUnexpectedException("beforeConstructor threw", e);
2533
}
2634
}
2735
}
36+
37+
private static void raspCallback(String path) {
38+
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(path);
39+
}
2840
}

0 commit comments

Comments
 (0)