Skip to content

Commit 8e98f4d

Browse files
authored
Provide path params to appsec (#2395)
1 parent c993b6a commit 8e98f4d

File tree

52 files changed

+1306
-218
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1306
-218
lines changed

appsec/src/extension/ddappsec.c

+50
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,50 @@ static PHP_FUNCTION(datadog_appsec_testing_request_exec)
456456
RETURN_TRUE;
457457
}
458458

459+
static PHP_FUNCTION(datadog_appsec_push_address)
460+
{
461+
UNUSED(return_value);
462+
if (!DDAPPSEC_G(active)) {
463+
mlog(dd_log_debug, "Trying to access to push_address "
464+
"function while appsec is disabled");
465+
return;
466+
}
467+
468+
zend_string *key = NULL;
469+
zval *value = NULL;
470+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz", &key, &value) == FAILURE) {
471+
RETURN_FALSE;
472+
}
473+
474+
zval parameters_zv;
475+
zend_array *parameters_arr = zend_new_array(1);
476+
ZVAL_ARR(&parameters_zv, parameters_arr);
477+
zend_hash_add(Z_ARRVAL(parameters_zv), key, value);
478+
Z_TRY_ADDREF_P(value);
479+
480+
dd_conn *conn = dd_helper_mgr_cur_conn();
481+
if (conn == NULL) {
482+
zval_ptr_dtor(&parameters_zv);
483+
mlog_g(dd_log_debug, "No connection; skipping push_address");
484+
return;
485+
}
486+
487+
dd_result res = dd_request_exec(conn, &parameters_zv);
488+
zval_ptr_dtor(&parameters_zv);
489+
490+
if (dd_req_is_user_req()) {
491+
if (res == dd_should_block || res == dd_should_redirect) {
492+
dd_req_call_blocking_function(res);
493+
}
494+
} else {
495+
if (res == dd_should_block) {
496+
dd_request_abort_static_page();
497+
} else if (res == dd_should_redirect) {
498+
dd_request_abort_redirect();
499+
}
500+
}
501+
}
502+
459503
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
460504
void_ret_bool_arginfo, 0, 0, _IS_BOOL, 0)
461505
ZEND_END_ARG_INFO()
@@ -464,9 +508,15 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(request_exec_arginfo, 0, 1, _IS_BOOL, 0)
464508
ZEND_ARG_INFO(0, "data")
465509
ZEND_END_ARG_INFO()
466510

511+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(push_address_arginfo, 0, 0, IS_VOID, 1)
512+
ZEND_ARG_INFO(0, key)
513+
ZEND_ARG_INFO(0, value)
514+
ZEND_END_ARG_INFO()
515+
467516
// clang-format off
468517
static const zend_function_entry functions[] = {
469518
ZEND_RAW_FENTRY(DD_APPSEC_NS "is_enabled", PHP_FN(datadog_appsec_is_enabled), void_ret_bool_arginfo, 0)
519+
ZEND_RAW_FENTRY(DD_APPSEC_NS "push_address", PHP_FN(datadog_appsec_push_address), push_address_arginfo, 0)
470520
PHP_FE_END
471521
};
472522
static const zend_function_entry testing_functions[] = {

appsec/tests/extension/inc/mock_helper.php

+10
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@ function print_commands($sort = true) {
152152
print_r($commands);
153153
}
154154

155+
function get_command($command) {
156+
$commands = $this->get_commands();
157+
foreach($commands as $c) {
158+
if ($c[0] == $command) {
159+
return $c;
160+
}
161+
}
162+
return [];
163+
}
164+
155165
static function ksort_recurse(&$arr) {
156166
if (!is_array($arr)) {
157167
return;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Push address gets blocked
3+
--INI--
4+
extension=ddtrace.so
5+
datadog.appsec.enabled=1
6+
--FILE--
7+
<?php
8+
use function datadog\appsec\testing\{rinit,rshutdown};
9+
use function datadog\appsec\push_address;
10+
11+
include __DIR__ . '/inc/mock_helper.php';
12+
13+
$helper = Helper::createInitedRun([
14+
response_list(response_request_init(['ok', []])),
15+
response_list(response_request_exec(['block', ['status_code' => '404', 'type' => 'json'], ['{"found":"attack"}','{"another":"attack"}']])),
16+
]);
17+
18+
rinit();
19+
push_address("server.request.path_params", ["some" => "params", "more" => "parameters"]);
20+
21+
var_dump("THIS SHOULD NOT GET IN THE OUTPUT");
22+
23+
?>
24+
--EXPECTHEADERS--
25+
Status: 404 Not Found
26+
Content-type: application/json
27+
--EXPECTF--
28+
{"errors": [{"title": "You've been blocked", "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}
29+
Warning: datadog\appsec\push_address(): Datadog blocked the request and presented a static error page in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
Push address are sent on request_exec - array
3+
--INI--
4+
extension=ddtrace.so
5+
datadog.appsec.enabled=1
6+
--FILE--
7+
<?php
8+
use function datadog\appsec\testing\{rinit,rshutdown};
9+
use function datadog\appsec\push_address;
10+
11+
include __DIR__ . '/inc/mock_helper.php';
12+
13+
$helper = Helper::createInitedRun([
14+
response_list(response_request_init(['ok', []])),
15+
response_list(response_request_exec(['ok', [], [], [], [], false])),
16+
response_list(response_request_shutdown(['ok', [], new ArrayObject(), new ArrayObject()]))
17+
]);
18+
19+
var_dump(rinit());
20+
push_address("server.request.path_params", ["some" => "params", "more" => "parameters"]);
21+
var_dump(rshutdown());
22+
23+
var_dump($helper->get_command("request_exec"));
24+
25+
?>
26+
--EXPECTF--
27+
bool(true)
28+
bool(true)
29+
array(2) {
30+
[0]=>
31+
string(12) "request_exec"
32+
[1]=>
33+
array(1) {
34+
[0]=>
35+
array(1) {
36+
["server.request.path_params"]=>
37+
array(2) {
38+
["some"]=>
39+
string(6) "params"
40+
["more"]=>
41+
string(10) "parameters"
42+
}
43+
}
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
Push address are sent on request_exec - string
3+
--INI--
4+
extension=ddtrace.so
5+
datadog.appsec.enabled=1
6+
--FILE--
7+
<?php
8+
use function datadog\appsec\testing\{rinit,rshutdown};
9+
use function datadog\appsec\push_address;
10+
11+
include __DIR__ . '/inc/mock_helper.php';
12+
13+
$helper = Helper::createInitedRun([
14+
response_list(response_request_init(['ok', []])),
15+
response_list(response_request_exec(['ok', [], [], [], [], false])),
16+
response_list(response_request_shutdown(['ok', [], new ArrayObject(), new ArrayObject()]))
17+
]);
18+
19+
var_dump(rinit());
20+
push_address("server.request.path_params", "some string");
21+
var_dump(rshutdown());
22+
23+
var_dump($helper->get_command("request_exec"));
24+
25+
?>
26+
--EXPECTF--
27+
bool(true)
28+
bool(true)
29+
array(2) {
30+
[0]=>
31+
string(12) "request_exec"
32+
[1]=>
33+
array(1) {
34+
[0]=>
35+
array(1) {
36+
["server.request.path_params"]=>
37+
string(11) "some string"
38+
}
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
Push address are sent on request_exec - integer
3+
--INI--
4+
extension=ddtrace.so
5+
datadog.appsec.enabled=1
6+
--FILE--
7+
<?php
8+
use function datadog\appsec\testing\{rinit,rshutdown};
9+
use function datadog\appsec\push_address;
10+
11+
include __DIR__ . '/inc/mock_helper.php';
12+
13+
$helper = Helper::createInitedRun([
14+
response_list(response_request_init(['ok', []])),
15+
response_list(response_request_exec(['ok', [], [], [], [], false])),
16+
response_list(response_request_shutdown(['ok', [], new ArrayObject(), new ArrayObject()]))
17+
]);
18+
19+
var_dump(rinit());
20+
push_address("server.request.path_params", 1234);
21+
var_dump(rshutdown());
22+
23+
var_dump($helper->get_command("request_exec"));
24+
25+
?>
26+
--EXPECTF--
27+
bool(true)
28+
bool(true)
29+
array(2) {
30+
[0]=>
31+
string(12) "request_exec"
32+
[1]=>
33+
array(1) {
34+
[0]=>
35+
array(1) {
36+
["server.request.path_params"]=>
37+
int(1234)
38+
}
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Push address gets blocked
3+
--INI--
4+
extension=ddtrace.so
5+
datadog.appsec.enabled=1
6+
--FILE--
7+
<?php
8+
use function datadog\appsec\testing\{rinit,rshutdown};
9+
use function datadog\appsec\push_address;
10+
11+
include __DIR__ . '/inc/mock_helper.php';
12+
13+
$helper = Helper::createInitedRun([
14+
response_list(response_request_init(['ok', []])),
15+
response_list(response_request_exec(['redirect', ['status_code' => '303', 'location' => 'https://datadoghq.com'], []])),
16+
]);
17+
18+
rinit();
19+
push_address("server.request.path_params", ["some" => "params", "more" => "parameters"]);
20+
21+
var_dump("THIS SHOULD NOT GET IN THE OUTPUT");
22+
23+
?>
24+
--EXPECTHEADERS--
25+
Status: 303 See Other
26+
Content-type: text/html; charset=UTF-8
27+
--EXPECTF--
28+
Warning: datadog\appsec\push_address(): Datadog blocked the request and attempted a redirection to https://datadoghq.com in %s on line %d

appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Laravel8xTests.groovy

+18-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import org.junit.jupiter.api.condition.EnabledIf
1010
import org.testcontainers.junit.jupiter.Container
1111
import org.testcontainers.junit.jupiter.Testcontainers
1212

13+
import java.net.http.HttpRequest
1314
import java.net.http.HttpResponse
1415

1516
import static com.datadog.appsec.php.integration.TestParams.getPhpVersion
1617
import static com.datadog.appsec.php.integration.TestParams.getVariant
18+
import static java.net.http.HttpResponse.BodyHandlers.ofString
1719

1820
@Testcontainers
1921
@EnabledIf('isExpectedVersion')
@@ -69,7 +71,6 @@ class Laravel8xTests {
6971
assert span.metrics._sampling_priority_v1 == 2.0d
7072
}
7173

72-
7374
@Test
7475
void 'Sign up automated event'() {
7576
def trace = container.traceFromRequest(
@@ -84,4 +85,20 @@ class Laravel8xTests {
8485
assert span.meta."appsec.events.users.signup.track" == "true"
8586
assert span.metrics._sampling_priority_v1 == 2.0d
8687
}
88+
89+
@Test
90+
void 'test path params'() {
91+
// Set ip which is blocked
92+
HttpRequest req = container.buildReq('/dynamic-path/someValue').GET().build()
93+
def trace = container.traceFromRequest(req, ofString()) { HttpResponse<String> re ->
94+
assert re.statusCode() == 403
95+
assert re.body().contains('blocked')
96+
}
97+
98+
Span span = trace.first()
99+
assert span.metrics."_dd.appsec.enabled" == 1.0d
100+
assert span.metrics."_dd.appsec.waf.duration" > 0.0d
101+
assert span.meta."_dd.appsec.event_rules.version" != ''
102+
assert span.meta."appsec.blocked" == "true"
103+
}
87104
}

appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Symfony62Tests.groovy

+15
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,19 @@ class Symfony62Tests {
8888
assert span.meta."appsec.events.users.signup.track" == "true"
8989
assert span.metrics._sampling_priority_v1 == 2.0d
9090
}
91+
92+
@Test
93+
void 'test path params'() {
94+
HttpRequest req = container.buildReq('/dynamic-path/someValue').GET().build()
95+
def trace = container.traceFromRequest(req, ofString()) { HttpResponse<String> re ->
96+
assert re.statusCode() == 403
97+
assert re.body().contains('blocked')
98+
}
99+
100+
Span span = trace.first()
101+
assert span.metrics."_dd.appsec.enabled" == 1.0d
102+
assert span.metrics."_dd.appsec.waf.duration" > 0.0d
103+
assert span.meta."_dd.appsec.event_rules.version" != ''
104+
assert span.meta."appsec.blocked" == "true"
105+
}
91106
}

appsec/tests/integration/src/test/waf/recommended.json

+29
Original file line numberDiff line numberDiff line change
@@ -3849,6 +3849,35 @@
38493849
],
38503850
"transformers": []
38513851
},
3852+
{
3853+
"id": "path-params-tests",
3854+
"name": "Rule for testing path params",
3855+
"tags": {
3856+
"type": "block_params",
3857+
"category": "security_response"
3858+
},
3859+
"conditions": [
3860+
{
3861+
"parameters": {
3862+
"inputs": [
3863+
{
3864+
"address": "server.request.path_params"
3865+
}
3866+
],
3867+
"list": [
3868+
"param01"
3869+
]
3870+
},
3871+
"operator": "phrase_match"
3872+
}
3873+
],
3874+
"transformers": [
3875+
"keys_only"
3876+
],
3877+
"on_match": [
3878+
"block"
3879+
]
3880+
},
38523881
{
38533882
"id": "crs-942-290",
38543883
"name": "Finds basic MongoDB SQL injection attempts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use Illuminate\Http\Response;
6+
7+
class MiscController extends Controller
8+
{
9+
public function dynamicPath(string $param01)
10+
{
11+
return response('Hi', 200);
12+
}
13+
}

appsec/tests/integration/src/test/www/laravel8x/routes/web.php

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020

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

0 commit comments

Comments
 (0)