Skip to content

Commit

Permalink
Redirect blocks (#195)
Browse files Browse the repository at this point in the history
  • Loading branch information
estringana authored Feb 14, 2023
1 parent c1b1451 commit 2f2e39e
Show file tree
Hide file tree
Showing 17 changed files with 363 additions and 34 deletions.
87 changes: 69 additions & 18 deletions src/extension/commands_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,9 @@ static dd_result _dd_command_exec(dd_conn *nonnull conn, bool check_cred,
}

// in
bool should_block;
dd_result res;
{
dd_imsg imsg = {0};
dd_result res;
if (check_cred) {
res = _imsg_recv_cred(&imsg, conn);
} else {
Expand Down Expand Up @@ -174,22 +173,18 @@ static dd_result _dd_command_exec(dd_conn *nonnull conn, bool check_cred,

return dd_error;
}
if (res != dd_success && res != dd_should_block) {
if (res != dd_success && res != dd_should_block &&
res != dd_should_redirect) {
mlog(dd_log_warning, "Processing for command %.*s failed: %s",
NAME_L, dd_result_to_string(res));
return res;
}
should_block = res == dd_should_block;
}

if (should_block) {
mlog(dd_log_info, "%.*s succeed and told to block", NAME_L);
return dd_should_block;
}

mlog(dd_log_debug, "%.*s succeed. Not blocking", NAME_L);
mlog(dd_log_info, "%.*s succeed and told to %s", NAME_L,
dd_result_to_string(res));

return dd_success;
return res;
}

dd_result ATTR_WARN_UNUSED dd_command_exec(dd_conn *nonnull conn,
Expand Down Expand Up @@ -311,7 +306,7 @@ static void _set_appsec_span_data(mpack_node_t node);

static void _command_process_block_parameters(mpack_node_t root)
{
int status_code = DEFAULT_RESPONSE_CODE;
int status_code = DEFAULT_BLOCKING_RESPONSE_CODE;
dd_response_type type = DEFAULT_RESPONSE_TYPE;

int expected_nodes = 2;
Expand Down Expand Up @@ -365,13 +360,63 @@ static void _command_process_block_parameters(mpack_node_t root)
}
}

dd_set_response_code_and_type(status_code, type);
dd_set_block_code_and_type(status_code, type);
}

static void _command_process_redirect_parameters(mpack_node_t root)
{
int status_code = DEFAULT_REDIRECTION_RESPONSE_CODE;
zend_string *location = NULL;

int expected_nodes = 2;
size_t count = mpack_node_map_count(root);
for (size_t i = 0; i < count && expected_nodes > 0; i++) {
mpack_node_t key = mpack_node_map_key_at(root, i);
mpack_node_t value = mpack_node_map_value_at(root, i);

if (mpack_node_type(key) != mpack_type_str) {
mlog(dd_log_warning,
"Failed to add response parameter: invalid type for key");
continue;
}
if (mpack_node_type(value) != mpack_type_str) {
mlog(dd_log_warning,
"Failed to add response parameter: invalid type for value");
continue;
}

if (dd_mpack_node_lstr_eq(key, "status_code")) {
size_t code_len = mpack_node_strlen(value);
if (code_len != 3) {
mlog(dd_log_warning, "Invalid http status code received %.*s",
(int)code_len, mpack_node_str(value));
continue;
}

char code_str[4] = {0};
memcpy(code_str, mpack_node_str(value), 3);

const int base = 10;
long parsed_value = strtol(code_str, NULL, base);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
if (parsed_value >= 300 && parsed_value < 400) {
status_code = (int)parsed_value;
}
--expected_nodes;
} else if (dd_mpack_node_lstr_eq(key, "location")) {
size_t location_len = mpack_node_strlen(value);
location = zend_string_init(mpack_node_str(value), location_len, 0);
--expected_nodes;
}
}

dd_set_redirect_code_and_location(status_code, location);
}

dd_result dd_command_proc_resp_verd_span_data(
mpack_node_t root, ATTR_UNUSED void *unspecnull ctx)
{
// expected: ['ok' / 'record' / 'block']
// expected: ['ok' / 'record' / 'block' / 'redirect']
mpack_node_t verdict = mpack_node_array_at(root, 0);
if (mlog_should_log(dd_log_debug)) {
const char *verd_str = mpack_node_str(verdict);
Expand All @@ -383,14 +428,20 @@ dd_result dd_command_proc_resp_verd_span_data(
verd_str);
}

bool should_block = dd_mpack_node_lstr_eq(verdict, "block");
dd_result res = dd_success;
// Parse parameters
if (should_block) {
if (dd_mpack_node_lstr_eq(verdict, "block")) {
res = dd_should_block;
_command_process_block_parameters(mpack_node_array_at(root, 1));
dd_tags_add_blocked();
} else if (dd_mpack_node_lstr_eq(verdict, "redirect")) {
res = dd_should_redirect;
_command_process_redirect_parameters(mpack_node_array_at(root, 1));
dd_tags_add_blocked();
}

if (should_block || dd_mpack_node_lstr_eq(verdict, "record")) {
if (res == dd_should_block || res == dd_should_redirect ||
dd_mpack_node_lstr_eq(verdict, "record")) {
_set_appsec_span_data(mpack_node_array_at(root, 2));
}

Expand All @@ -403,7 +454,7 @@ dd_result dd_command_proc_resp_verd_span_data(
dd_command_process_metrics(metrics);
}

return should_block ? dd_should_block : dd_success;
return res;
}

static void _add_appsec_span_data_frag(mpack_node_t node)
Expand Down
2 changes: 2 additions & 0 deletions src/extension/ddappsec.c
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ static int _do_rinit(INIT_FUNC_ARGS)
dd_helper_close_conn();
} else if (res == dd_should_block) {
dd_request_abort_static_page();
} else if (res == dd_should_redirect) {
dd_request_abort_redirect();
} else if (res) {
mlog_g(
dd_log_info, "request init failed: %s", dd_result_to_string(res));
Expand Down
11 changes: 6 additions & 5 deletions src/extension/dddefs.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Unless explicitly stated otherwise all files in this repository are
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2021 Datadog, Inc.
// This product includes software developed at Datadog
// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
#pragma once

#include "attributes.h"
Expand All @@ -13,9 +13,10 @@ typedef enum {
dd_success = 0,
dd_network, // error in communication; connection should be abandoned
dd_should_block, // caller should abort the request
dd_error, // misc error
dd_try_later, // non-fatal error, try again
dd_helper_error // helper failed to process message (non-fatal)
dd_should_redirect, // caller should redirect the request
dd_error, // misc error
dd_try_later, // non-fatal error, try again
dd_helper_error // helper failed to process message (non-fatal)
} dd_result;

const char *nonnull dd_result_to_string(dd_result result);
Expand Down
43 changes: 41 additions & 2 deletions src/extension/request_abort.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ static const char static_error_json[] =

static zend_string *_custom_error_html = NULL;
static zend_string *_custom_error_json = NULL;
static THREAD_LOCAL_ON_ZTS int _response_code = DEFAULT_RESPONSE_CODE;
static THREAD_LOCAL_ON_ZTS int _response_code = DEFAULT_BLOCKING_RESPONSE_CODE;
static THREAD_LOCAL_ON_ZTS dd_response_type _response_type =
DEFAULT_RESPONSE_TYPE;
static THREAD_LOCAL_ON_ZTS zend_string *_redirection_location = NULL;

static void _abort_prelude(void);
ATTR_FORMAT(1, 2)
Expand Down Expand Up @@ -150,7 +151,7 @@ static dd_response_type _get_response_type_from_accept_header()
}

// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
void dd_set_response_code_and_type(int code, dd_response_type type)
void dd_set_block_code_and_type(int code, dd_response_type type)
{
_response_code = code;

Expand All @@ -167,6 +168,44 @@ void dd_set_response_code_and_type(int code, dd_response_type type)
}
}

// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
void dd_set_redirect_code_and_location(int code, zend_string *nullable location)
{
_response_code = code;
_redirection_location = location;
}

void dd_request_abort_redirect()
{
if (_redirection_location == NULL || ZSTR_LEN(_redirection_location) == 0) {
mlog(dd_log_warning, "Failing to redirect: No location set");
return;
}
_abort_prelude();
char *line;
uint line_len = (uint)spprintf(
&line, 0, "Location: %s", ZSTR_VAL(_redirection_location));

SG(sapi_headers).http_response_code = _response_code;
int res = sapi_header_op(SAPI_HEADER_REPLACE,
&(sapi_header_line){.line = line, .line_len = line_len});
if (res == FAILURE) {
mlog(dd_log_warning, "Could not forward to %s",
ZSTR_VAL(_redirection_location));
}

efree(line);

if (sapi_flush() == SUCCESS) {
mlog(dd_log_debug, "Successful call to sapi_flush()");
} else {
mlog(dd_log_warning, "Call to sapi_flush() failed");
}

_emit_error("Datadog blocked the request and attempted a redirection to %s",
ZSTR_VAL(_redirection_location));
}

void dd_request_abort_static_page()
{
_abort_prelude();
Expand Down
12 changes: 8 additions & 4 deletions src/extension/request_abort.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Unless explicitly stated otherwise all files in this repository are
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2021 Datadog, Inc.
// This product includes software developed at Datadog
// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
#pragma once

typedef enum {
Expand All @@ -12,12 +12,16 @@ typedef enum {
} dd_response_type;

// NOLINTNEXTLINE(modernize-macro-to-enum)
#define DEFAULT_RESPONSE_CODE 403
#define DEFAULT_BLOCKING_RESPONSE_CODE 403
#define DEFAULT_REDIRECTION_RESPONSE_CODE 303
#define DEFAULT_RESPONSE_TYPE response_type_auto

void dd_set_response_code_and_type(int code, dd_response_type type);
void dd_set_block_code_and_type(int code, dd_response_type type);
void dd_set_redirect_code_and_location(
int code, zend_string *nullable location);

void dd_request_abort_startup(void);
void dd_request_abort_rinit_once(void);
// noreturn unless called from rinit on fpm
void dd_request_abort_static_page(void);
void dd_request_abort_redirect(void);
8 changes: 8 additions & 0 deletions src/helper/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ bool client::handle_command(network::request_init::request &command)
response->verdict = network::verdict::block;
response->parameters = std::move(res->parameters);
break;
case engine::action_type::redirect:
response->verdict = network::verdict::redirect;
response->parameters = std::move(res->parameters);
break;
case engine::action_type::record:
default:
response->verdict = network::verdict::record;
Expand Down Expand Up @@ -320,6 +324,10 @@ bool client::handle_command(network::request_shutdown::request &command)
response->verdict = network::verdict::block;
response->parameters = std::move(res->parameters);
break;
case engine::action_type::redirect:
response->verdict = network::verdict::redirect;
response->parameters = std::move(res->parameters);
break;
case engine::action_type::record:
default:
response->verdict = network::verdict::record;
Expand Down
10 changes: 6 additions & 4 deletions src/helper/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,12 @@ template <typename T> engine::action parse_action(T &action_object)
}
std::string type = action_object["type"].GetString();

if (type != "block_request") {
engine::action action;
if (type == "block_request") {
action.type = engine::action_type::block;
} else if (type == "redirect_request") {
action.type = engine::action_type::redirect;
} else {
throw parsing_error(
"unknown action.type " + type + " only block_request supported");
}
Expand All @@ -137,9 +142,6 @@ template <typename T> engine::action parse_action(T &action_object)
throw parsing_error("no action.parameters found or unexpected type");
}
const auto &parameters = action_object["parameters"];

engine::action action;
action.type = engine::action_type::block;
for (auto iter = parameters.MemberBegin(); iter != parameters.MemberEnd();
++iter) {
if (!iter->name.IsString()) {
Expand Down
1 change: 1 addition & 0 deletions src/helper/network/proto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct verdict {
static constexpr std::string_view ok = "ok";
static constexpr std::string_view record = "record";
static constexpr std::string_view block = "block";
static constexpr std::string_view redirect = "redirect";
};

using header_t = struct __attribute__((__packed__)) header {
Expand Down
45 changes: 45 additions & 0 deletions tests/docker/recommended.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@
"block"
]
},
{
"id": "redirect-001-001",
"name": "Redirected IP Addresses",
"tags": {
"type": "redirected_ip",
"category": "security_response"
},
"conditions": [
{
"parameters": {
"inputs": [
{
"address": "http.client_ip"
}
],
"data": "redirected_ips"
},
"operator": "ip_match"
}
],
"transformers": [],
"on_match": [
"redirect"
]
},
{
"id": "crs-913-110",
"name": "Acunetix",
Expand Down Expand Up @@ -6595,6 +6620,26 @@
"expiration": 0
}
]
},
{
"id": "redirected_ips",
"type": "ip_with_expiration",
"data": [
{
"value": "80.80.80.81",
"expiration": 0
}
]
}
],
"actions": [
{
"id": "redirect",
"type": "redirect_request",
"parameters": {
"status_code": "303",
"location": "datadoghq.com"
}
}
]
}
Loading

0 comments on commit 2f2e39e

Please sign in to comment.