contrast-agent 7.3.0 → 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/assess/policy/policy_node.rb +25 -6
- data/lib/contrast/agent/assess/policy/propagator/response.rb +64 -0
- data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
- data/lib/contrast/agent/assess/policy/source_method.rb +5 -0
- data/lib/contrast/agent/assess/rule/response/body_rule.rb +22 -7
- data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +4 -1
- 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/telemetry/exception/obfuscate.rb +4 -3
- data/lib/contrast/agent/telemetry/identifier.rb +13 -26
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/components/assess.rb +33 -6
- data/lib/contrast/components/base.rb +4 -2
- data/lib/contrast/components/config.rb +2 -2
- data/lib/contrast/components/protect.rb +14 -1
- data/lib/contrast/components/settings.rb +11 -1
- data/lib/contrast/config/diagnostics/command_line.rb +2 -2
- data/lib/contrast/config/diagnostics/environment_variables.rb +2 -1
- data/lib/contrast/config/diagnostics/tools.rb +15 -5
- data/lib/contrast/configuration.rb +61 -29
- data/lib/contrast/logger/application.rb +3 -3
- data/lib/contrast/utils/assess/propagation_method_utils.rb +2 -0
- data/lib/contrast/utils/os.rb +1 -9
- data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +0 -3
- data/lib/contrast.rb +1 -1
- data/resources/assess/policy.json +80 -3
- metadata +3 -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);
|
@@ -16,6 +16,7 @@ module Contrast
|
|
16
16
|
class PolicyNode < Contrast::Agent::Patching::Policy::PolicyNode
|
17
17
|
include Contrast::Components::Logger::InstanceMethods
|
18
18
|
include PolicyNodeUtils
|
19
|
+
|
19
20
|
JSON_TAGS = 'tags'
|
20
21
|
JSON_DATAFLOW = 'dataflow'
|
21
22
|
# The keys used to read from policy.json to create the individual
|
@@ -48,6 +49,9 @@ module Contrast
|
|
48
49
|
].cs__freeze
|
49
50
|
TO_S = %w[to_s to_str].cs__freeze
|
50
51
|
|
52
|
+
# Here are all Responses that will be tracked as sources, or methods they use, like body.
|
53
|
+
RESPONSE_SOURCES = %w[Net::HTTPResponse Rack::Response Sinatra::Response].cs__freeze
|
54
|
+
|
51
55
|
def initialize policy_hash = {}
|
52
56
|
super(policy_hash)
|
53
57
|
@source_string = policy_hash[JSON_SOURCE]
|
@@ -57,13 +61,14 @@ module Contrast
|
|
57
61
|
@targets = convert_policy_markers(target_string)
|
58
62
|
@_use_original_object = ORIGINAL_OBJECT_METHODS.include?(@method_name)
|
59
63
|
@_use_original_on_bang_method = assign_on_bang_check(policy_hash)
|
64
|
+
@_use_response_as_source = RESPONSE_SOURCES.include?(@class_name)
|
60
65
|
end
|
61
66
|
|
67
|
+
# If we have KEEP action on String, and the method is to_s, that method would return self:
|
68
|
+
# String#to_s => self or string. This method is included here to cover the situations such as
|
69
|
+
# String.to_s.html_safe, where normally the dynamic sources properties get lost. To solve this
|
70
|
+
# we will simply return the original object here.
|
62
71
|
def assign_on_bang_check policy_hash
|
63
|
-
# If we have KEEP action on String, and the method is to_s, that method would return self:
|
64
|
-
# String#to_s => self or string. This method is included here to cover the situations such as
|
65
|
-
# String.to_s.html_safe, where normally the dynamic sources properties get lost. To solve this
|
66
|
-
# we will simply return the original object here.
|
67
72
|
return true if @_use_original_object && TO_S.include?(policy_hash[JSON_METHOD_NAME])
|
68
73
|
|
69
74
|
@_use_original_object &&
|
@@ -166,7 +171,7 @@ module Contrast
|
|
166
171
|
# that the method is without bang - it does not change the source, but rather
|
167
172
|
# creates a copy of it.
|
168
173
|
#
|
169
|
-
# @return
|
174
|
+
# @return [Boolean]
|
170
175
|
def use_original_object?
|
171
176
|
@_use_original_object && Contrast::ASSESS.track_original_object?
|
172
177
|
end
|
@@ -175,10 +180,24 @@ module Contrast
|
|
175
180
|
# that the target return is the same as object - a bang method modifying the
|
176
181
|
# source.
|
177
182
|
#
|
178
|
-
# @return
|
183
|
+
# @return [Boolean]
|
179
184
|
def use_original_on_bang_method?
|
180
185
|
@_use_original_on_bang_method && Contrast::ASSESS.track_original_object?
|
181
186
|
end
|
187
|
+
|
188
|
+
# This method will check if policy is fit to use response as source.
|
189
|
+
#
|
190
|
+
# @return [Boolean]
|
191
|
+
def use_response_as_source?
|
192
|
+
Contrast::ASSESS.track_response_as_source?
|
193
|
+
end
|
194
|
+
|
195
|
+
# This method will check if the policy node is for response method.
|
196
|
+
#
|
197
|
+
# @return [Boolean]
|
198
|
+
def response_source_node?
|
199
|
+
@_use_response_as_source
|
200
|
+
end
|
182
201
|
end
|
183
202
|
end
|
184
203
|
end
|
@@ -0,0 +1,64 @@
|
|
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/agent/assess/policy/propagator/select'
|
5
|
+
require 'contrast/utils/duck_utils'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Assess
|
10
|
+
module Policy
|
11
|
+
module Propagator
|
12
|
+
# Propagation that results in all the tags of the source being
|
13
|
+
# applied to the target at the point of insertion. The target's
|
14
|
+
# preexisting tags are shifted to account for this insertion.
|
15
|
+
class Response < Contrast::Agent::Assess::Policy::Propagator::Base
|
16
|
+
class << self
|
17
|
+
# This will path the Net::HTTP.request method. It takes two parameters:
|
18
|
+
# - req: Net::HTTPGenericRequest
|
19
|
+
# - body: String
|
20
|
+
# As body may be optional, we need to check if it's nil or not.
|
21
|
+
#
|
22
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
|
23
|
+
# @param preshift [Contrast::Agent::Assess::Preshift]
|
24
|
+
# @param ret [Object] Return targer from method invocation.
|
25
|
+
# @param _block [nil, {}] block passed.
|
26
|
+
def net_response_keep propagation_node, preshift, ret, _block
|
27
|
+
return unless Contrast::ASSESS.track_response_as_source?
|
28
|
+
|
29
|
+
# Check to see if the argument is of correct type, and whether the body is tracked or not.
|
30
|
+
# if it's tracked and the body is not nil, then copy the properties from the source's body
|
31
|
+
# to the target's body.
|
32
|
+
source_body = if preshift.args.length == 2
|
33
|
+
preshift.args[1]
|
34
|
+
else
|
35
|
+
preshift.args[0]&.body
|
36
|
+
end
|
37
|
+
copy_body_tags(propagation_node, source_body, ret)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Copy the properties form source body to the response body, if one is present.
|
43
|
+
#
|
44
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
|
45
|
+
# @param source_body [String] the tracked body to copy from.
|
46
|
+
# @param ret [String] the return target from method invocation.
|
47
|
+
# @return [String, nil]
|
48
|
+
def copy_body_tags propagation_node, source_body, ret
|
49
|
+
return if Contrast::Utils::DuckUtils.empty_duck?(source_body)
|
50
|
+
return unless ret&.body&.cs__is_a?(String)
|
51
|
+
return unless source_body&.cs__is_a?(String)
|
52
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret.body))
|
53
|
+
|
54
|
+
# KEEP
|
55
|
+
properties.copy_from(source_body, ret.body, 0, propagation_node.untags)
|
56
|
+
ret
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -31,6 +31,7 @@ module Contrast
|
|
31
31
|
require 'contrast/agent/assess/policy/propagator/substitution'
|
32
32
|
require 'contrast/agent/assess/policy/propagator/trim'
|
33
33
|
require 'contrast/agent/assess/policy/propagator/buffer'
|
34
|
+
require 'contrast/agent/assess/policy/propagator/response'
|
34
35
|
end
|
35
36
|
end
|
36
37
|
end
|
@@ -46,6 +46,11 @@ module Contrast
|
|
46
46
|
# Exclusions makes method slow:
|
47
47
|
return if excluded_by_url?
|
48
48
|
|
49
|
+
# Check to see if the source node is to be used for response as source.
|
50
|
+
if method_policy.source_node.response_source_node? && !method_policy.source_node.use_response_as_source?
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
49
54
|
# used to hold the object and ret
|
50
55
|
source_data = Contrast::Agent::Assess::Events::EventData.new(nil, nil, object, ret, nil)
|
51
56
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'rack'
|
5
5
|
require 'contrast/utils/hash_digest'
|
6
|
+
require 'contrast/utils/duck_utils'
|
6
7
|
require 'contrast/utils/string_utils'
|
7
8
|
require 'contrast/agent/assess/rule/response/base_rule'
|
8
9
|
|
@@ -44,21 +45,35 @@ module Contrast
|
|
44
45
|
# @param element_start_str [String] element to find in html section
|
45
46
|
# @return [Array<Hash>] the found elements of this section, as well as their start and end indexes.
|
46
47
|
def html_elements section, element_start_str = '', capture_overflow: false
|
48
|
+
return [] unless section
|
49
|
+
return [] unless (potentials = potential_elements(section, element_start_str).flatten).any?
|
50
|
+
|
47
51
|
elements = []
|
48
52
|
section_start = 0
|
49
|
-
return [] unless section
|
50
53
|
|
51
|
-
|
54
|
+
potentials.each do |potential_element|
|
52
55
|
next unless potential_element
|
53
56
|
next unless element_openings.any? { |opening| potential_element.start_with?(opening) }
|
54
57
|
|
55
|
-
|
56
|
-
next
|
58
|
+
start = section&.index(element_start_str, section_start)
|
59
|
+
next if Contrast::Utils::DuckUtils.empty_duck?(start)
|
60
|
+
|
61
|
+
stop = potential_element.index('>').to_i
|
62
|
+
next if Contrast::Utils::DuckUtils.empty_duck?(stop)
|
57
63
|
|
58
|
-
|
59
|
-
|
64
|
+
section_close = start + 6 + stop
|
65
|
+
# Now we have valid tag section with start and stop.
|
66
|
+
# Save new boundaries. This is to make sure that If
|
67
|
+
# on previous iteration there were non valid section,
|
68
|
+
# the start_section will be assigned to nil, thus making
|
69
|
+
# the detection of new section not possible, and throwing
|
70
|
+
# an error. To that end old values are kept safe.
|
71
|
+
#
|
72
|
+
# Assign new start index.
|
73
|
+
section_start = start
|
74
|
+
# Assign new end index.
|
75
|
+
element_stop = stop
|
60
76
|
|
61
|
-
section_close = section_start + 6 + element_stop
|
62
77
|
elements << capture(section, section_start, section_close, element_stop, overflow: capture_overflow)
|
63
78
|
section_start = section_close
|
64
79
|
end
|
@@ -70,7 +70,10 @@ module Contrast
|
|
70
70
|
# @param response [Contrast::Agent::Response] the response of the application
|
71
71
|
# @return [Array<Hash<String,String>]
|
72
72
|
def cache_meta_tags response
|
73
|
-
|
73
|
+
head_tag = response.body&.split(HEAD_TAG)&.last
|
74
|
+
return [] unless head_tag
|
75
|
+
|
76
|
+
html_elements(head_tag, META_START_STR, capture_overflow: false).
|
74
77
|
select { |tag| cache_control_tag?(tag[HTML_PROP]) }
|
75
78
|
end
|
76
79
|
|
@@ -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
|