Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ public interface KnownAddresses {
/** The representation of opened file on the filesystem */
Address<String> IO_FS_FILE = new Address<>("server.io.fs.file");

/** The representation of a file being written on the filesystem */
Address<String> IO_FS_FILE_WRITE = new Address<>("server.io.fs.file_write");

/** The database type (ex: mysql, postgresql, sqlite) */
Address<String> DB_TYPE = new Address<>("server.db.system");

Expand Down Expand Up @@ -240,6 +243,8 @@ static Address<?> forName(String name) {
return IO_NET_RESPONSE_BODY;
case "server.io.fs.file":
return IO_FS_FILE;
case "server.io.fs.file_write":
return IO_FS_FILE_WRITE;
case "server.db.system":
return DB_TYPE;
case "server.db.statement":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public class GatewayBridge {
private volatile DataSubscriberInfo httpClientRequestSubInfo;
private volatile DataSubscriberInfo httpClientResponseSubInfo;
private volatile DataSubscriberInfo ioFileSubInfo;
private volatile DataSubscriberInfo ioFileWriteSubInfo;
private volatile DataSubscriberInfo sessionIdSubInfo;
private volatile DataSubscriberInfo userIdSubInfo;
private final ConcurrentHashMap<String, DataSubscriberInfo> loginEventSubInfo =
Expand Down Expand Up @@ -188,6 +189,7 @@ public void init() {
subscriptionService.registerCallback(EVENTS.httpClientRequest(), this::onHttpClientRequest);
subscriptionService.registerCallback(EVENTS.httpClientResponse(), this::onHttpClientResponse);
subscriptionService.registerCallback(EVENTS.fileLoaded(), this::onFileLoaded);
subscriptionService.registerCallback(EVENTS.fileWritten(), this::onFileWritten);
subscriptionService.registerCallback(EVENTS.requestSession(), this::onRequestSession);
subscriptionService.registerCallback(EVENTS.execCmd(), this::onExecCmd);
subscriptionService.registerCallback(EVENTS.shellCmd(), this::onShellCmd);
Expand Down Expand Up @@ -548,6 +550,36 @@ private Flow<Void> onFileLoaded(RequestContext ctx_, String path) {
}
}

private Flow<Void> onFileWritten(RequestContext ctx_, String path) {
AppSecRequestContext ctx = ctx_.getData(RequestContextSlot.APPSEC);
if (ctx == null) {
return NoopFlow.INSTANCE;
}
while (true) {
DataSubscriberInfo subInfo = ioFileWriteSubInfo;
if (subInfo == null) {
subInfo =
producerService.getDataSubscribers(
KnownAddresses.IO_FS_FILE, KnownAddresses.IO_FS_FILE_WRITE);
ioFileWriteSubInfo = subInfo;
}
if (subInfo == null || subInfo.isEmpty()) {
return NoopFlow.INSTANCE;
}
DataBundle bundle =
new MapDataBundle.Builder(CAPACITY_0_2)
.add(KnownAddresses.IO_FS_FILE, path)
.add(KnownAddresses.IO_FS_FILE_WRITE, path)
.build();
try {
GatewayContext gwCtx = new GatewayContext(true, RuleType.LFI);
return producerService.publishDataEvent(subInfo, ctx, bundle, gwCtx);
} catch (ExpiredSubscriberInfoException e) {
ioFileWriteSubInfo = null;
}
}
}

private Flow<Void> onRequestFilesFilenames(RequestContext ctx_, List<String> filenames) {
AppSecRequestContext ctx = ctx_.getData(RequestContextSlot.APPSEC);
if (ctx == null || filenames == null || filenames.isEmpty()) {
Expand Down
69 changes: 69 additions & 0 deletions dd-java-agent/appsec/src/main/resources/default_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5457,6 +5457,75 @@
],
"transformers": []
},
{
"id": "dog-920-110",
"name": "Zipslip Attack - Unsafe Zip extraction",
"tags": {
"type": "http_protocol_violation",
"category": "attack_attempt",
"cwe": "502",
"capec": "1000/152/586",
"confidence": "0",
"module": "waf"
},
"conditions": [
{
"parameters": {
"inputs": [
{
"address": "server.request.body.filenames"
},
{
"address": "server.request.headers.no_cookies",
"key_path": [
"x-filename"
]
},
{
"address": "server.request.headers.no_cookies",
"key_path": [
"x_filename"
]
},
{
"address": "server.request.headers.no_cookies",
"key_path": [
"x.filename"
]
},
{
"address": "server.request.headers.no_cookies",
"key_path": [
"x-file-name"
]
}
],
"regex": "\\.zip$",
"options": {
"case_sensitive": true,
"min_length": 5
}
},
"operator": "match_regex"
},
{
"parameters": {
"inputs": [
{
"address": "server.io.fs.file_write"
}
],
"regex": "(?:^|[/\\\\])\\.\\.[/\\\\]",
"options": {
"case_sensitive": true,
"min_length": 4
}
},
"operator": "match_regex"
}
],
"transformers": []
},
{
"id": "dog-931-001",
"name": "RFI: URL Payload to well known RFI target",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class KnownAddressesSpecificationForkedTest extends Specification {
'server.io.net.response.headers',
'server.io.net.response.body',
'server.io.fs.file',
'server.io.fs.file_write',
'server.sys.exec.cmd',
'server.sys.shell.cmd',
'waf.context.processor'
Expand All @@ -57,7 +58,7 @@ class KnownAddressesSpecificationForkedTest extends Specification {

void 'number of known addresses is expected number'() {
expect:
Address.instanceCount() == 45
Address.instanceCount() == 46
KnownAddresses.WAF_CONTEXT_PROCESSOR.serial == Address.instanceCount() - 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class GatewayBridgeSpecification extends DDSpecification {
BiFunction<RequestContext, HttpClientResponse, Flow<Void>> httpClientResponseCB
BiFunction<RequestContext, Long, Flow<Void>> httpClientSamplingCB
BiFunction<RequestContext, String, Flow<Void>> fileLoadedCB
BiFunction<RequestContext, String, Flow<Void>> fileWrittenCB
BiFunction<RequestContext, List<String>, Flow<Void>> requestFilesFilenamesCB
BiFunction<RequestContext, String, Flow<Void>> requestSessionCB
BiFunction<RequestContext, String[], Flow<Void>> execCmdCB
Expand Down Expand Up @@ -536,6 +537,9 @@ class GatewayBridgeSpecification extends DDSpecification {
1 * ig.registerCallback(EVENTS.fileLoaded(), _) >> {
fileLoadedCB = it[1]; null
}
1 * ig.registerCallback(EVENTS.fileWritten(), _) >> {
fileWrittenCB = it[1]; null
}
1 * ig.registerCallback(EVENTS.requestSession(), _) >> {
requestSessionCB = it[1]; null
}
Expand Down Expand Up @@ -1082,6 +1086,30 @@ class GatewayBridgeSpecification extends DDSpecification {
gatewayContext.isRasp == true
}

void 'process file written'() {
setup:
final path = '/tmp/output.txt'
eventDispatcher.getDataSubscribers({
KnownAddresses.IO_FS_FILE in it && KnownAddresses.IO_FS_FILE_WRITE in it
}) >> nonEmptyDsInfo
DataBundle bundle
GatewayContext gatewayContext

when:
Flow<?> flow = fileWrittenCB.apply(ctx, path)

then:
1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> {
a, b, db, gw -> bundle = db; gatewayContext = gw; NoopFlow.INSTANCE
}
bundle.get(KnownAddresses.IO_FS_FILE) == path
bundle.get(KnownAddresses.IO_FS_FILE_WRITE) == path
flow.result == null
flow.action == Flow.Action.Noop.INSTANCE
gatewayContext.isTransient == true
gatewayContext.isRasp == true
}

void 'process request files filenames'() {
setup:
final filenames = ['malicious.php', 'document.pdf']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
@Sink(VulnerabilityTypes.PATH_TRAVERSAL)
@CallSite(
spi = {IastCallSites.class, RaspCallSites.class},
helpers = FileLoadedRaspHelper.class)
helpers = FileIORaspHelper.class)
public class FileCallSite {

@CallSite.Before("void java.io.File.<init>(java.lang.String)")
Expand Down Expand Up @@ -101,18 +101,18 @@ private static void iastCallback(URI uri) {
}

private static void raspCallback(File parent, String child) {
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(parent, child);
FileIORaspHelper.INSTANCE.beforeFileLoaded(parent, child);
}

private static void raspCallback(String parent, String file) {
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(parent, file);
FileIORaspHelper.INSTANCE.beforeFileLoaded(parent, file);
}

private static void raspCallback(String s) {
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(s);
FileIORaspHelper.INSTANCE.beforeFileLoaded(s);
}

private static void raspCallback(URI uri) {
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(uri);
FileIORaspHelper.INSTANCE.beforeFileLoaded(uri);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import datadog.appsec.api.blocking.BlockingException;
import datadog.trace.api.Config;
import datadog.trace.api.gateway.BlockResponseFunction;
import datadog.trace.api.gateway.EventType;
import datadog.trace.api.gateway.Flow;
import datadog.trace.api.gateway.RequestContext;
import datadog.trace.api.gateway.RequestContextSlot;
Expand All @@ -19,13 +20,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FileLoadedRaspHelper {
public class FileIORaspHelper {

public static FileLoadedRaspHelper INSTANCE = new FileLoadedRaspHelper();
public static FileIORaspHelper INSTANCE = new FileIORaspHelper();

private static final Logger LOGGER = LoggerFactory.getLogger(FileLoadedRaspHelper.class);
private static final Logger LOGGER = LoggerFactory.getLogger(FileIORaspHelper.class);

private FileLoadedRaspHelper() {
private FileIORaspHelper() {
// prevent instantiation
}

Expand Down Expand Up @@ -93,16 +94,24 @@ public void beforeFileLoaded(@Nullable final File parent, @Nonnull final String
}

public void beforeFileLoaded(@Nonnull final String path) {
invokeRaspCallback(EVENTS.fileLoaded(), path);
}

public void beforeFileWritten(@Nonnull final String path) {
invokeRaspCallback(EVENTS.fileWritten(), path);
}

private void invokeRaspCallback(
EventType<BiFunction<RequestContext, String, Flow<Void>>> eventType,
@Nonnull final String path) {
if (!Config.get().isAppSecRaspEnabled()) {
return;
}
try {
final BiFunction<RequestContext, String, Flow<Void>> fileLoadedCallback =
AgentTracer.get()
.getCallbackProvider(RequestContextSlot.APPSEC)
.getCallback(EVENTS.fileLoaded());
final BiFunction<RequestContext, String, Flow<Void>> callback =
AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC).getCallback(eventType);

if (fileLoadedCallback == null) {
if (callback == null) {
return;
}

Expand All @@ -116,7 +125,7 @@ public void beforeFileLoaded(@Nonnull final String path) {
return;
}

Flow<Void> flow = fileLoadedCallback.apply(ctx, path);
Flow<Void> flow = callback.apply(ctx, path);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
BlockResponseFunction brf = ctx.getBlockResponseFunction();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@Sink(VulnerabilityTypes.PATH_TRAVERSAL)
@CallSite(
spi = {IastCallSites.class, RaspCallSites.class},
helpers = FileLoadedRaspHelper.class)
helpers = FileIORaspHelper.class)
public class FileInputStreamCallSite {

@CallSite.Before("void java.io.FileInputStream.<init>(java.lang.String)")
Expand All @@ -35,6 +35,6 @@ private static void iastCallback(String path) {
}

private static void raspCallback(String path) {
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(path);
FileIORaspHelper.INSTANCE.beforeFileLoaded(path);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@Sink(VulnerabilityTypes.PATH_TRAVERSAL)
@CallSite(
spi = {IastCallSites.class, RaspCallSites.class},
helpers = FileLoadedRaspHelper.class)
helpers = FileIORaspHelper.class)
public class FileOutputStreamCallSite {

@CallSite.Before("void java.io.FileOutputStream.<init>(java.lang.String)")
Expand All @@ -36,6 +36,6 @@ private static void iastCallback(String path) {
}

private static void raspCallback(String path) {
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(path);
FileIORaspHelper.INSTANCE.beforeFileWritten(path);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@Sink(VulnerabilityTypes.PATH_TRAVERSAL)
@CallSite(
spi = {IastCallSites.class, RaspCallSites.class},
helpers = FileLoadedRaspHelper.class)
helpers = FileIORaspHelper.class)
public class PathCallSite {

@CallSite.Before("java.nio.file.Path java.nio.file.Path.resolve(java.lang.String)")
Expand All @@ -36,6 +36,6 @@ private static void iastCallback(String other) {
}

private static void raspCallback(String other) {
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(other);
FileIORaspHelper.INSTANCE.beforeFileLoaded(other);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@Sink(VulnerabilityTypes.PATH_TRAVERSAL)
@CallSite(
spi = {IastCallSites.class, RaspCallSites.class},
helpers = FileLoadedRaspHelper.class)
helpers = FileIORaspHelper.class)
public class PathsCallSite {

@CallSite.Before(
Expand Down Expand Up @@ -58,10 +58,10 @@ private static void iastCallback(String first, String[] more) {
}

private static void raspCallback(String first, String[] more) {
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(first, more);
FileIORaspHelper.INSTANCE.beforeFileLoaded(first, more);
}

private static void raspCallback(URI uri) {
FileLoadedRaspHelper.INSTANCE.beforeFileLoaded(uri);
FileIORaspHelper.INSTANCE.beforeFileLoaded(uri);
}
}
Loading
Loading