contrast-agent 7.3.1 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/cs__scope/cs__scope.c +76 -7
- data/ext/cs__scope/cs__scope.h +4 -0
- data/lib/contrast/agent/inventory/policy/datastores.rb +0 -3
- data/lib/contrast/agent/protect/rule/base.rb +5 -1
- data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +17 -5
- data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
- data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +8 -1
- data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
- data/lib/contrast/agent/protect/state.rb +110 -0
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +4 -10
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +11 -12
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +6 -29
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -2
- data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -0
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +4 -2
- data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -5
- data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
- data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +15 -2
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response.rb +0 -2
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +4 -5
- data/lib/contrast/agent/reporting/settings/protect.rb +61 -18
- data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
- data/lib/contrast/agent/reporting/settings/server_features.rb +2 -0
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/components/agent.rb +3 -5
- data/lib/contrast/components/api.rb +3 -3
- data/lib/contrast/components/assess_rules.rb +1 -2
- data/lib/contrast/components/base.rb +1 -2
- data/lib/contrast/components/config/sources.rb +23 -0
- data/lib/contrast/components/logger.rb +19 -0
- data/lib/contrast/components/protect.rb +69 -15
- data/lib/contrast/components/sampling.rb +5 -12
- data/lib/contrast/components/security_logger.rb +17 -0
- data/lib/contrast/components/settings.rb +114 -70
- data/lib/contrast/config/certification_configuration.rb +1 -1
- data/lib/contrast/config/configuration_files.rb +0 -2
- data/lib/contrast/config/diagnostics/config.rb +3 -3
- data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
- data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
- data/lib/contrast/config/diagnostics/monitor.rb +1 -1
- data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
- data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
- data/lib/contrast/config/diagnostics/tools.rb +23 -84
- data/lib/contrast/config/request_audit_configuration.rb +1 -1
- data/lib/contrast/config/server_configuration.rb +3 -15
- data/lib/contrast/configuration.rb +5 -2
- data/lib/contrast/framework/manager.rb +4 -3
- data/lib/contrast/framework/manager_extend.rb +3 -1
- data/lib/contrast/framework/rack/support.rb +11 -2
- data/lib/contrast/utils/log_utils.rb +1 -1
- data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +0 -3
- data/lib/contrast/utils/request_utils.rb +1 -1
- data/lib/contrast/utils/timer.rb +1 -1
- data/lib/contrast.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1266effb11fb78123e8461ce7295f9b4a67a88b7dda632bc57da5d70cb39f87d
|
4
|
+
data.tar.gz: e553aa33bd73ab712585b774578c10ff6f19ae925fdea8adb89c3b6ac253e399
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7abce257d4092010babc21cff242307343ea2fc83bdbd040b8cd95e64be9b2e640963a06ae017053989be17e98442bbc528987c2da5b8f7bc8688b776022c783
|
7
|
+
data.tar.gz: f85de50b08e0eaa5f5d8c6a571fe4d04a03cbbcaab1f9c7f2431dac6b4373b00e04f63be94b5f02d56e222248c5fe256b61fb43ba123d041e1ce585b80e63a5f
|
data/ext/cs__scope/cs__scope.c
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
#include "../cs__common/cs__common.h"
|
6
6
|
#include <ruby.h>
|
7
7
|
#include <stdlib.h>
|
8
|
+
#include <pthread.h>
|
8
9
|
|
9
10
|
/*-----------------------------------------
|
10
11
|
| Calls to Contrast modules and classes
|
@@ -23,6 +24,9 @@ static char truthy_arr[3][5] = {"true", "True", "TRUE"};
|
|
23
24
|
| Helpers
|
24
25
|
-----------*/
|
25
26
|
|
27
|
+
/* Declare new c mutex for scope locking: */
|
28
|
+
pthread_mutex_t c_mutex;
|
29
|
+
|
26
30
|
/**
|
27
31
|
* @brief get scope for ec or create new
|
28
32
|
*
|
@@ -153,22 +157,40 @@ VALUE contrast_scope_application_update() {
|
|
153
157
|
* [ Do not touch, do not free. We don't control it! ]
|
154
158
|
*/
|
155
159
|
char *eVar = getenv(c_const_ctr_agent_app_scope);
|
156
|
-
|
160
|
+
/* check to see if the ENV variable is set */
|
161
|
+
return INT2FIX(env_var_set(eVar));
|
162
|
+
}
|
157
163
|
|
164
|
+
/**
|
165
|
+
* @brief Determines if the Contrast Scope should be set with the Trap Context.
|
166
|
+
* @return 1 if set, 0 if not set.
|
167
|
+
*/
|
168
|
+
int contrast_scope_with_trap_context() {
|
169
|
+
char *eVar = getenv(c_const_ctr_agent_scope_trap_context);
|
170
|
+
return env_var_set(eVar);
|
171
|
+
}
|
172
|
+
|
173
|
+
/**
|
174
|
+
* @brief Checks to see if the ENV variable is set.
|
175
|
+
* @param args VALUE [String] ENV variable name.
|
176
|
+
* @return 1 if set, 0 if not set.
|
177
|
+
*/
|
178
|
+
int env_var_set(char *eVar) {
|
179
|
+
VALUE env_var = eVar;
|
158
180
|
/* check to see if the ENV variable is set */
|
159
|
-
if (RTEST(
|
181
|
+
if (RTEST(env_var)) {
|
160
182
|
/* Application Scope is set*/
|
161
183
|
int i;
|
162
184
|
for (i = 0; i < sizeof(truthy_arr) / sizeof(truthy_arr[0]); i++) {
|
163
185
|
if (strcmp(eVar, truthy_arr[i]) == 0) {
|
164
|
-
return
|
186
|
+
return 1;
|
165
187
|
}
|
166
188
|
}
|
167
189
|
/* this covers all of the false values */
|
168
|
-
return
|
190
|
+
return 0;
|
169
191
|
} else {
|
170
192
|
/* Application Scope is not set ( NULL )*/
|
171
|
-
return
|
193
|
+
return 0;
|
172
194
|
}
|
173
195
|
}
|
174
196
|
|
@@ -505,8 +527,48 @@ VALUE contrast_scope_interface_init(VALUE self, VALUE args) {
|
|
505
527
|
VALUE contrast_scope_for_current_ec(VALUE self, VALUE args) {
|
506
528
|
/* synchronize */
|
507
529
|
VALUE mutex = rb_const_get(scope_mod, rb_intern(rb_const_mon));
|
508
|
-
|
509
|
-
|
530
|
+
if (contrast_scope_with_trap_context()) {
|
531
|
+
/**
|
532
|
+
* trap context safety:
|
533
|
+
*
|
534
|
+
* If mutex synchonize is called inside a trap context,
|
535
|
+
* it will raise a thread error. To avoid that, there are
|
536
|
+
* different approaches as to trap all signal in a loop,
|
537
|
+
* but that is not a good idea, since the scope is checked
|
538
|
+
* once, and only one signal could be trapped. Another way
|
539
|
+
* is to make new thread around the mutex synchronization
|
540
|
+
* call, but this will create a new execution context and
|
541
|
+
* the returned scope will be different.
|
542
|
+
*
|
543
|
+
* Most of the thread error occur randomly whenever GC is
|
544
|
+
* started. We cannot detect, When the GC is started, it
|
545
|
+
* will stop all current Ruby code execution while running.
|
546
|
+
*
|
547
|
+
* Instead we call the `get_ec()` directly since Ruby have
|
548
|
+
* GVL (Global VM Lock) when calling it's C API therefore
|
549
|
+
* This should be safe called from Ruby land. Just in case
|
550
|
+
* this is a feature flag, so that only be used when thread
|
551
|
+
* safety could be traded for signal safety.
|
552
|
+
*
|
553
|
+
* This is still experimental as relies ong the GLV and the
|
554
|
+
* Ruby VM is not thread safe by default. Various problems
|
555
|
+
* might occur, when the mutex is in Ruby and called from
|
556
|
+
* different thread, at once, but since the mutex stays in
|
557
|
+
* the C API context, this might work fine. Still use only
|
558
|
+
* when unusual conditions are met.
|
559
|
+
*
|
560
|
+
* In addition we could use C mutex lock just in case. Ruby
|
561
|
+
* Threads under the hood are C threads (pthread), so they
|
562
|
+
* should be treated as such. In theory only one thread can
|
563
|
+
* access this code at a time.
|
564
|
+
*/
|
565
|
+
pthread_mutex_lock(&c_mutex);
|
566
|
+
VALUE res = get_ec();
|
567
|
+
pthread_mutex_unlock(&c_mutex);
|
568
|
+
return res;
|
569
|
+
} else {
|
570
|
+
return rb_mutex_synchronize(mutex, get_ec, 0);
|
571
|
+
}
|
510
572
|
}
|
511
573
|
|
512
574
|
/*--------------------------------------------------------
|
@@ -819,6 +881,9 @@ VALUE scope_mod_sweep_dead_ecs(VALUE self, VALUE args) {
|
|
819
881
|
}
|
820
882
|
|
821
883
|
void Init_cs__scope() {
|
884
|
+
/* Init new mutex handle */
|
885
|
+
pthread_mutex_init(&c_mutex, NULL);
|
886
|
+
|
822
887
|
/* ivs */
|
823
888
|
rb_iv_cntr_scope = "@contrast_scope";
|
824
889
|
rb_iv_dslr_scope = "@deserialization_scope";
|
@@ -829,6 +894,7 @@ void Init_cs__scope() {
|
|
829
894
|
rb_const_ec = "EXECUTION_CONTEXT";
|
830
895
|
rb_const_ec_keys = "EC_KEYS";
|
831
896
|
c_const_ctr_agent_app_scope = "CONTRAST__AGENT__RUBY__APPLICATION_SCOPE";
|
897
|
+
c_const_ctr_agent_scope_trap_context = "CONTRAST__AGENT__SCOPE__WITH_TRAP_CONTEXT";
|
832
898
|
|
833
899
|
/* Symbols */
|
834
900
|
rb_sym_scope_mod = rb_intern("Scope");
|
@@ -977,4 +1043,7 @@ void Init_cs__scope() {
|
|
977
1043
|
inst_methods_enter_method_scope, 1);
|
978
1044
|
rb_define_method(scope_inst_methods, "contrast_exit_method_scopes!",
|
979
1045
|
inst_methods_exit_method_scope, 1);
|
1046
|
+
|
1047
|
+
/* free the c_mutex */
|
1048
|
+
pthread_mutex_destroy(&c_mutex);
|
980
1049
|
}
|
data/ext/cs__scope/cs__scope.h
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#include <ruby.h>
|
2
|
+
#include <ruby/thread.h>
|
2
3
|
|
3
4
|
/* Calls to Contrast modules and classes */
|
4
5
|
VALUE scope_interface;
|
@@ -14,6 +15,7 @@ static VALUE rb_const_ec;
|
|
14
15
|
static VALUE rb_const_mon;
|
15
16
|
static VALUE rb_const_ec_keys;
|
16
17
|
static VALUE c_const_ctr_agent_app_scope;
|
18
|
+
static VALUE c_const_ctr_agent_scope_trap_context;
|
17
19
|
|
18
20
|
/* Symbols */
|
19
21
|
static VALUE rb_sym_scope_mod;
|
@@ -58,6 +60,7 @@ VALUE scope_klass_exit_scope(VALUE self, VALUE method_scope_sym);
|
|
58
60
|
VALUE contrast_scope_interface_init(VALUE self, VALUE args);
|
59
61
|
VALUE contrast_scope_for_current_ec(VALUE self, VALUE args);
|
60
62
|
VALUE contrast_scope_application_update();
|
63
|
+
int contrast_scope_with_trap_context();
|
61
64
|
|
62
65
|
/* Scope instance methods */
|
63
66
|
VALUE inst_methods_in_cntr_scope(VALUE self, VALUE args);
|
@@ -83,6 +86,7 @@ VALUE inst_methods_exit_method_scope(VALUE self, VALUE scopes_to_exit);
|
|
83
86
|
VALUE is_in_scope(int scope);
|
84
87
|
VALUE get_ec();
|
85
88
|
VALUE rb_new_c_scope();
|
89
|
+
int env_var_set(char *eVar);
|
86
90
|
int scope_increase(int scope);
|
87
91
|
int scope_decrease(int scope);
|
88
92
|
void rb_raise_scope_no_method_err(const VALUE method_scope_sym);
|
@@ -38,9 +38,6 @@ module Contrast
|
|
38
38
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
39
39
|
return unless context&.activity
|
40
40
|
|
41
|
-
context.activity.query_count += 1
|
42
|
-
return unless context.activity.query_count == 1
|
43
|
-
|
44
41
|
Contrast::Agent::Inventory::DatabaseConfig.append_db_config(context.activity)
|
45
42
|
end
|
46
43
|
end
|
@@ -45,7 +45,6 @@ module Contrast
|
|
45
45
|
#
|
46
46
|
# @return mode [Symbol]
|
47
47
|
def initialize
|
48
|
-
::Contrast::PROTECT.defend_rules[rule_name] = self
|
49
48
|
@mode = mode_from_settings
|
50
49
|
end
|
51
50
|
|
@@ -63,6 +62,11 @@ module Contrast
|
|
63
62
|
RULE_NAME
|
64
63
|
end
|
65
64
|
|
65
|
+
# Update state form Settings or Configuration.
|
66
|
+
def update
|
67
|
+
@mode = mode_from_settings
|
68
|
+
end
|
69
|
+
|
66
70
|
# Should return the short name.
|
67
71
|
#
|
68
72
|
# @return [String]
|
@@ -31,15 +31,27 @@ module Contrast
|
|
31
31
|
NAME
|
32
32
|
end
|
33
33
|
|
34
|
+
# Sub-rules forwarders:
|
35
|
+
|
36
|
+
# @return [Contrast::Agent::Protect::Rule::CmdiBackdoors]
|
37
|
+
def command_backdoors
|
38
|
+
@_command_backdoors ||= Contrast::Agent::Protect::Rule::CmdiBackdoors.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Contrast::Agent::Protect::Rule::CmdiChainedCommand]
|
42
|
+
def semantic_chained_commands
|
43
|
+
@_semantic_chained_commands ||= Contrast::Agent::Protect::Rule::CmdiChainedCommand.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def semantic_dangerous_paths
|
47
|
+
@_semantic_dangerous_paths ||= Contrast::Agent::Protect::Rule::CmdiDangerousPath.new
|
48
|
+
end
|
49
|
+
|
34
50
|
# Array of sub_rules:
|
35
51
|
#
|
36
52
|
# @return [Array]
|
37
53
|
def sub_rules
|
38
|
-
@_sub_rules ||= [
|
39
|
-
Contrast::Agent::Protect::Rule::CmdiBackdoors.new,
|
40
|
-
Contrast::Agent::Protect::Rule::CmdiChainedCommand.new,
|
41
|
-
Contrast::Agent::Protect::Rule::CmdiDangerousPath.new
|
42
|
-
].cs__freeze
|
54
|
+
@_sub_rules ||= [command_backdoors, semantic_chained_commands, semantic_dangerous_paths].cs__freeze
|
43
55
|
end
|
44
56
|
|
45
57
|
def applicable_user_inputs
|
@@ -24,7 +24,7 @@ module Contrast
|
|
24
24
|
COOKIE_VALUE, PARAMETER_VALUE, HEADER, JSON_VALUE, MULTIPART_VALUE, XML_VALUE, DWR_VALUE
|
25
25
|
].cs__freeze
|
26
26
|
|
27
|
-
BASE64_INPUT_TYPES = [BODY, COOKIE_VALUE,
|
27
|
+
BASE64_INPUT_TYPES = [BODY, COOKIE_VALUE, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE].cs__freeze
|
28
28
|
|
29
29
|
class << self
|
30
30
|
include Contrast::Components::Logger::InstanceMethods
|
@@ -172,7 +172,9 @@ module Contrast
|
|
172
172
|
end
|
173
173
|
|
174
174
|
# Decodes the value for the given input type.
|
175
|
-
#
|
175
|
+
#
|
176
|
+
# This applies to Values sources only:
|
177
|
+
# BODY, COOKIE_VALUE, HEADER, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE
|
176
178
|
#
|
177
179
|
# @param value [String]
|
178
180
|
# @param input_type [Symbol]
|
@@ -181,6 +183,9 @@ module Contrast
|
|
181
183
|
return value unless Contrast::PROTECT.normalize_base64?
|
182
184
|
return value unless BASE64_INPUT_TYPES.include?(input_type)
|
183
185
|
|
186
|
+
# TODO: RUBY-2110 Update the HEADER handling if possible.
|
187
|
+
# We need only the Header values.
|
188
|
+
|
184
189
|
cs__decode64(value, input_type)
|
185
190
|
end
|
186
191
|
end
|
@@ -16,7 +16,7 @@ module Contrast
|
|
16
16
|
|
17
17
|
# Still a list is needed for this one, as it is not possible to determine if the value is encoded or not.
|
18
18
|
# As long as the list is short the method has a good percentage of success.
|
19
|
-
KNOWN_DECODING_EXCEPTIONS = %w[cmd].cs__freeze
|
19
|
+
KNOWN_DECODING_EXCEPTIONS = %w[cmd version if_modified_since].cs__freeze
|
20
20
|
|
21
21
|
# This methods is not performant, but is more safe for false positive.
|
22
22
|
# Base64 check is no trivial task. For example if one passes a value like 'stringdw' it will return true,
|
@@ -30,11 +30,18 @@ module Contrast
|
|
30
30
|
NAME
|
31
31
|
end
|
32
32
|
|
33
|
+
# Sub-rules forwarders:
|
34
|
+
|
35
|
+
# @return [Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass]
|
36
|
+
def semantic_file_security_bypass
|
37
|
+
@_semantic_file_security_bypass ||= Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass.new
|
38
|
+
end
|
39
|
+
|
33
40
|
# Array of sub_rules
|
34
41
|
#
|
35
42
|
# @return [Array]
|
36
43
|
def sub_rules
|
37
|
-
@_sub_rules ||= [
|
44
|
+
@_sub_rules ||= [semantic_file_security_bypass].cs__freeze
|
38
45
|
end
|
39
46
|
|
40
47
|
def applicable_user_inputs
|
@@ -35,11 +35,18 @@ module Contrast
|
|
35
35
|
BLOCK_MESSAGE
|
36
36
|
end
|
37
37
|
|
38
|
+
# Sub-rules forwarders:
|
39
|
+
|
40
|
+
# @return [Contrast::Agent::Protect::Rule::SqliDangerousFunctions]
|
41
|
+
def semantic_dangerous_functions
|
42
|
+
@_semantic_dangerous_functions ||= Contrast::Agent::Protect::Rule::SqliDangerousFunctions.new
|
43
|
+
end
|
44
|
+
|
38
45
|
# Array of sub_rules
|
39
46
|
#
|
40
47
|
# @return [Array]
|
41
48
|
def sub_rules
|
42
|
-
@_sub_rules ||= [
|
49
|
+
@_sub_rules ||= [semantic_dangerous_functions].cs__freeze
|
43
50
|
end
|
44
51
|
|
45
52
|
def applicable_user_inputs
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/utils/object_share'
|
5
|
+
require 'contrast/components/logger'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Protect
|
10
|
+
# Master class for each protect rule. This class will hold all the rules references.
|
11
|
+
# Any access to the rules should be done through this class. and new rules should be
|
12
|
+
# added here. Each main rule should require and include and initialize it's sub-rules.
|
13
|
+
class State
|
14
|
+
include Contrast::Components::Logger::InstanceMethods
|
15
|
+
|
16
|
+
# @return [boolean] State dictated by local or server settings
|
17
|
+
attr_accessor :enabled
|
18
|
+
# @return [Contrast::Agent::Protect::Rule::BotBlocker] the bot blocker rule
|
19
|
+
attr_reader :bot_blocker
|
20
|
+
# @return [Contrast::Agent::Protect::Rule::CmdInjection] the command injection rule
|
21
|
+
attr_reader :cmd_injection
|
22
|
+
# @return [Contrast::Agent::Protect::Rule::CmdiBackdoors]
|
23
|
+
attr_reader :cmd_injection_command_backdoors
|
24
|
+
# @return [Contrast::Agent::Protect::Rule::CmdiChainedCommand]
|
25
|
+
attr_reader :cmd_injection_semantic_chained_commands
|
26
|
+
# @return [Contrast::Agent::Protect::Rule::CmdiDangerousPath]
|
27
|
+
attr_reader :cmd_injection_semantic_dangerous_paths
|
28
|
+
# @return [Contrast::Agent::Protect::Rule::Deserialization]
|
29
|
+
attr_reader :untrusted_deserialization
|
30
|
+
# @return [Contrast::Agent::Protect::Rule::NoSqli]
|
31
|
+
attr_reader :nosql_injection
|
32
|
+
# @return [Contrast::Agent::Protect::Rule::PathTraversal]
|
33
|
+
attr_reader :path_traversal
|
34
|
+
# @return [Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass]
|
35
|
+
attr_reader :path_traversal_semantic_file_security_bypass
|
36
|
+
# @return [Contrast::Agent::Protect::Rule::Sqli]
|
37
|
+
attr_reader :sql_injection
|
38
|
+
# @return [Contrast::Agent::Protect::Rule::SqliDangerousFunctions]
|
39
|
+
attr_reader :sql_injection_semantic_dangerous_functions
|
40
|
+
# @return [Contrast::Agent::Protect::Rule::UnsafeFileUpload] the unsafe file upload rule
|
41
|
+
attr_reader :unsafe_file_upload
|
42
|
+
# @return [Contrast::Agent::Protect::Rule::Xss] the reflected xss rule
|
43
|
+
attr_reader :reflected_xss
|
44
|
+
# @return [Contrast::Agent::Protect::Rule::Xxe] the xxe rule
|
45
|
+
attr_reader :xxe
|
46
|
+
|
47
|
+
# Initialize all the protect rules. This should be the one place to access each live
|
48
|
+
# rule reference.
|
49
|
+
def initialize
|
50
|
+
@bot_blocker = Contrast::Agent::Protect::Rule::BotBlocker.new
|
51
|
+
@cmd_injection = Contrast::Agent::Protect::Rule::CmdInjection.new
|
52
|
+
@cmd_injection_command_backdoors = @cmd_injection.command_backdoors
|
53
|
+
@cmd_injection_semantic_chained_commands = @cmd_injection.semantic_chained_commands
|
54
|
+
@cmd_injection_semantic_dangerous_paths = @cmd_injection.semantic_dangerous_paths
|
55
|
+
@untrusted_deserialization = Contrast::Agent::Protect::Rule::Deserialization.new
|
56
|
+
@nosql_injection = Contrast::Agent::Protect::Rule::NoSqli.new
|
57
|
+
@path_traversal = Contrast::Agent::Protect::Rule::PathTraversal.new
|
58
|
+
@path_traversal_semantic_file_security_bypass = @path_traversal.semantic_file_security_bypass
|
59
|
+
@sql_injection = Contrast::Agent::Protect::Rule::Sqli.new
|
60
|
+
@sql_injection_semantic_dangerous_functions = @sql_injection.semantic_dangerous_functions
|
61
|
+
@unsafe_file_upload = Contrast::Agent::Protect::Rule::UnsafeFileUpload.new
|
62
|
+
@reflected_xss = Contrast::Agent::Protect::Rule::Xss.new
|
63
|
+
@xxe = Contrast::Agent::Protect::Rule::Xxe.new
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return the Rules in Hash form {rule_id => rule_class }.
|
67
|
+
# This is used to traverse for each rule and update it's settings.
|
68
|
+
# Also is the way a rule is retrieved given the ID is known.
|
69
|
+
#
|
70
|
+
# @return [Hash<String, Contrast::Agent::Protect::Rule::Base>]
|
71
|
+
def rules
|
72
|
+
@_rules ||= {
|
73
|
+
@bot_blocker.rule_name => @bot_blocker,
|
74
|
+
@cmd_injection.rule_name => @cmd_injection,
|
75
|
+
@cmd_injection_command_backdoors.rule_name => @cmd_injection_command_backdoors,
|
76
|
+
@cmd_injection_semantic_chained_commands.rule_name => @cmd_injection_semantic_chained_commands,
|
77
|
+
@cmd_injection_semantic_dangerous_paths.rule_name => @cmd_injection_semantic_dangerous_paths,
|
78
|
+
@untrusted_deserialization.rule_name => @untrusted_deserialization,
|
79
|
+
@nosql_injection.rule_name => @nosql_injection,
|
80
|
+
@path_traversal.rule_name => @path_traversal,
|
81
|
+
@path_traversal_semantic_file_security_bypass.rule_name => @path_traversal_semantic_file_security_bypass,
|
82
|
+
@sql_injection.rule_name => @sql_injection,
|
83
|
+
@sql_injection_semantic_dangerous_functions.rule_name => @sql_injection_semantic_dangerous_functions,
|
84
|
+
@unsafe_file_upload.rule_name => @unsafe_file_upload,
|
85
|
+
@reflected_xss.rule_name => @reflected_xss,
|
86
|
+
@xxe.rule_name => @xxe
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
# Update all settings from configuration.
|
91
|
+
def update
|
92
|
+
rules.values.each(&:update)
|
93
|
+
logger.info('Current rule settings:')
|
94
|
+
rules.each { |k, v| logger.info('Protect Rule mode set', rule: k, mode: v.mode) }
|
95
|
+
end
|
96
|
+
|
97
|
+
# Check the local configurations first then the server settings.
|
98
|
+
def enabled?
|
99
|
+
Contrast::PROTECT.enable || Contrast::SETTINGS.protect_state.enabled
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param [String] rule_id
|
103
|
+
# @return [Contrast::Agent::Protect::Rule::Base]
|
104
|
+
def [] rule_id
|
105
|
+
rules[rule_id]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -16,16 +16,11 @@ module Contrast
|
|
16
16
|
include Contrast::Agent::Reporting::ResponseType
|
17
17
|
include Contrast::Components::Logger::InstanceMethods
|
18
18
|
|
19
|
-
# @return [Integer]
|
20
|
-
attr_accessor :query_count
|
21
|
-
# @return [Array]
|
22
|
-
attr_accessor :routes
|
23
19
|
# @return [Contrast::Agent::Response]
|
24
20
|
attr_accessor :response
|
25
21
|
|
22
|
+
# @param ia_request [Contrast::Agent::Request]
|
26
23
|
def initialize ia_request: nil
|
27
|
-
@routes = []
|
28
|
-
@query_count = 0
|
29
24
|
@event_method = :PUT
|
30
25
|
@event_type = :application_activity
|
31
26
|
@event_endpoint = Contrast::Agent::Reporting::Endpoints.application_activity
|
@@ -66,7 +61,7 @@ module Contrast
|
|
66
61
|
# searching with rule_id and response_type
|
67
62
|
#
|
68
63
|
# @param rule_id [String] name of the protect rule
|
69
|
-
# @param response_type[Symbol<Contrast::Agent::Reporting::ResponseType]
|
64
|
+
# @param response_type[Symbol<Contrast::Agent::Reporting::ResponseType>]
|
70
65
|
# filter by response type
|
71
66
|
# @return [Array<Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity>, nil]
|
72
67
|
# return any matches.
|
@@ -99,9 +94,8 @@ module Contrast
|
|
99
94
|
defend.attackers.map { |a| a.protection_rules.values }
|
100
95
|
end
|
101
96
|
|
102
|
-
# This is primary used for attaching new data and merging existing
|
103
|
-
#
|
104
|
-
# time_map is updated correctly.
|
97
|
+
# This is primary used for attaching new data and merging existing samples and counts per rule entry in
|
98
|
+
# attackers.
|
105
99
|
#
|
106
100
|
# @param attack_result [Contrast::Agent::Reporting::AttackResult]
|
107
101
|
def attach_defend attack_result
|
@@ -55,6 +55,7 @@ module Contrast
|
|
55
55
|
|
56
56
|
# Find an existing attacker if it matches on source details
|
57
57
|
# @param new_attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
|
58
|
+
# @return [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity, nil]
|
58
59
|
def find_existing_attacker_activity new_attacker_activity
|
59
60
|
attackers.find do |existing|
|
60
61
|
existing.source_forwarded_for == new_attacker_activity.source_forwarded_for &&
|
@@ -67,30 +68,28 @@ module Contrast
|
|
67
68
|
# @param rule [String]
|
68
69
|
def attach_existing existing_attacker_activity, attacker_activity, rule
|
69
70
|
new_violation = attacker_activity.protection_rules[rule]
|
71
|
+
return unless new_violation
|
72
|
+
|
70
73
|
sample_activity = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity
|
71
74
|
if (previously_violated = existing_attacker_activity.protection_rules[rule])
|
72
|
-
if (
|
75
|
+
if (new_blocked_samples = new_violation.blocked&.samples)&.any?
|
73
76
|
previously_violated.blocked ||= sample_activity.new
|
74
|
-
previously_violated.blocked.samples.concat(
|
75
|
-
previously_violated.blocked.merge_time_maps(new_blocked.time_map)
|
77
|
+
previously_violated.blocked.samples.concat(new_blocked_samples)
|
76
78
|
end
|
77
79
|
|
78
|
-
if (
|
80
|
+
if (new_exploited_samples = new_violation.exploited&.samples)&.any?
|
79
81
|
previously_violated.exploited ||= sample_activity.new
|
80
|
-
previously_violated.exploited.samples.concat(
|
81
|
-
previously_violated.exploited.merge_time_maps(new_exploited.time_map)
|
82
|
+
previously_violated.exploited.samples.concat(new_exploited_samples)
|
82
83
|
end
|
83
84
|
|
84
|
-
if (
|
85
|
+
if (new_ineffective_samples = new_violation.ineffective&.samples)&.any?
|
85
86
|
previously_violated.ineffective ||= sample_activity.new
|
86
|
-
previously_violated.ineffective.samples.concat(
|
87
|
-
previously_violated.ineffective.merge_time_maps(new_ineffective.time_map)
|
87
|
+
previously_violated.ineffective.samples.concat(new_ineffective_samples)
|
88
88
|
end
|
89
89
|
|
90
|
-
if (
|
90
|
+
if (new_suspicious_samples = new_violation.suspicious&.samples)&.any?
|
91
91
|
previously_violated.suspicious ||= sample_activity.new
|
92
|
-
previously_violated.suspicious.samples.concat(
|
93
|
-
previously_violated.suspicious.merge_time_maps(new_suspicious.time_map)
|
92
|
+
previously_violated.suspicious.samples.concat(new_suspicious_samples)
|
94
93
|
end
|
95
94
|
else
|
96
95
|
existing_attacker_activity.protection_rules[rule] = new_violation
|
data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb
CHANGED
@@ -13,21 +13,17 @@ module Contrast
|
|
13
13
|
class ApplicationDefendAttackSampleActivity < Contrast::Agent::Reporting::ReportableHash
|
14
14
|
# @return [Array<Contrast::Agent::Reporting::ApplicationDefendAttackSample>]
|
15
15
|
attr_reader :samples
|
16
|
-
# @return [Hash<Integer,Integer>] map of time from start in seconds to number of attacks in that second
|
17
|
-
attr_reader :time_map
|
18
16
|
|
19
17
|
def initialize
|
20
18
|
@samples = []
|
21
|
-
@start_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms ||
|
19
|
+
@start_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || Contrast::Utils::Timer.now_ms
|
22
20
|
@event_type = :application_defend_attack_sample_activity
|
23
|
-
@time_map = Hash.new { |h, k| h[k] = 0 }
|
24
21
|
super()
|
25
22
|
end
|
26
23
|
|
27
24
|
def to_controlled_hash
|
28
25
|
validate
|
29
26
|
{
|
30
|
-
attackTimeMap: time_map,
|
31
27
|
samples: samples.map(&:to_controlled_hash),
|
32
28
|
startTime: @start_time, # Start time in ms.
|
33
29
|
total: 1 # there will only ever be 1 attack sample, until batching is done
|
@@ -36,37 +32,18 @@ module Contrast
|
|
36
32
|
|
37
33
|
def validate
|
38
34
|
raise(ArgumentError, 'Start Time is not presented') unless @start_time
|
35
|
+
|
36
|
+
nil
|
39
37
|
end
|
40
38
|
|
41
39
|
# @param attack_result [Contrast::Agent::Reporting::AttackResult]
|
42
40
|
def attach_data attack_result
|
43
41
|
attack_result.samples.each do |attack_sample|
|
44
|
-
base_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0
|
45
|
-
sample_time = attack_sample.time_stamp.to_i
|
46
42
|
samples << Contrast::Agent::Reporting::ApplicationDefendAttackSample.convert(attack_result, attack_sample)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
sample_time
|
51
|
-
end
|
52
|
-
attack_second = (@start_time - base_time) / 1000 # in seconds
|
53
|
-
time_map[attack_second] += 1
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# This method will merge time_maps of attack samples with same
|
58
|
-
# type.
|
59
|
-
#
|
60
|
-
# @param map [Hash<Integer,Integer>] TimeMap to append to previously_violated rule
|
61
|
-
# samples.
|
62
|
-
# @return time_map [Hash<Integer,Integer>] merged time map with updated occurrences.
|
63
|
-
def merge_time_maps map
|
64
|
-
# If the second is the same (key) if we just merge there won't be a new entry,
|
65
|
-
# so just increase the attack count.
|
66
|
-
map.each_key do |key|
|
67
|
-
@time_map[key] = @time_map.fetch(key, 0) + map[key]
|
43
|
+
# If somehow this sample was found before this container was created, change the time to reflect that
|
44
|
+
sample_time = attack_sample.time_stamp.to_i
|
45
|
+
@start_time = sample_time if sample_time < @start_time
|
68
46
|
end
|
69
|
-
@time_map
|
70
47
|
end
|
71
48
|
end
|
72
49
|
end
|
@@ -28,8 +28,7 @@ module Contrast
|
|
28
28
|
# saved request.
|
29
29
|
def initialize ia_request: nil
|
30
30
|
@protection_rules = {}
|
31
|
-
req = ia_request || Contrast::Agent::REQUEST_TRACKER.current&.request
|
32
|
-
if req
|
31
|
+
if (req = ia_request || Contrast::Agent::REQUEST_TRACKER.current&.request)
|
33
32
|
@source_ip = req.ip || Contrast::Utils::ObjectShare::EMPTY_STRING
|
34
33
|
@source_forwarded_for = req.headers['X-Forwarded-For']
|
35
34
|
end
|
@@ -15,7 +15,7 @@ module Contrast
|
|
15
15
|
class ApplicationInventoryActivity < Contrast::Agent::Reporting::ApplicationReportingEvent
|
16
16
|
# return [Array<Contrast::Agent::Reporting::ArchitectureComponent>]
|
17
17
|
attr_reader :components
|
18
|
-
# @ return [Array<String
|
18
|
+
# @ return [Array<String>] - User-Agent Header value
|
19
19
|
attr_reader :browsers
|
20
20
|
|
21
21
|
def initialize
|
@@ -34,7 +34,7 @@ module Contrast
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# @param architectures [Array<Contrast::Agent::Reporting::ArchitectureComponent>,
|
37
|
-
#
|
37
|
+
# Contrast::Agent::Reporting::ArchitectureComponent]
|
38
38
|
def attach_data architectures
|
39
39
|
Array(architectures).each do |architecture|
|
40
40
|
@components << architecture
|
@@ -125,6 +125,7 @@ module Contrast
|
|
125
125
|
# @raise [ArgumentError]
|
126
126
|
def validate
|
127
127
|
raise(ArgumentError, "#{ self } did not have a proper rule. Unable to continue.") unless @rule_id
|
128
|
+
|
128
129
|
unless ::Contrast::ASSESS.session_id
|
129
130
|
raise(ArgumentError, "#{ self } did not have a proper session id. Unable to continue.")
|
130
131
|
end
|
@@ -422,6 +422,8 @@ module Contrast
|
|
422
422
|
raise(ArgumentError, "#{ self } did not have a proper thread. Unable to continue.") unless thread
|
423
423
|
raise(ArgumentError, "#{ self } did not have a proper time. Unable to continue.") unless time
|
424
424
|
raise(ArgumentError, "#{ self } did not have a proper type. Unable to continue.") unless type
|
425
|
+
|
426
|
+
nil
|
425
427
|
end
|
426
428
|
|
427
429
|
# @raise [ArgumentError]
|
@@ -435,6 +437,8 @@ module Contrast
|
|
435
437
|
raise(ArgumentError, "#{ self } did not have a proper signature. Unable to continue.") unless signature
|
436
438
|
raise(ArgumentError, "#{ self } did not have a proper stack. Unable to continue.") unless stack
|
437
439
|
raise(ArgumentError, "#{ self } did not have a proper tags. Unable to continue.") unless reportable_tags
|
440
|
+
|
441
|
+
nil
|
438
442
|
end
|
439
443
|
end
|
440
444
|
end
|