contrast-agent 7.3.1 → 7.3.2
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/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 +4 -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/finding_request.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +15 -2
- data/lib/contrast/agent/reporting/reporting_utilities/response.rb +0 -2
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +5 -2
- data/lib/contrast/agent/reporting/settings/protect.rb +61 -18
- data/lib/contrast/agent/reporting/settings/server_features.rb +2 -0
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/components/protect.rb +14 -1
- data/lib/contrast/components/settings.rb +11 -1
- data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +0 -3
- data/lib/contrast.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4ed370d3625c9e07c5966fb4e091e96cc37a6a2fce8376d63a547e655b27173
|
4
|
+
data.tar.gz: 601a4767e4317af72e2c610df6ba19bb68df2c42f128bb873539204c93801019
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 597307a8f3521cd9783c07ccc8b2f54d6bf2cf1631db84bd29fba683fbd93fc4f5eff47bbbc2ed50c37c02fd3c59d5df987526c839c5e731299cdb6e02e30c6f
|
7
|
+
data.tar.gz: 95898f87cbabbcc24d9ce3f0d5ba1397b4399f9ca35f4c4a1290c7e3d9c7cd26d6bf13c99affc013dea2456260aeb918349a97905863ba790ba869a272faccfc
|
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
|
@@ -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
|
@@ -41,32 +37,11 @@ module Contrast
|
|
41
37
|
# @param attack_result [Contrast::Agent::Reporting::AttackResult]
|
42
38
|
def attach_data attack_result
|
43
39
|
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
40
|
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]
|
41
|
+
# If somehow this sample was found before this container was created, change the time to reflect that
|
42
|
+
sample_time = attack_sample.time_stamp.to_i
|
43
|
+
@start_time = sample_time if sample_time < @start_time
|
68
44
|
end
|
69
|
-
@time_map
|
70
45
|
end
|
71
46
|
end
|
72
47
|
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
|
@@ -31,7 +31,7 @@ module Contrast
|
|
31
31
|
attr_reader :uri
|
32
32
|
# @return [String] the HTTP version of this request
|
33
33
|
attr_reader :version
|
34
|
-
# @return [
|
34
|
+
# @return [String]
|
35
35
|
attr_reader :ip
|
36
36
|
# @return [String] Byte representation of the body
|
37
37
|
attr_accessor :body_binary
|
@@ -40,7 +40,7 @@ module Contrast
|
|
40
40
|
|
41
41
|
class << self
|
42
42
|
# @param request [Contrast::Agent::Request]
|
43
|
-
# @return [Contrast::Agent::Reporting::FindingRequest]
|
43
|
+
# @return [Contrast::Agent::Reporting::FindingRequest, nil]
|
44
44
|
def convert request
|
45
45
|
return unless request
|
46
46
|
|
@@ -126,10 +126,23 @@ module Contrast
|
|
126
126
|
# @param response_data [Hash]
|
127
127
|
# @param res [Contrast::Agent::Reporting::Response]
|
128
128
|
def ng_extract_log_settings response_data, res
|
129
|
-
|
129
|
+
# agent_startup event defines the log level under features.
|
130
|
+
log_level = if response_data[:features]
|
131
|
+
response_data[:features][:logLevel]
|
132
|
+
else
|
133
|
+
response_data[:logLevel]
|
134
|
+
end
|
135
|
+
return unless log_level
|
130
136
|
|
131
137
|
res.server_features.log_level = log_level
|
132
|
-
|
138
|
+
log_file = if response_data[:features]
|
139
|
+
response_data[:features][:logFile]
|
140
|
+
else
|
141
|
+
response_data[:logFile]
|
142
|
+
end
|
143
|
+
return unless log_file
|
144
|
+
|
145
|
+
res.server_features.log_file = log_file
|
133
146
|
end
|
134
147
|
end
|
135
148
|
end
|
@@ -87,8 +87,6 @@ module Contrast
|
|
87
87
|
messages: messages,
|
88
88
|
features: server_features.nil? ? nil : server_features.to_controlled_hash,
|
89
89
|
settings: application_settings.nil? ? nil : application_settings.to_controlled_hash,
|
90
|
-
logLevel: server_features&.log_level,
|
91
|
-
logFile: server_features&.log_file,
|
92
90
|
reactions: server_features.nil? ? nil : reactions.map(&:to_controlled_hash)
|
93
91
|
}.compact
|
94
92
|
end
|
@@ -232,11 +232,14 @@ module Contrast
|
|
232
232
|
return unless ::Contrast::AGENT.enabled?
|
233
233
|
|
234
234
|
logger.trace_with_time('Rebuilding rule modes from TeamServer') do
|
235
|
-
|
235
|
+
# TODO: RUBY-999999 Make sure when updating Protect rules to reflect the mode source (always DEFAULT_VALUE)
|
236
236
|
::Contrast::AGENT.reset_ruleset
|
237
|
+
::Contrast::SETTINGS.build_protect_rules if ::Contrast::PROTECT.enabled?
|
237
238
|
logger.info('Current rule settings:')
|
238
239
|
::Contrast::PROTECT.defend_rules.each { |k, v| logger.info('Protect Rule mode set', rule: k, mode: v.mode) }
|
239
|
-
|
240
|
+
if ::Contrast::ASSESS.enabled?
|
241
|
+
logger.info('Disabled Assess Rules', rules: ::Contrast::ASSESS.disabled_rules)
|
242
|
+
end
|
240
243
|
end
|
241
244
|
end
|
242
245
|
|
@@ -14,6 +14,13 @@ module Contrast
|
|
14
14
|
class Protect
|
15
15
|
# modes set by NG endpoints; block at perimeter needs to be check against the blockAtEntry boolean value
|
16
16
|
NG_PROTECT_RULES_MODE = %w[OFF MONITORING BLOCKING].cs__freeze
|
17
|
+
ACTIVE_PROTECT_RULES_LIST = %w[
|
18
|
+
bot-blocker cmd-injection cmd-injection-command-backdoors cmd-injection-semantic-chained-commands
|
19
|
+
cmd-injection-semantic-dangerous-paths untrusted-deserialization nosql-injection path-traversal
|
20
|
+
path-traversal-semantic-file-security-bypass sql-injection sql-injection-semantic-dangerous-functions
|
21
|
+
unsafe-file-upload reflected-xss xxe
|
22
|
+
].cs__freeze
|
23
|
+
|
17
24
|
# The settings for each protect rule for this application
|
18
25
|
#
|
19
26
|
# @return protection_rules [Array<protectRule>] protectRule: {
|
@@ -80,30 +87,66 @@ module Contrast
|
|
80
87
|
modes_by_id = {}
|
81
88
|
protection_rules.each do |rule|
|
82
89
|
setting_mode = rule[:mode] || rule['mode']
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
else
|
91
|
-
:BLOCK
|
92
|
-
end
|
93
|
-
else
|
94
|
-
:NO_ACTION
|
95
|
-
end
|
96
|
-
else
|
97
|
-
# modes set by newer settings endpoints are of [OFF MONITOR BLOCK BLOCK_AT_PERIMETER] and
|
98
|
-
# can just be cast to symbols
|
99
|
-
setting_mode.to_sym
|
100
|
-
end
|
90
|
+
# BlockAtEnrtry is only available for the protection_rules Array.
|
91
|
+
# It is used in both ng and non ng payloads. If the array is empty
|
92
|
+
# this method will short circuit at the very first line and return
|
93
|
+
# empty hash. this means that the #rules_settings_to_settings_hash
|
94
|
+
# will be used next to extract the settings.
|
95
|
+
bap = rule[:blockAtEntry] || rule['blockAtEntry']
|
96
|
+
api_mode = assign_mode(setting_mode, block_at_entry: !!bap == bap)
|
101
97
|
|
102
98
|
id = rule[:id] || rule['id']
|
103
99
|
modes_by_id[id] = api_mode
|
104
100
|
end
|
105
101
|
modes_by_id
|
106
102
|
end
|
103
|
+
|
104
|
+
# Converts settings into Agent Settings understandable hash {RULE_ID => MODE}
|
105
|
+
# Takes Hash<String, Contrast::Agent::Reporting::Settings::ProtectRule> and converts it
|
106
|
+
# to Hash<RULE_ID => MODE>
|
107
|
+
#
|
108
|
+
# @return rules [Hash<RULE_ID => MODE>, nil] Hash with rule_id as key and mode as value
|
109
|
+
def rules_settings_to_settings_hash
|
110
|
+
return {} if rule_settings.empty?
|
111
|
+
|
112
|
+
modes_by_id = {}
|
113
|
+
rule_settings.each do |rule_id, rule_mode|
|
114
|
+
next unless active_defend_rules.include?(rule_id.to_s)
|
115
|
+
|
116
|
+
modes_by_id[rule_id.to_s] = assign_mode(rule_mode.mode)
|
117
|
+
end
|
118
|
+
modes_by_id
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns list of actively used protection rules to be updated, or default list.
|
122
|
+
# This will be used to query the received settings for the ones used by the Agent.
|
123
|
+
def active_defend_rules
|
124
|
+
return ACTIVE_PROTECT_RULES_LIST unless defined?(Contrast::SETTINGS)
|
125
|
+
|
126
|
+
current_rules = (Contrast::PROTECT&.defend_rules&.keys if defined?(Contrast::PROTECT))
|
127
|
+
return current_rules unless current_rules&.empty?
|
128
|
+
|
129
|
+
ACTIVE_PROTECT_RULES_LIST
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
# Assigns the received settings mode to be used as actual config.
|
135
|
+
# @param setting_mode []
|
136
|
+
def assign_mode setting_mode, block_at_entry: false
|
137
|
+
# modes set by newer settings endpoints are of [OFF MONITOR BLOCK BLOCK_AT_PERIMETER] and
|
138
|
+
# can just be cast to symbols
|
139
|
+
return setting_mode.to_sym unless NG_PROTECT_RULES_MODE.include?(setting_mode)
|
140
|
+
|
141
|
+
case setting_mode
|
142
|
+
when NG_PROTECT_RULES_MODE[1]
|
143
|
+
:MONITOR
|
144
|
+
when NG_PROTECT_RULES_MODE[2]
|
145
|
+
block_at_entry ? :BLOCK_AT_PERIMETER : :BLOCK
|
146
|
+
else
|
147
|
+
:NO_ACTION
|
148
|
+
end
|
149
|
+
end
|
107
150
|
end
|
108
151
|
end
|
109
152
|
end
|
@@ -85,6 +85,8 @@ module Contrast
|
|
85
85
|
security_logger: security_logger.settings_blank? ? nil : security_logger.to_controlled_hash,
|
86
86
|
assessment: @_assess ? assess.to_controlled_hash : {},
|
87
87
|
defend: @_protect ? protect.to_controlled_hash : {},
|
88
|
+
logLevel: log_level,
|
89
|
+
logFile: log_file,
|
88
90
|
telemetry: telemetry
|
89
91
|
}.compact
|
90
92
|
end
|
@@ -10,7 +10,7 @@ module Contrast
|
|
10
10
|
module Protect
|
11
11
|
# A wrapper build around the Common Agent Configuration project to allow for access of the values contained in
|
12
12
|
# its parent_configuration_spec.yaml. Specifically, this allows for querying the state of the Protect product.
|
13
|
-
class Interface
|
13
|
+
class Interface # rubocop:disable Metrics/ClassLength
|
14
14
|
include Contrast::Components::ComponentBase
|
15
15
|
include Contrast::Config::BaseConfiguration
|
16
16
|
|
@@ -168,6 +168,19 @@ module Contrast
|
|
168
168
|
|
169
169
|
@_forcibly_enabled ||= true?(::Contrast::CONFIG.protect.enable)
|
170
170
|
end
|
171
|
+
|
172
|
+
# Used for conversion to contrast metrics hash:
|
173
|
+
def forcibly_enabled
|
174
|
+
forcibly_enabled?
|
175
|
+
end
|
176
|
+
|
177
|
+
def forcibly_disabled
|
178
|
+
forcibly_disabled?
|
179
|
+
end
|
180
|
+
|
181
|
+
def report_custom_code_sysfile_access
|
182
|
+
report_custom_code_sysfile_access?
|
183
|
+
end
|
171
184
|
end
|
172
185
|
end
|
173
186
|
end
|
@@ -156,7 +156,7 @@ module Contrast
|
|
156
156
|
def update_from_application_settings settings_response
|
157
157
|
return unless (app_settings = settings_response&.application_settings)
|
158
158
|
|
159
|
-
|
159
|
+
extract_protect_app_settings(app_settings)
|
160
160
|
update_exclusion_matchers(app_settings.exclusions)
|
161
161
|
app_settings.protect.virtual_patches = app_settings.protect.virtual_patches unless
|
162
162
|
settings_empty?(app_settings.protect.virtual_patches)
|
@@ -294,6 +294,16 @@ module Contrast
|
|
294
294
|
level[parts[-1]] = value
|
295
295
|
Contrast::CONFIG.sources.set(parts.join('.'), Contrast::Components::Config::Sources::CONTRAST_UI)
|
296
296
|
end
|
297
|
+
|
298
|
+
# Extract the rules modes from protection_rules or rules_settings fields.
|
299
|
+
#
|
300
|
+
# @param app_settings [Contrast::Agent::Reporting::Settings::ApplicationSettings]
|
301
|
+
def extract_protect_app_settings app_settings
|
302
|
+
modes_by_id = app_settings.protect.protection_rules_to_settings_hash
|
303
|
+
modes_by_id = app_settings.protect.rules_settings_to_settings_hash if settings_empty?(modes_by_id)
|
304
|
+
# Preserve previous state if no new settings are extracted:
|
305
|
+
@application_state.modes_by_id = modes_by_id unless settings_empty?(modes_by_id)
|
306
|
+
end
|
297
307
|
end
|
298
308
|
end
|
299
309
|
end
|
@@ -23,9 +23,6 @@ module Contrast
|
|
23
23
|
return unless activity
|
24
24
|
return if activity.defend.attackers.empty?
|
25
25
|
|
26
|
-
activity_batch.query_count += activity.query_count
|
27
|
-
activity_batch.routes << activity.routes
|
28
|
-
activity_batch.routes.flatten!
|
29
26
|
merge_attackers(activity)
|
30
27
|
activity_batch.attach_inventory(activity.inventory) unless activity.inventory.empty?
|
31
28
|
end
|
data/lib/contrast.rb
CHANGED
@@ -75,7 +75,7 @@ module Contrast # :nodoc:
|
|
75
75
|
API = CONFIG.api
|
76
76
|
SETTINGS = Contrast::Components::Settings::Interface.new
|
77
77
|
ASSESS = CONFIG.assess
|
78
|
-
PROTECT =
|
78
|
+
PROTECT = CONFIG.protect
|
79
79
|
INVENTORY = CONFIG.inventory
|
80
80
|
AGENT = CONFIG.agent
|
81
81
|
RUBY_INTERFACE = AGENT.ruby
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contrast-agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.3.
|
4
|
+
version: 7.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- galen.palmer@contrastsecurity.com
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: exe
|
15
15
|
cert_chain: []
|
16
|
-
date: 2023-08-
|
16
|
+
date: 2023-08-09 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: bundler
|