Skip to content

Commit

Permalink
Provide path params to appsec (#2395)
Browse files Browse the repository at this point in the history
  • Loading branch information
estringana committed Mar 28, 2024
1 parent c993b6a commit 8e98f4d
Show file tree
Hide file tree
Showing 52 changed files with 1,306 additions and 218 deletions.
50 changes: 50 additions & 0 deletions appsec/src/extension/ddappsec.c
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,50 @@ static PHP_FUNCTION(datadog_appsec_testing_request_exec)
RETURN_TRUE;
}

static PHP_FUNCTION(datadog_appsec_push_address)
{
UNUSED(return_value);
if (!DDAPPSEC_G(active)) {
mlog(dd_log_debug, "Trying to access to push_address "
"function while appsec is disabled");
return;
}

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

zval parameters_zv;
zend_array *parameters_arr = zend_new_array(1);
ZVAL_ARR(&parameters_zv, parameters_arr);
zend_hash_add(Z_ARRVAL(parameters_zv), key, value);
Z_TRY_ADDREF_P(value);

dd_conn *conn = dd_helper_mgr_cur_conn();
if (conn == NULL) {
zval_ptr_dtor(&parameters_zv);
mlog_g(dd_log_debug, "No connection; skipping push_address");
return;
}

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

if (dd_req_is_user_req()) {
if (res == dd_should_block || res == dd_should_redirect) {
dd_req_call_blocking_function(res);
}
} else {
if (res == dd_should_block) {
dd_request_abort_static_page();
} else if (res == dd_should_redirect) {
dd_request_abort_redirect();
}
}
}

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
void_ret_bool_arginfo, 0, 0, _IS_BOOL, 0)
ZEND_END_ARG_INFO()
Expand All @@ -464,9 +508,15 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(request_exec_arginfo, 0, 1, _IS_BOOL, 0)
ZEND_ARG_INFO(0, "data")
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_END_ARG_INFO()

// clang-format off
static const zend_function_entry functions[] = {
ZEND_RAW_FENTRY(DD_APPSEC_NS "is_enabled", PHP_FN(datadog_appsec_is_enabled), void_ret_bool_arginfo, 0)
ZEND_RAW_FENTRY(DD_APPSEC_NS "push_address", PHP_FN(datadog_appsec_push_address), push_address_arginfo, 0)
PHP_FE_END
};
static const zend_function_entry testing_functions[] = {
Expand Down
10 changes: 10 additions & 0 deletions appsec/tests/extension/inc/mock_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ function print_commands($sort = true) {
print_r($commands);
}

function get_command($command) {
$commands = $this->get_commands();
foreach($commands as $c) {
if ($c[0] == $command) {
return $c;
}
}
return [];
}

static function ksort_recurse(&$arr) {
if (!is_array($arr)) {
return;
Expand Down
29 changes: 29 additions & 0 deletions appsec/tests/extension/push_params_block.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
Push address gets blocked
--INI--
extension=ddtrace.so
datadog.appsec.enabled=1
--FILE--
<?php
use function datadog\appsec\testing\{rinit,rshutdown};
use function datadog\appsec\push_address;

include __DIR__ . '/inc/mock_helper.php';

$helper = Helper::createInitedRun([
response_list(response_request_init(['ok', []])),
response_list(response_request_exec(['block', ['status_code' => '404', 'type' => 'json'], ['{"found":"attack"}','{"another":"attack"}']])),
]);

rinit();
push_address("server.request.path_params", ["some" => "params", "more" => "parameters"]);

var_dump("THIS SHOULD NOT GET IN THE OUTPUT");

?>
--EXPECTHEADERS--
Status: 404 Not Found
Content-type: application/json
--EXPECTF--
{"errors": [{"title": "You've been blocked", "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}
Warning: datadog\appsec\push_address(): Datadog blocked the request and presented a static error page in %s on line %d
45 changes: 45 additions & 0 deletions appsec/tests/extension/push_params_ok_01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
--TEST--
Push address are sent on request_exec - array
--INI--
extension=ddtrace.so
datadog.appsec.enabled=1
--FILE--
<?php
use function datadog\appsec\testing\{rinit,rshutdown};
use function datadog\appsec\push_address;

include __DIR__ . '/inc/mock_helper.php';

$helper = Helper::createInitedRun([
response_list(response_request_init(['ok', []])),
response_list(response_request_exec(['ok', [], [], [], [], false])),
response_list(response_request_shutdown(['ok', [], new ArrayObject(), new ArrayObject()]))
]);

var_dump(rinit());
push_address("server.request.path_params", ["some" => "params", "more" => "parameters"]);
var_dump(rshutdown());

var_dump($helper->get_command("request_exec"));

?>
--EXPECTF--
bool(true)
bool(true)
array(2) {
[0]=>
string(12) "request_exec"
[1]=>
array(1) {
[0]=>
array(1) {
["server.request.path_params"]=>
array(2) {
["some"]=>
string(6) "params"
["more"]=>
string(10) "parameters"
}
}
}
}
40 changes: 40 additions & 0 deletions appsec/tests/extension/push_params_ok_02.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
Push address are sent on request_exec - string
--INI--
extension=ddtrace.so
datadog.appsec.enabled=1
--FILE--
<?php
use function datadog\appsec\testing\{rinit,rshutdown};
use function datadog\appsec\push_address;

include __DIR__ . '/inc/mock_helper.php';

$helper = Helper::createInitedRun([
response_list(response_request_init(['ok', []])),
response_list(response_request_exec(['ok', [], [], [], [], false])),
response_list(response_request_shutdown(['ok', [], new ArrayObject(), new ArrayObject()]))
]);

var_dump(rinit());
push_address("server.request.path_params", "some string");
var_dump(rshutdown());

var_dump($helper->get_command("request_exec"));

?>
--EXPECTF--
bool(true)
bool(true)
array(2) {
[0]=>
string(12) "request_exec"
[1]=>
array(1) {
[0]=>
array(1) {
["server.request.path_params"]=>
string(11) "some string"
}
}
}
40 changes: 40 additions & 0 deletions appsec/tests/extension/push_params_ok_03.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
Push address are sent on request_exec - integer
--INI--
extension=ddtrace.so
datadog.appsec.enabled=1
--FILE--
<?php
use function datadog\appsec\testing\{rinit,rshutdown};
use function datadog\appsec\push_address;

include __DIR__ . '/inc/mock_helper.php';

$helper = Helper::createInitedRun([
response_list(response_request_init(['ok', []])),
response_list(response_request_exec(['ok', [], [], [], [], false])),
response_list(response_request_shutdown(['ok', [], new ArrayObject(), new ArrayObject()]))
]);

var_dump(rinit());
push_address("server.request.path_params", 1234);
var_dump(rshutdown());

var_dump($helper->get_command("request_exec"));

?>
--EXPECTF--
bool(true)
bool(true)
array(2) {
[0]=>
string(12) "request_exec"
[1]=>
array(1) {
[0]=>
array(1) {
["server.request.path_params"]=>
int(1234)
}
}
}
28 changes: 28 additions & 0 deletions appsec/tests/extension/push_params_redirect.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
Push address gets blocked
--INI--
extension=ddtrace.so
datadog.appsec.enabled=1
--FILE--
<?php
use function datadog\appsec\testing\{rinit,rshutdown};
use function datadog\appsec\push_address;

include __DIR__ . '/inc/mock_helper.php';

$helper = Helper::createInitedRun([
response_list(response_request_init(['ok', []])),
response_list(response_request_exec(['redirect', ['status_code' => '303', 'location' => 'https://datadoghq.com'], []])),
]);

rinit();
push_address("server.request.path_params", ["some" => "params", "more" => "parameters"]);

var_dump("THIS SHOULD NOT GET IN THE OUTPUT");

?>
--EXPECTHEADERS--
Status: 303 See Other
Content-type: text/html; charset=UTF-8
--EXPECTF--
Warning: datadog\appsec\push_address(): Datadog blocked the request and attempted a redirection to https://datadoghq.com in %s on line %d
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import org.junit.jupiter.api.condition.EnabledIf
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

import java.net.http.HttpRequest
import java.net.http.HttpResponse

import static com.datadog.appsec.php.integration.TestParams.getPhpVersion
import static com.datadog.appsec.php.integration.TestParams.getVariant
import static java.net.http.HttpResponse.BodyHandlers.ofString

@Testcontainers
@EnabledIf('isExpectedVersion')
Expand Down Expand Up @@ -69,7 +71,6 @@ class Laravel8xTests {
assert span.metrics._sampling_priority_v1 == 2.0d
}


@Test
void 'Sign up automated event'() {
def trace = container.traceFromRequest(
Expand All @@ -84,4 +85,20 @@ class Laravel8xTests {
assert span.meta."appsec.events.users.signup.track" == "true"
assert span.metrics._sampling_priority_v1 == 2.0d
}

@Test
void 'test path params'() {
// Set ip which is blocked
HttpRequest req = container.buildReq('/dynamic-path/someValue').GET().build()
def trace = container.traceFromRequest(req, ofString()) { HttpResponse<String> re ->
assert re.statusCode() == 403
assert re.body().contains('blocked')
}

Span span = trace.first()
assert span.metrics."_dd.appsec.enabled" == 1.0d
assert span.metrics."_dd.appsec.waf.duration" > 0.0d
assert span.meta."_dd.appsec.event_rules.version" != ''
assert span.meta."appsec.blocked" == "true"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,19 @@ class Symfony62Tests {
assert span.meta."appsec.events.users.signup.track" == "true"
assert span.metrics._sampling_priority_v1 == 2.0d
}

@Test
void 'test path params'() {
HttpRequest req = container.buildReq('/dynamic-path/someValue').GET().build()
def trace = container.traceFromRequest(req, ofString()) { HttpResponse<String> re ->
assert re.statusCode() == 403
assert re.body().contains('blocked')
}

Span span = trace.first()
assert span.metrics."_dd.appsec.enabled" == 1.0d
assert span.metrics."_dd.appsec.waf.duration" > 0.0d
assert span.meta."_dd.appsec.event_rules.version" != ''
assert span.meta."appsec.blocked" == "true"
}
}
29 changes: 29 additions & 0 deletions appsec/tests/integration/src/test/waf/recommended.json
Original file line number Diff line number Diff line change
Expand Up @@ -3849,6 +3849,35 @@
],
"transformers": []
},
{
"id": "path-params-tests",
"name": "Rule for testing path params",
"tags": {
"type": "block_params",
"category": "security_response"
},
"conditions": [
{
"parameters": {
"inputs": [
{
"address": "server.request.path_params"
}
],
"list": [
"param01"
]
},
"operator": "phrase_match"
}
],
"transformers": [
"keys_only"
],
"on_match": [
"block"
]
},
{
"id": "crs-942-290",
"name": "Finds basic MongoDB SQL injection attempts",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Response;

class MiscController extends Controller
{
public function dynamicPath(string $param01)
{
return response('Hi', 200);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@

Route::get('/authenticate', '\App\Http\Controllers\LoginController@authenticate');
Route::get('/register', '\App\Http\Controllers\LoginController@register');
Route::get('/dynamic-path/{param01}', '\App\Http\Controllers\MiscController@dynamicPath');

0 comments on commit 8e98f4d

Please sign in to comment.