Skip to content

Commit

Permalink
Implement LFI (#2770)
Browse files Browse the repository at this point in the history
This PR implements the first exploit prevention added to PHP. That means that apart of wrapping the LFI php functions, it also implements everything else to report exploits. This PR consists on:

- Wrapped certain file operations
    - file_get_contents
    - file_put_contents
    - fopen
    - readfile
- Add exploit preventions metrics
- Add LFI capability to RC
- Add rasp configurations
  • Loading branch information
estringana authored Dec 12, 2024
1 parent 4bf79f2 commit 4cc2897
Show file tree
Hide file tree
Showing 55 changed files with 1,103 additions and 118 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,8 @@ test_integrations_amqp35: global_test_run_dependencies tests/Integrations/AMQP/V
$(call run_tests_debug,tests/Integrations/AMQP/V3_5)
test_integrations_deferred_loading: global_test_run_dependencies tests/Integrations/DeferredLoading/composer.lock-php$(PHP_MAJOR_MINOR)
$(call run_tests_debug,tests/Integrations/DeferredLoading)
test_integrations_filesystem: global_test_run_dependencies
$(call run_tests_debug,tests/Integrations/Filesystem)
test_integrations_curl: global_test_run_dependencies
$(call run_tests_debug,tests/Integrations/Curl)
test_integrations_elasticsearch1: global_test_run_dependencies tests/Integrations/Elasticsearch/V1/composer.lock-php$(PHP_MAJOR_MINOR)
Expand Down
9 changes: 4 additions & 5 deletions appsec/src/extension/backtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "backtrace.h"
#include "compatibility.h"
#include "configuration.h"
#include "ddappsec.h"
#include "ddtrace.h"
#include "logging.h"
#include "php_compat.h"
Expand Down Expand Up @@ -246,13 +247,11 @@ bool dd_report_exploit_backtrace(zend_string *nullable id)
zval *dd_stack = zend_hash_find(Z_ARR_P(meta_struct), _dd_stack_key);
zval *exploit = NULL;
if (!dd_stack || Z_TYPE_P(dd_stack) == IS_NULL) {
zval new_dd_stack;
zval new_exploit;
dd_stack = zend_hash_add_new(
Z_ARR_P(meta_struct), _dd_stack_key, &new_dd_stack);
Z_ARR_P(meta_struct), _dd_stack_key, &EG(uninitialized_zval));
array_init(dd_stack);
exploit =
zend_hash_add_new(Z_ARR_P(dd_stack), _exploit_key, &new_exploit);
exploit = zend_hash_add_new(
Z_ARR_P(dd_stack), _exploit_key, &EG(uninitialized_zval));
array_init(exploit);
} else if (Z_TYPE_P(dd_stack) != IS_ARRAY) {
return false;
Expand Down
8 changes: 5 additions & 3 deletions appsec/src/extension/commands/request_exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

struct ctx {
struct req_info req_info; // dd_command_proc_resp_verd_span_data expect it
bool rasp;
zval *nonnull data;
};

Expand All @@ -22,21 +23,21 @@ static dd_result _pack_command(mpack_writer_t *nonnull w, void *nonnull ctx);
static const dd_command_spec _spec = {
.name = "request_exec",
.name_len = sizeof("request_exec") - 1,
.num_args = 1, // a single map
.num_args = 2,
.outgoing_cb = _pack_command,
.incoming_cb = dd_command_proc_resp_verd_span_data,
.config_features_cb = dd_command_process_config_features_unexpected,
};

dd_result dd_request_exec(dd_conn *nonnull conn, zval *nonnull data)
dd_result dd_request_exec(dd_conn *nonnull conn, zval *nonnull data, bool rasp)
{
if (Z_TYPE_P(data) != IS_ARRAY) {
mlog(dd_log_debug, "Invalid data provided to command request_exec, "
"expected hash table.");
return dd_error;
}

struct ctx ctx = {.data = data};
struct ctx ctx = {.rasp = rasp, .data = data};

return dd_command_exec_req_info(conn, &_spec, &ctx.req_info);
}
Expand All @@ -46,6 +47,7 @@ static dd_result _pack_command(mpack_writer_t *nonnull w, void *nonnull _ctx)
assert(_ctx != NULL);
struct ctx *ctx = _ctx;

mpack_write(w, ctx->rasp);
dd_mpack_write_zval(w, ctx->data);

return dd_success;
Expand Down
2 changes: 1 addition & 1 deletion appsec/src/extension/commands/request_exec.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
#include <SAPI.h>
#include <php.h>

dd_result dd_request_exec(dd_conn *nonnull conn, zval *nonnull data);
dd_result dd_request_exec(dd_conn *nonnull conn, zval *nonnull data, bool rasp);
1 change: 1 addition & 0 deletions appsec/src/extension/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ extern bool runtime_config_first_init;
SYSCFG(BOOL, DD_APPSEC_HELPER_LAUNCH, "true") \
CONFIG(STRING, DD_APPSEC_HELPER_PATH, DD_BASE("bin/libddappsec-helper.so")) \
SYSCFG(BOOL, DD_APPSEC_STACK_TRACE_ENABLED, "true") \
SYSCFG(BOOL, DD_APPSEC_RASP_ENABLED , "false") \
SYSCFG(INT, DD_APPSEC_MAX_STACK_TRACE_DEPTH, "32") \
SYSCFG(INT, DD_APPSEC_MAX_STACK_TRACES, "2") \
CONFIG(STRING, DD_APPSEC_HELPER_RUNTIME_PATH, "/tmp", .ini_change = dd_on_runtime_path_update) \
Expand Down
30 changes: 27 additions & 3 deletions appsec/src/extension/ddappsec.c
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ static PHP_FUNCTION(datadog_appsec_testing_request_exec)
RETURN_FALSE;
}

if (dd_request_exec(conn, data) != dd_success) {
if (dd_request_exec(conn, data, false) != dd_success) {
RETURN_FALSE;
}

Expand All @@ -476,6 +476,10 @@ static PHP_FUNCTION(datadog_appsec_testing_request_exec)

static PHP_FUNCTION(datadog_appsec_push_address)
{
struct timespec start;
struct timespec end;
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
long elapsed = 0;
UNUSED(return_value);
if (!DDAPPSEC_G(active)) {
mlog(dd_log_debug, "Trying to access to push_address "
Expand All @@ -485,10 +489,16 @@ static PHP_FUNCTION(datadog_appsec_push_address)

zend_string *key = NULL;
zval *value = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz", &key, &value) == FAILURE) {
bool rasp = false;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|b", &key, &value, &rasp) ==
FAILURE) {
RETURN_FALSE;
}

if (rasp && !get_global_DD_APPSEC_RASP_ENABLED()) {
return;
}

zval parameters_zv;
zend_array *parameters_arr = zend_new_array(1);
ZVAL_ARR(&parameters_zv, parameters_arr);
Expand All @@ -502,9 +512,22 @@ static PHP_FUNCTION(datadog_appsec_push_address)
return;
}

dd_result res = dd_request_exec(conn, &parameters_zv);
dd_result res = dd_request_exec(conn, &parameters_zv, rasp);
zval_ptr_dtor(&parameters_zv);

if (rasp) {
clock_gettime(CLOCK_MONOTONIC_RAW, &end);
elapsed =
((int64_t)end.tv_sec - (int64_t)start.tv_sec) *
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
(int64_t)1000000000 +
((int64_t)end.tv_nsec - (int64_t)start.tv_nsec);
zend_object *span = dd_trace_get_active_root_span();
if (span) {
dd_tags_add_rasp_duration_ext(span, elapsed);
}
}

if (dd_req_is_user_req()) {
if (res == dd_should_block || res == dd_should_redirect) {
dd_req_call_blocking_function(res);
Expand All @@ -529,6 +552,7 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(push_address_arginfo, 0, 0, IS_VOID, 1)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, value)
ZEND_ARG_INFO(0, rasp)
ZEND_END_ARG_INFO()

// clang-format off
Expand Down
17 changes: 17 additions & 0 deletions appsec/src/extension/tags.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"_dd.appsec.events.users.login.success.sdk"
#define DD_EVENTS_USER_LOGIN_FAILURE_SDK \
"_dd.appsec.events.users.login.failure.sdk"
#define DD_EVENTS_RASP_DURATION_EXT "_dd.appsec.rasp.duration_ext"

static zend_string *_dd_tag_data_zstr;
static zend_string *_dd_tag_event_zstr;
Expand All @@ -78,6 +79,7 @@ static zend_string *_dd_tag_rh_content_encoding; // response
static zend_string *_dd_tag_rh_content_language; // response
static zend_string *_dd_tag_user_id;
static zend_string *_dd_metric_enabled;
static zend_string *_dd_rasp_duration_ext;
static zend_string *_dd_signup_event;
static zend_string *_dd_login_success_event;
static zend_string *_dd_login_failure_event;
Expand Down Expand Up @@ -173,6 +175,9 @@ void dd_tags_startup()
_key_remote_addr_zstr =
zend_string_init_interned(LSTRARG("REMOTE_ADDR"), 1);

_dd_rasp_duration_ext =
zend_string_init_interned(LSTRARG(DD_EVENTS_RASP_DURATION_EXT), 1);

// Event related strings
_track_zstr =
zend_string_init_interned(LSTRARG("track"), 1 /* permanent */);
Expand Down Expand Up @@ -298,6 +303,18 @@ void dd_tags_set_event_user_id(zend_string *nonnull zstr)
_event_user_id = zend_string_copy(zstr);
}

void dd_tags_add_rasp_duration_ext(
zend_object *nonnull span, zend_long duration)
{
zval *metrics_zv = dd_trace_span_get_metrics(span);
if (!metrics_zv) {
return;
}
zval zv;
ZVAL_LONG(&zv, duration);
zend_hash_add(Z_ARRVAL_P(metrics_zv), _dd_rasp_duration_ext, &zv);
}

void dd_tags_rshutdown()
{
zend_llist_clean(&_appsec_json_frags);
Expand Down
3 changes: 2 additions & 1 deletion appsec/src/extension/tags.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ void dd_tags_set_event_user_id(zend_string *nonnull zstr);
// does not increase the refcount on zstr
void dd_tags_add_appsec_json_frag(zend_string *nonnull zstr);


void dd_tags_add_rasp_duration_ext(
zend_object *nonnull span, zend_long duration);
2 changes: 1 addition & 1 deletion appsec/src/extension/user_tracking.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ void dd_find_and_apply_verdict_for_user(zend_string *nonnull user_id)
zend_hash_str_add_new(
Z_ARRVAL(data_zv), "usr.id", sizeof("usr.id") - 1, &user_id_zv);

dd_result res = dd_request_exec(conn, &data_zv);
dd_result res = dd_request_exec(conn, &data_zv, false);
zval_ptr_dtor(&data_zv);

dd_tags_set_event_user_id(user_id);
Expand Down
6 changes: 3 additions & 3 deletions appsec/src/helper/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,14 @@ template <typename T> bool client::service_guard()

template <typename T>
std::shared_ptr<typename T::response> client::publish(
typename T::request &command)
typename T::request &command, bool rasp)
{
SPDLOG_DEBUG("received command {}", T::name);

auto response = std::make_shared<typename T::response>();
try {
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
auto res = context_->publish(std::move(command.data));
auto res = context_->publish(std::move(command.data), rasp);
if (res) {
for (auto &act : res->actions) {
dds::network::action_struct new_action;
Expand Down Expand Up @@ -316,7 +316,7 @@ bool client::handle_command(network::request_exec::request &command)
context_.emplace(*service_->get_engine());
}

auto response = publish<network::request_exec>(command);
auto response = publish<network::request_exec>(command, command.rasp);
return send_message<network::request_exec>(response);
}

Expand Down
3 changes: 2 additions & 1 deletion appsec/src/helper/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ class client {

protected:
template <typename T>
std::shared_ptr<typename T::response> publish(typename T::request &command);
std::shared_ptr<typename T::response> publish(
typename T::request &command, bool rasp = false);
template <typename T> bool service_guard();
template <typename T, bool actions = true>
bool send_message(const std::shared_ptr<typename T::response> &message);
Expand Down
5 changes: 3 additions & 2 deletions appsec/src/helper/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ void engine::update(
std::atomic_store_explicit(&common_, new_common, std::memory_order_release);
}

std::optional<engine::result> engine::context::publish(parameter &&param)
std::optional<engine::result> engine::context::publish(
parameter &&param, bool rasp)
{
// Once the parameter reaches this function, it is guaranteed to be
// owned by the engine.
Expand Down Expand Up @@ -82,7 +83,7 @@ std::optional<engine::result> engine::context::publish(parameter &&param)
}
try {
const auto &listener = it->second;
listener->call(data, event_);
listener->call(data, event_, rasp);
} catch (std::exception &e) {
SPDLOG_ERROR("subscriber failed: {}", e.what());
}
Expand Down
2 changes: 1 addition & 1 deletion appsec/src/helper/engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class engine {
context &operator=(context &&) = delete;
~context() = default;

std::optional<result> publish(parameter &&param);
std::optional<result> publish(parameter &&param, bool rasp = false);
// NOLINTNEXTLINE(google-runtime-references)
void get_metrics(metrics::telemetry_submitter &msubmitter);

Expand Down
5 changes: 5 additions & 0 deletions appsec/src/helper/metrics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ constexpr std::string_view event_rules_version =
constexpr std::string_view waf_version = "_dd.appsec.waf.version";
constexpr std::string_view waf_duration = "_dd.appsec.waf.duration";

// rasp
constexpr std::string_view rasp_duration = "_dd.appsec.rasp.duration";
constexpr std::string_view rasp_rule_eval = "_dd.appsec.rasp.rule.eval";
constexpr std::string_view rasp_timeout = "_dd.appsec.rasp.timeout";

} // namespace dds::metrics

template <>
Expand Down
3 changes: 2 additions & 1 deletion appsec/src/helper/network/proto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ struct request_exec {
static constexpr const char *name = request_exec::name;
static constexpr request_id id = request_id::request_exec;

bool rasp = false;
dds::parameter data;

request() = default;
Expand All @@ -196,7 +197,7 @@ struct request_exec {
request &operator=(request &&) = default;
~request() override = default;

MSGPACK_DEFINE(data)
MSGPACK_DEFINE(rasp, data)
};

struct response : base_response_generic<response> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class engine_listener : public listener_base {
[[nodiscard]] std::unordered_set<product> get_supported_products() override
{
return {known_products::ASM, known_products::ASM_DD,
known_products::ASM_DATA};
known_products::ASM_DATA, known_products::ASM_RASP_LFI};
}

protected:
Expand Down
5 changes: 5 additions & 0 deletions appsec/src/helper/remote_config/product.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ struct known_products {
static inline constexpr product ASM_DATA{std::string_view{"ASM_DATA"}};
static inline constexpr product ASM_FEATURES{
std::string_view{"ASM_FEATURES"}};
static inline constexpr product ASM_RASP_LFI{
std::string_view{"ASM_RASP_LFI"}};
static inline constexpr product UNKNOWN{std::string_view{"UNKOWN"}};

static product for_name(std::string_view name)
Expand All @@ -45,6 +47,9 @@ struct known_products {
if (name == ASM_FEATURES.name()) {
return ASM_FEATURES;
}
if (name == ASM_RASP_LFI.name()) {
return ASM_RASP_LFI;
}

return UNKNOWN;
}
Expand Down
3 changes: 2 additions & 1 deletion appsec/src/helper/subscriber/base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class subscriber {

virtual ~listener() = default;
// NOLINTNEXTLINE(google-runtime-references)
virtual void call(parameter_view &data, event &event) = 0;
virtual void call(
parameter_view &data, event &event, bool rasp = false) = 0;

// NOLINTNEXTLINE(google-runtime-references)
virtual void submit_metrics(
Expand Down
20 changes: 19 additions & 1 deletion appsec/src/helper/subscriber/waf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@ instance::listener::~listener()
}
}

void instance::listener::call(dds::parameter_view &data, event &event)
void instance::listener::call(
dds::parameter_view &data, event &event, bool rasp)
{
ddwaf_result res;
DDWAF_RET_CODE code;
Expand Down Expand Up @@ -345,6 +346,14 @@ void instance::listener::call(dds::parameter_view &data, event &event)
break;
}
}
if (rasp) {
// NOLINTNEXTLINE
rasp_runtime_ += res.total_runtime / 1000.0;
rasp_calls_++;
if (res.timeout) {
rasp_timeouts_ += 1;
}
}

const parameter_view derivatives{res.derivatives};
for (const auto &derivative : derivatives) {
Expand Down Expand Up @@ -403,6 +412,15 @@ void instance::listener::submit_metrics(
metrics::event_rules_version, std::string{ruleset_version_});
msubmitter.submit_span_metric(metrics::waf_duration, total_runtime_);

if (rasp_calls_ > 0) {
msubmitter.submit_span_metric(metrics::rasp_duration, rasp_runtime_);
msubmitter.submit_span_metric(metrics::rasp_rule_eval, rasp_calls_);
if (rasp_timeouts_ > 0) {
msubmitter.submit_span_metric(
metrics::rasp_timeout, rasp_timeouts_);
}
}

for (const auto &[key, value] : derivatives_) {
std::string derivative = value;
if (value.length() > max_plain_schema_allowed &&
Expand Down
Loading

0 comments on commit 4cc2897

Please sign in to comment.