contrast-agent 7.3.1 → 7.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|