tcell_agent 0.4.0 → 1.0.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/Rakefile +9 -22
- data/bin/tcell_agent +127 -132
- data/lib/tcell_agent/agent/event_processor.rb +23 -22
- data/lib/tcell_agent/agent/fork_pipe_manager.rb +7 -7
- data/lib/tcell_agent/agent/policy_manager.rb +20 -15
- data/lib/tcell_agent/agent/policy_types.rb +5 -11
- data/lib/tcell_agent/agent/static_agent.rb +5 -1
- data/lib/tcell_agent/agent.rb +6 -4
- data/lib/tcell_agent/api.rb +7 -9
- data/lib/tcell_agent/appsensor/meta_data.rb +11 -4
- data/lib/tcell_agent/authlogic.rb +3 -3
- data/lib/tcell_agent/cmdi.rb +6 -4
- data/lib/tcell_agent/config/unknown_options.rb +3 -1
- data/lib/tcell_agent/configuration.rb +47 -49
- data/lib/tcell_agent/devise.rb +2 -2
- data/lib/tcell_agent/hooks/login_fraud.rb +58 -29
- data/lib/tcell_agent/instrumentation.rb +11 -10
- data/lib/tcell_agent/logger.rb +2 -2
- data/lib/tcell_agent/patches/meta_data.rb +9 -13
- data/lib/tcell_agent/patches.rb +7 -10
- data/lib/tcell_agent/policies/clickjacking_policy.rb +4 -5
- data/lib/tcell_agent/policies/content_security_policy.rb +6 -12
- data/lib/tcell_agent/policies/dataloss_policy.rb +2 -2
- data/lib/tcell_agent/policies/http_redirect_policy.rb +2 -2
- data/lib/tcell_agent/policies/policy.rb +0 -2
- data/lib/tcell_agent/policies/rust_policies.rb +90 -0
- data/lib/tcell_agent/policies/secure_headers_policy.rb +2 -2
- data/lib/tcell_agent/rails/auth/authlogic.rb +42 -24
- data/lib/tcell_agent/rails/auth/devise.rb +44 -23
- data/lib/tcell_agent/rails/auth/doorkeeper.rb +33 -15
- data/lib/tcell_agent/rails/better_ip.rb +1 -1
- data/lib/tcell_agent/rails/csrf_exception.rb +2 -2
- data/lib/tcell_agent/rails/dlp/process_request.rb +1 -1
- data/lib/tcell_agent/rails/dlp.rb +6 -6
- data/lib/tcell_agent/rails/dlp_handler.rb +1 -1
- data/lib/tcell_agent/rails/js_agent_insert.rb +1 -1
- data/lib/tcell_agent/rails/middleware/body_filter_middleware.rb +1 -1
- data/lib/tcell_agent/rails/middleware/context_middleware.rb +3 -2
- data/lib/tcell_agent/rails/middleware/headers_middleware.rb +10 -9
- data/lib/tcell_agent/rails/routes/grape.rb +6 -6
- data/lib/tcell_agent/rails/routes.rb +8 -11
- data/lib/tcell_agent/rust/libtcellagent-0.11.1.dylib +0 -0
- data/lib/tcell_agent/rust/{libtcellagent-0.6.1.so → libtcellagent-0.11.1.so} +0 -0
- data/lib/tcell_agent/rust/models.rb +16 -0
- data/lib/tcell_agent/rust/tcellagent-0.11.1.dll +0 -0
- data/lib/tcell_agent/rust/whisperer.rb +119 -48
- data/lib/tcell_agent/sensor_events/appsensor_meta_event.rb +17 -20
- data/lib/tcell_agent/sensor_events/command_injection.rb +50 -5
- data/lib/tcell_agent/sensor_events/login_fraud.rb +34 -18
- data/lib/tcell_agent/sensor_events/patches.rb +21 -0
- data/lib/tcell_agent/sensor_events/server_agent.rb +3 -3
- data/lib/tcell_agent/sensor_events/util/utils.rb +4 -3
- data/lib/tcell_agent/servers/puma.rb +2 -2
- data/lib/tcell_agent/servers/unicorn.rb +1 -1
- data/lib/tcell_agent/utils/passwords.rb +28 -0
- data/lib/tcell_agent/version.rb +1 -1
- data/lib/tcell_agent.rb +1 -5
- data/spec/apps/rails-3.2/config/tcell_agent.config +15 -0
- data/spec/apps/rails-3.2/log/development.log +0 -0
- data/spec/apps/rails-3.2/log/test.log +12 -0
- data/spec/apps/rails-4.1/log/test.log +0 -0
- data/spec/lib/tcell_agent/agent/fork_pipe_manager_spec.rb +46 -45
- data/spec/lib/tcell_agent/agent/policy_manager_spec.rb +276 -164
- data/spec/lib/tcell_agent/agent/static_agent_spec.rb +44 -47
- data/spec/lib/tcell_agent/api/api_spec.rb +16 -16
- data/spec/lib/tcell_agent/appsensor/injections_reporter_spec.rb +131 -116
- data/spec/lib/tcell_agent/appsensor/meta_data_spec.rb +55 -51
- data/spec/lib/tcell_agent/cmdi_spec.rb +413 -436
- data/spec/lib/tcell_agent/config/unknown_options_spec.rb +145 -128
- data/spec/lib/tcell_agent/configuration_spec.rb +165 -169
- data/spec/lib/tcell_agent/hooks/login_fraud_spec.rb +144 -153
- data/spec/lib/tcell_agent/instrumentation_spec.rb +84 -85
- data/spec/lib/tcell_agent/patches_spec.rb +70 -111
- data/spec/lib/tcell_agent/policies/appsensor_policy_spec.rb +313 -244
- data/spec/lib/tcell_agent/policies/clickjacking_policy_spec.rb +28 -28
- data/spec/lib/tcell_agent/policies/command_injection_policy_spec.rb +643 -513
- data/spec/lib/tcell_agent/policies/content_security_policy_spec.rb +55 -102
- data/spec/lib/tcell_agent/policies/dataloss_policy_spec.rb +111 -134
- data/spec/lib/tcell_agent/policies/http_redirect_policy_spec.rb +141 -146
- data/spec/lib/tcell_agent/policies/http_tx_policy_spec.rb +8 -8
- data/spec/lib/tcell_agent/policies/login_policy_spec.rb +15 -17
- data/spec/lib/tcell_agent/policies/patches_policy_spec.rb +231 -559
- data/spec/lib/tcell_agent/policies/secure_headers_policy_spec.rb +27 -27
- data/spec/lib/tcell_agent/rails/better_ip_spec.rb +30 -34
- data/spec/lib/tcell_agent/rails/logger_spec.rb +50 -49
- data/spec/lib/tcell_agent/rails/middleware/appsensor_middleware_spec.rb +182 -199
- data/spec/lib/tcell_agent/rails/middleware/dlp_middleware_spec.rb +110 -84
- data/spec/lib/tcell_agent/rails/middleware/global_middleware_spec.rb +107 -85
- data/spec/lib/tcell_agent/rails/middleware/redirect_middleware_spec.rb +68 -40
- data/spec/lib/tcell_agent/rails/middleware/tcell_body_proxy_spec.rb +81 -67
- data/spec/lib/tcell_agent/rails/responses_spec.rb +33 -37
- data/spec/lib/tcell_agent/rails/routes/grape_spec.rb +116 -121
- data/spec/lib/tcell_agent/rails/routes/route_id_spec.rb +25 -28
- data/spec/lib/tcell_agent/rails/routes/routes_spec.rb +87 -85
- data/spec/lib/tcell_agent/rails_spec.rb +1 -6
- data/spec/lib/tcell_agent/rust/models_spec.rb +112 -0
- data/spec/lib/tcell_agent/rust/whisperer_spec.rb +502 -179
- data/spec/lib/tcell_agent/sensor_events/appsensor_meta_event_spec.rb +44 -33
- data/spec/lib/tcell_agent/sensor_events/dlp_spec.rb +4 -4
- data/spec/lib/tcell_agent/sensor_events/sessions_metric_spec.rb +183 -169
- data/spec/lib/tcell_agent/sensor_events/util/sanitizer_utilities_spec.rb +25 -25
- data/spec/lib/tcell_agent/utils/bounded_queue_spec.rb +17 -20
- data/spec/lib/tcell_agent/utils/params_spec.rb +28 -28
- data/spec/lib/tcell_agent/utils/passwords_spec.rb +143 -0
- data/spec/lib/tcell_agent/utils/strings_spec.rb +35 -35
- data/spec/lib/tcell_agent_spec.rb +8 -8
- data/spec/spec_helper.rb +4 -4
- data/spec/support/middleware_helper.rb +10 -10
- data/spec/support/static_agent_overrides.rb +16 -12
- data/tcell_agent.gemspec +17 -33
- metadata +43 -198
- data/LICENSE_libinjection +0 -32
- data/Readme.txt +0 -7
- data/ext/libinjection/extconf.rb +0 -3
- data/ext/libinjection/libinjection.h +0 -65
- data/ext/libinjection/libinjection_html5.c +0 -847
- data/ext/libinjection/libinjection_html5.h +0 -54
- data/ext/libinjection/libinjection_sqli.c +0 -2317
- data/ext/libinjection/libinjection_sqli.h +0 -295
- data/ext/libinjection/libinjection_sqli_data.h +0 -9004
- data/ext/libinjection/libinjection_wrap.c +0 -3525
- data/ext/libinjection/libinjection_xss.c +0 -531
- data/ext/libinjection/libinjection_xss.h +0 -21
- data/lib/tcell_agent/appsensor/injections_matcher.rb +0 -155
- data/lib/tcell_agent/appsensor/rules/appsensor_rule_manager.rb +0 -49
- data/lib/tcell_agent/appsensor/rules/appsensor_rule_set.rb +0 -67
- data/lib/tcell_agent/appsensor/rules/baserules.json +0 -467
- data/lib/tcell_agent/patches/block_rule.rb +0 -93
- data/lib/tcell_agent/patches/sensors_matcher.rb +0 -31
- data/lib/tcell_agent/policies/appsensor/cmdi_sensor.rb +0 -23
- data/lib/tcell_agent/policies/appsensor/fpt_sensor.rb +0 -23
- data/lib/tcell_agent/policies/appsensor/injection_sensor.rb +0 -117
- data/lib/tcell_agent/policies/appsensor/nullbyte_sensor.rb +0 -26
- data/lib/tcell_agent/policies/appsensor/retr_sensor.rb +0 -22
- data/lib/tcell_agent/policies/appsensor/sqli_sensor.rb +0 -34
- data/lib/tcell_agent/policies/appsensor/xss_sensor.rb +0 -34
- data/lib/tcell_agent/policies/appsensor_policy.rb +0 -49
- data/lib/tcell_agent/policies/command_injection_policy.rb +0 -196
- data/lib/tcell_agent/policies/honeytokens_policy.rb +0 -69
- data/lib/tcell_agent/policies/patches_policy.rb +0 -84
- data/lib/tcell_agent/rust/libtcellagent-0.6.1.dylib +0 -0
- data/lib/tcell_agent/rust/tcellagent-0.6.1.dll +0 -0
- data/spec/apps/rails-3.2/Gemfile +0 -25
- data/spec/apps/rails-3.2/Gemfile.lock +0 -126
- data/spec/apps/rails-3.2/Rakefile +0 -7
- data/spec/apps/rails-3.2/app/assets/images/rails.png +0 -0
- data/spec/apps/rails-3.2/app/assets/javascripts/application.js +0 -15
- data/spec/apps/rails-3.2/app/assets/stylesheets/application.css +0 -13
- data/spec/apps/rails-3.2/app/controllers/application_controller.rb +0 -3
- data/spec/apps/rails-3.2/app/controllers/t_cell_app_controller.rb +0 -5
- data/spec/apps/rails-3.2/app/helpers/application_helper.rb +0 -2
- data/spec/apps/rails-3.2/app/views/layouts/application.html.erb +0 -14
- data/spec/apps/rails-3.2/app/views/t_cell_app/index.html.erb +0 -1
- data/spec/apps/rails-3.2/config/application.rb +0 -63
- data/spec/apps/rails-3.2/config/boot.rb +0 -6
- data/spec/apps/rails-3.2/config/environment.rb +0 -5
- data/spec/apps/rails-3.2/config/environments/test.rb +0 -37
- data/spec/apps/rails-3.2/config/routes.rb +0 -11
- data/spec/apps/rails-3.2/config.ru +0 -4
- data/spec/apps/rails-4.1/Gemfile +0 -7
- data/spec/apps/rails-4.1/Gemfile.lock +0 -114
- data/spec/apps/rails-4.1/Rakefile +0 -6
- data/spec/apps/rails-4.1/app/assets/javascripts/application.js +0 -16
- data/spec/apps/rails-4.1/app/assets/stylesheets/application.css +0 -15
- data/spec/apps/rails-4.1/app/controllers/application_controller.rb +0 -5
- data/spec/apps/rails-4.1/app/controllers/t_cell_app_controller.rb +0 -5
- data/spec/apps/rails-4.1/app/helpers/application_helper.rb +0 -2
- data/spec/apps/rails-4.1/app/views/layouts/application.html.erb +0 -14
- data/spec/apps/rails-4.1/app/views/t_cell_app/index.html.erb +0 -1
- data/spec/apps/rails-4.1/config/application.rb +0 -24
- data/spec/apps/rails-4.1/config/boot.rb +0 -4
- data/spec/apps/rails-4.1/config/environment.rb +0 -5
- data/spec/apps/rails-4.1/config/environments/test.rb +0 -41
- data/spec/apps/rails-4.1/config/initializers/assets.rb +0 -8
- data/spec/apps/rails-4.1/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/apps/rails-4.1/config/initializers/cookies_serializer.rb +0 -3
- data/spec/apps/rails-4.1/config/initializers/filter_parameter_logging.rb +0 -4
- data/spec/apps/rails-4.1/config/initializers/inflections.rb +0 -16
- data/spec/apps/rails-4.1/config/initializers/mime_types.rb +0 -4
- data/spec/apps/rails-4.1/config/initializers/session_store.rb +0 -3
- data/spec/apps/rails-4.1/config/initializers/wrap_parameters.rb +0 -14
- data/spec/apps/rails-4.1/config/locales/en.yml +0 -23
- data/spec/apps/rails-4.1/config/routes.rb +0 -12
- data/spec/apps/rails-4.1/config/secrets.yml +0 -22
- data/spec/apps/rails-4.1/config.ru +0 -4
- data/spec/controllers/application_controller.rb +0 -12
- data/spec/lib/tcell_agent/appsensor/injections_matcher_spec.rb +0 -522
- data/spec/lib/tcell_agent/appsensor/rules/appsensor_rule_manager_spec.rb +0 -23
- data/spec/lib/tcell_agent/appsensor/rules/appsensor_rule_set_spec.rb +0 -159
- data/spec/lib/tcell_agent/patches/block_rule_spec.rb +0 -458
- data/spec/lib/tcell_agent/patches/sensors_matcher_spec.rb +0 -35
- data/spec/lib/tcell_agent/policies/appsensor/cmdi_sensor_spec.rb +0 -139
- data/spec/lib/tcell_agent/policies/appsensor/fpt_sensor_spec.rb +0 -139
- data/spec/lib/tcell_agent/policies/appsensor/nullbyte_sensor_spec.rb +0 -167
- data/spec/lib/tcell_agent/policies/appsensor/retr_sensor_spec.rb +0 -139
- data/spec/lib/tcell_agent/policies/appsensor/sqli_sensor_spec.rb +0 -246
- data/spec/lib/tcell_agent/policies/appsensor/xss_sensor_spec.rb +0 -882
- data/spec/lib/tcell_agent/policies/honeytokens_policy_spec.rb +0 -22
|
@@ -4,29 +4,46 @@ require 'tcell_agent/sensor_events/login_fraud'
|
|
|
4
4
|
module TCellAgent
|
|
5
5
|
module Hooks
|
|
6
6
|
module LoginFraud
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
def self.report_login_event(status,
|
|
8
|
+
env_or_header_keys,
|
|
9
|
+
tcell_data,
|
|
10
|
+
user_id,
|
|
11
|
+
password,
|
|
12
|
+
user_valid)
|
|
13
|
+
if TCellAgent.configuration.enabled &&
|
|
14
|
+
TCellAgent.configuration.should_intercept_requests?
|
|
10
15
|
login_fraud_policy = TCellAgent.policy(TCellAgent::PolicyTypes::LoginFraud)
|
|
11
16
|
|
|
12
|
-
if
|
|
17
|
+
if login_fraud_policy && login_fraud_policy.enabled
|
|
13
18
|
if tcell_data
|
|
14
|
-
if ![TCellAgent::Hooks::V1::Login::LOGIN_FAILURE,
|
|
19
|
+
if ![TCellAgent::Hooks::V1::Login::LOGIN_FAILURE,
|
|
20
|
+
TCellAgent::Hooks::V1::Login::LOGIN_SUCCESS].include?(status)
|
|
15
21
|
TCellAgent.logger.error("Unkown login status: #{status}")
|
|
16
|
-
|
|
22
|
+
|
|
23
|
+
elsif (status == TCellAgent::Hooks::V1::Login::LOGIN_FAILURE) &&
|
|
24
|
+
login_fraud_policy.login_failed_enabled
|
|
17
25
|
TCellAgent.send_event(
|
|
18
|
-
TCellAgent::SensorEvents::LoginFailure.new(env_or_header_keys,
|
|
26
|
+
TCellAgent::SensorEvents::LoginFailure.new(env_or_header_keys,
|
|
27
|
+
tcell_data,
|
|
28
|
+
user_id,
|
|
29
|
+
password,
|
|
30
|
+
user_valid)
|
|
19
31
|
)
|
|
20
|
-
|
|
32
|
+
|
|
33
|
+
elsif (status == TCellAgent::Hooks::V1::Login::LOGIN_SUCCESS) &&
|
|
34
|
+
login_fraud_policy.login_success_enabled
|
|
21
35
|
TCellAgent.send_event(
|
|
22
|
-
TCellAgent::SensorEvents::LoginSuccess.new(env_or_header_keys,
|
|
36
|
+
TCellAgent::SensorEvents::LoginSuccess.new(env_or_header_keys,
|
|
37
|
+
tcell_data,
|
|
38
|
+
user_id,
|
|
39
|
+
password,
|
|
40
|
+
user_valid)
|
|
23
41
|
)
|
|
24
42
|
end
|
|
25
43
|
end
|
|
26
44
|
end
|
|
27
45
|
end
|
|
28
46
|
end
|
|
29
|
-
|
|
30
47
|
end
|
|
31
48
|
end
|
|
32
49
|
end
|
|
@@ -34,15 +51,24 @@ end
|
|
|
34
51
|
if defined?(TCellAgent::Hooks::V1::Frameworks::Rails::Login)
|
|
35
52
|
TCellAgent::Hooks::V1::Frameworks::Rails::Login.module_eval do
|
|
36
53
|
class << self
|
|
37
|
-
|
|
38
54
|
alias_method :tcell_register_login_event, :register_login_event
|
|
39
|
-
def register_login_event(status,
|
|
40
|
-
|
|
55
|
+
def register_login_event(status,
|
|
56
|
+
rails_request,
|
|
57
|
+
user_id,
|
|
58
|
+
user_valid = nil,
|
|
59
|
+
password = nil)
|
|
60
|
+
TCellAgent::Instrumentation.safe_block('Rails Auth Hooks') do
|
|
41
61
|
tcell_data = rails_request.env[TCellAgent::Instrumentation::TCELL_ID]
|
|
42
|
-
TCellAgent::Hooks::LoginFraud.report_login_event(
|
|
62
|
+
TCellAgent::Hooks::LoginFraud.report_login_event(
|
|
63
|
+
status,
|
|
64
|
+
rails_request.env,
|
|
65
|
+
tcell_data,
|
|
66
|
+
user_id,
|
|
67
|
+
password,
|
|
68
|
+
user_valid
|
|
69
|
+
)
|
|
43
70
|
end
|
|
44
71
|
end
|
|
45
|
-
|
|
46
72
|
end
|
|
47
73
|
end
|
|
48
74
|
end
|
|
@@ -50,19 +76,18 @@ end
|
|
|
50
76
|
if defined?(TCellAgent::Hooks::V1::Login)
|
|
51
77
|
TCellAgent::Hooks::V1::Login.module_eval do
|
|
52
78
|
class << self
|
|
53
|
-
|
|
54
79
|
alias_method :tcell_register_login_event, :register_login_event
|
|
55
|
-
def register_login_event(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
TCellAgent::Instrumentation.safe_block(
|
|
80
|
+
def register_login_event(status,
|
|
81
|
+
session_id,
|
|
82
|
+
user_agent,
|
|
83
|
+
referrer,
|
|
84
|
+
remote_address,
|
|
85
|
+
header_keys,
|
|
86
|
+
user_id,
|
|
87
|
+
document_uri,
|
|
88
|
+
user_valid = nil,
|
|
89
|
+
password = nil)
|
|
90
|
+
TCellAgent::Instrumentation.safe_block('Login Auth Hooks') do
|
|
66
91
|
tcell_data = TCellAgent::Instrumentation::TCellData.new
|
|
67
92
|
tcell_data.user_agent = user_agent
|
|
68
93
|
tcell_data.referrer = referrer
|
|
@@ -70,10 +95,14 @@ if defined?(TCellAgent::Hooks::V1::Login)
|
|
|
70
95
|
tcell_data.path = document_uri
|
|
71
96
|
tcell_data.hmac_session_id = TCellAgent::SensorEvents::Util.hmac(session_id)
|
|
72
97
|
|
|
73
|
-
TCellAgent::Hooks::LoginFraud.report_login_event(status,
|
|
98
|
+
TCellAgent::Hooks::LoginFraud.report_login_event(status,
|
|
99
|
+
header_keys,
|
|
100
|
+
tcell_data,
|
|
101
|
+
user_id,
|
|
102
|
+
password,
|
|
103
|
+
user_valid)
|
|
74
104
|
end
|
|
75
105
|
end
|
|
76
|
-
|
|
77
106
|
end
|
|
78
107
|
end
|
|
79
108
|
end
|
|
@@ -59,10 +59,11 @@ module TCellAgent
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
class TCellData
|
|
62
|
-
attr_accessor :transaction_id, :session_id, :hmac_session_id, :user_id,
|
|
63
|
-
:
|
|
64
|
-
:
|
|
65
|
-
:
|
|
62
|
+
attr_accessor :transaction_id, :session_id, :hmac_session_id, :user_id,
|
|
63
|
+
:password, :route_id, :path, :uri, :fullpath, :context_filters_by_term,
|
|
64
|
+
:database_filters, :ip_address, :user_agent, :request_method,
|
|
65
|
+
:path_parameters, :patches_blocking_triggered, :grape_mount_endpoint,
|
|
66
|
+
:referrer, :csrf_exception_name, :sql_exceptions, :database_result_sizes
|
|
66
67
|
|
|
67
68
|
def self.filterx(sanitize_string, event_flag, replace_flag, term)
|
|
68
69
|
send_event = false
|
|
@@ -79,13 +80,13 @@ module TCellAgent
|
|
|
79
80
|
return send_event
|
|
80
81
|
end
|
|
81
82
|
def initialize
|
|
82
|
-
@
|
|
83
|
+
@patches_blocking_triggered = false
|
|
83
84
|
@context_filters_by_term = Hash.new{|h,k| h[k] = Set.new}
|
|
84
85
|
@sql_exceptions = []
|
|
85
86
|
@database_result_sizes = []
|
|
86
87
|
end
|
|
87
88
|
def is_valid_term?(term)
|
|
88
|
-
if term != nil && term != ''
|
|
89
|
+
if term != nil && term != '' && term.to_s.length >= 5
|
|
89
90
|
return true
|
|
90
91
|
end
|
|
91
92
|
return false
|
|
@@ -113,7 +114,7 @@ module TCellAgent
|
|
|
113
114
|
|
|
114
115
|
def filter_body!(body)
|
|
115
116
|
dlp_policy = TCellAgent.policy(TCellAgent::PolicyTypes::DataLoss)
|
|
116
|
-
if dlp_policy
|
|
117
|
+
if dlp_policy && self.session_id
|
|
117
118
|
session_id_actions = dlp_policy.get_actions_for_session_id
|
|
118
119
|
if session_id_actions
|
|
119
120
|
send_flag = TCellData.filterx(body, session_id_actions.body_event, session_id_actions.body_redact, self.session_id)
|
|
@@ -160,7 +161,7 @@ module TCellAgent
|
|
|
160
161
|
|
|
161
162
|
def filter_log(log_msg)
|
|
162
163
|
dlp_policy = TCellAgent.policy(TCellAgent::PolicyTypes::DataLoss)
|
|
163
|
-
if dlp_policy
|
|
164
|
+
if dlp_policy && self.session_id
|
|
164
165
|
session_id_actions = dlp_policy.get_actions_for_session_id
|
|
165
166
|
if session_id_actions
|
|
166
167
|
send_flag = TCellData.filterx(log_msg, session_id_actions.log_event, session_id_actions.log_redact, self.session_id)
|
|
@@ -224,7 +225,7 @@ module TCellAgent
|
|
|
224
225
|
begin
|
|
225
226
|
block.call()
|
|
226
227
|
|
|
227
|
-
rescue
|
|
228
|
+
rescue StandardError => ex
|
|
228
229
|
TCellAgent.logger.debug "Exception in safe_block #{message}: #{ex.class} happened, message is #{ex.message}"
|
|
229
230
|
TCellAgent.logger.debug(ex.backtrace)
|
|
230
231
|
end
|
|
@@ -233,7 +234,7 @@ module TCellAgent
|
|
|
233
234
|
def self.safe_block_no_log(message, &block)
|
|
234
235
|
begin
|
|
235
236
|
block.call()
|
|
236
|
-
rescue
|
|
237
|
+
rescue StandardError
|
|
237
238
|
end
|
|
238
239
|
end
|
|
239
240
|
end
|
data/lib/tcell_agent/logger.rb
CHANGED
|
@@ -96,7 +96,7 @@ module TCellAgent
|
|
|
96
96
|
|
|
97
97
|
log_device = TCellLogDevice.new(
|
|
98
98
|
TCellAgent.configuration.appfirewall_payloads_log_filename,
|
|
99
|
-
shift_age
|
|
99
|
+
:shift_age => 9, :shift_size => 5242880
|
|
100
100
|
)
|
|
101
101
|
@payloads_logger = Logger.new(log_device)
|
|
102
102
|
@payloads_logger.level = Logger::INFO
|
|
@@ -138,7 +138,7 @@ module TCellAgent
|
|
|
138
138
|
logging_directory = File.dirname(logging_file)
|
|
139
139
|
TCellAgent::Utils::IO.create_directory(logging_directory, TCellAgent.configuration.agent_home_owner.to_s)
|
|
140
140
|
|
|
141
|
-
log_device = TCellLogDevice.new(logging_file, shift_age
|
|
141
|
+
log_device = TCellLogDevice.new(logging_file, :shift_age => 9, :shift_size => 5242880)
|
|
142
142
|
|
|
143
143
|
level = loggingLevelFromString(logging_options[:level] || logging_options["level"])
|
|
144
144
|
# limit the total log file to about 9 * 5 = 45 mb
|
|
@@ -9,21 +9,20 @@ module TCellAgent
|
|
|
9
9
|
module Patches
|
|
10
10
|
|
|
11
11
|
class MetaData < TCellAgent::AppSensor::MetaData
|
|
12
|
-
|
|
13
12
|
class << self
|
|
14
13
|
def build(request)
|
|
15
14
|
tcell_context = request.env[TCellAgent::Instrumentation::TCELL_ID]
|
|
16
15
|
meta_event = MetaData.new(
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
tcell_context.request_method,
|
|
17
|
+
tcell_context.ip_address,
|
|
19
18
|
tcell_context.route_id,
|
|
20
19
|
tcell_context.hmac_session_id,
|
|
21
20
|
tcell_context.user_id,
|
|
22
|
-
tcell_context.transaction_id
|
|
21
|
+
tcell_context.transaction_id,
|
|
22
|
+
tcell_context.uri
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
meta_event.path =
|
|
26
|
-
meta_event.user_agent = request.env['HTTP_USER_AGENT']
|
|
25
|
+
meta_event.path = tcell_context.path
|
|
27
26
|
|
|
28
27
|
meta_event.get_dict = request.GET
|
|
29
28
|
meta_event.cookie_dict = request.cookies
|
|
@@ -31,9 +30,6 @@ module TCellAgent
|
|
|
31
30
|
|
|
32
31
|
meta_event.post_dict = request.POST
|
|
33
32
|
|
|
34
|
-
meta_event.path_parameters = request.env[TCellAgent::Instrumentation::TCELL_ID].path_parameters
|
|
35
|
-
|
|
36
|
-
|
|
37
33
|
# Positions strio to the beginning of input, resetting lineno to zero.
|
|
38
34
|
# rails 4.1 seems to read the stringIO directly and so body.gets is empty
|
|
39
35
|
# this is called
|
|
@@ -50,14 +46,14 @@ module TCellAgent
|
|
|
50
46
|
end
|
|
51
47
|
end
|
|
52
48
|
|
|
53
|
-
attr_accessor :path, :request_content_bytes_len
|
|
49
|
+
attr_accessor :path, :request_content_bytes_len
|
|
54
50
|
|
|
55
|
-
def initialize(method, remote_address, route_id, session_id, user_id, transaction_id)
|
|
56
|
-
super(method, remote_address, route_id, session_id, user_id, transaction_id)
|
|
51
|
+
def initialize(method, remote_address, route_id, session_id, user_id, transaction_id, location)
|
|
52
|
+
super(method, remote_address, route_id, session_id, user_id, transaction_id, location)
|
|
57
53
|
|
|
58
54
|
@request_content_bytes_len = 0
|
|
59
|
-
@user_agent = nil
|
|
60
55
|
end
|
|
61
56
|
end
|
|
57
|
+
|
|
62
58
|
end
|
|
63
59
|
end
|
data/lib/tcell_agent/patches.rb
CHANGED
|
@@ -3,22 +3,19 @@ module TCellAgent
|
|
|
3
3
|
|
|
4
4
|
module Patches
|
|
5
5
|
def self.block?(request)
|
|
6
|
-
TCellAgent::Instrumentation.safe_block("Checking
|
|
7
|
-
|
|
6
|
+
TCellAgent::Instrumentation.safe_block("Checking patches blocking") do
|
|
7
|
+
rust_policies = TCellAgent.policy(TCellAgent::PolicyTypes::Rust)
|
|
8
8
|
|
|
9
|
-
if
|
|
9
|
+
if rust_policies && rust_policies.patches_enabled
|
|
10
10
|
meta_data = TCellAgent::Patches::MetaData.build(request)
|
|
11
|
-
|
|
11
|
+
block_request = rust_policies.block_request?(meta_data)
|
|
12
|
+
request.env[TCellAgent::Instrumentation::TCELL_ID].patches_blocking_triggered = block_request
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
request.env[TCellAgent::Instrumentation::TCELL_ID].ip_blocking_triggered = true
|
|
15
|
-
|
|
16
|
-
return resp
|
|
17
|
-
end
|
|
14
|
+
return block_request
|
|
18
15
|
end
|
|
19
16
|
end
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
false
|
|
22
19
|
end
|
|
23
20
|
end
|
|
24
21
|
|
|
@@ -18,10 +18,10 @@ module TCellAgent
|
|
|
18
18
|
if !(type && value)
|
|
19
19
|
raise "Type and value were not set"
|
|
20
20
|
end
|
|
21
|
-
if type.
|
|
21
|
+
if type.casecmp("content-security-policy") == 0
|
|
22
22
|
type = "csp"
|
|
23
23
|
end
|
|
24
|
-
if
|
|
24
|
+
if !@@approved_headers.include?(type.downcase)
|
|
25
25
|
raise "Type was not included in approved_headers"
|
|
26
26
|
end
|
|
27
27
|
if value != value.gsub(/[^\p{L}\w\d\-_\ :\/,;.'\*"%?@#=$]/,'')
|
|
@@ -52,7 +52,7 @@ module TCellAgent
|
|
|
52
52
|
end
|
|
53
53
|
report_uri = uri.to_s
|
|
54
54
|
return "#{self.raw_value}; report-uri #{report_uri}"
|
|
55
|
-
rescue
|
|
55
|
+
rescue StandardError => e
|
|
56
56
|
return self.raw_value
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -91,8 +91,7 @@ module TCellAgent
|
|
|
91
91
|
begin
|
|
92
92
|
csp_header = ContentSecurityPolicyHeader.new(header["name"], header["value"], header["report-uri"])
|
|
93
93
|
csp_headers.push(csp_header)
|
|
94
|
-
rescue
|
|
95
|
-
#pass
|
|
94
|
+
rescue StandardError
|
|
96
95
|
end
|
|
97
96
|
end
|
|
98
97
|
end
|
|
@@ -23,12 +23,12 @@ module TCellAgent
|
|
|
23
23
|
if !(type && value)
|
|
24
24
|
raise "Type and value were not set"
|
|
25
25
|
end
|
|
26
|
-
if type.
|
|
26
|
+
if type.casecmp("content-security-policy") == 0
|
|
27
27
|
type = "csp"
|
|
28
|
-
elsif type.
|
|
28
|
+
elsif type.casecmp("content-security-policy-report-only") == 0
|
|
29
29
|
type = "csp-report"
|
|
30
30
|
end
|
|
31
|
-
if
|
|
31
|
+
if !@@approved_headers.include?(type.downcase)
|
|
32
32
|
raise "Type was not included in approved_headers"
|
|
33
33
|
end
|
|
34
34
|
if value != value.gsub(/[^\p{L}\w\d\-_\ :\/,;.'\*"%?@#=$]/,'')
|
|
@@ -51,7 +51,7 @@ module TCellAgent
|
|
|
51
51
|
if transaction_id
|
|
52
52
|
new_query_ar << ["tid", transaction_id]
|
|
53
53
|
end
|
|
54
|
-
if session_id
|
|
54
|
+
if session_id && session_id.length > 0
|
|
55
55
|
new_query_ar << ["sid", session_id]
|
|
56
56
|
end
|
|
57
57
|
if route_id
|
|
@@ -71,7 +71,7 @@ module TCellAgent
|
|
|
71
71
|
report_uri = report_uri + "c=" + checksum.to_s
|
|
72
72
|
end
|
|
73
73
|
return "#{self.raw_value}; report-uri #{report_uri}"
|
|
74
|
-
rescue
|
|
74
|
+
rescue StandardError
|
|
75
75
|
return self.raw_value
|
|
76
76
|
end
|
|
77
77
|
end
|
|
@@ -122,8 +122,6 @@ module TCellAgent
|
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
if policy_json.has_key?("headers")
|
|
125
|
-
TCellAgent.configuration.js_agent_url = TCellAgent.configuration.startup_js_agent_url
|
|
126
|
-
|
|
127
125
|
headers = policy_json["headers"]
|
|
128
126
|
csp_headers = []
|
|
129
127
|
|
|
@@ -131,13 +129,9 @@ module TCellAgent
|
|
|
131
129
|
headers.each do |header|
|
|
132
130
|
if header.has_key?("name") && header.has_key?("value")
|
|
133
131
|
begin
|
|
134
|
-
if TCellAgent.configuration.startup_js_agent_url == "https://api.tcell.io/tcellagent.min.js" && header["value"] =~ /https:\/\/jsagent.tcell.io/
|
|
135
|
-
TCellAgent.configuration.js_agent_url = "https://jsagent.tcell.io/tcellagent.min.js"
|
|
136
|
-
end
|
|
137
|
-
|
|
138
132
|
csp_header = ContentSecurityPolicyHeader.new(header["name"], header["value"], header["report-uri"], csp.policy_id)
|
|
139
133
|
csp_headers.push(csp_header)
|
|
140
|
-
rescue
|
|
134
|
+
rescue StandardError
|
|
141
135
|
end
|
|
142
136
|
end
|
|
143
137
|
end
|
|
@@ -88,7 +88,7 @@ module TCellAgent
|
|
|
88
88
|
get_actions_for_request(RequestProtectionManager::FORM, parameter_name.downcase, route_id)
|
|
89
89
|
end
|
|
90
90
|
def get_actions_for_request(context, variable, route_id=nil)
|
|
91
|
-
if (context == nil
|
|
91
|
+
if (context == nil || variable == nil)
|
|
92
92
|
return nil
|
|
93
93
|
end
|
|
94
94
|
if (route_id == nil)
|
|
@@ -262,7 +262,7 @@ module TCellAgent
|
|
|
262
262
|
actions = protection_json.fetch("actions",{})
|
|
263
263
|
filter_actions = DataLossPolicy.actions_from_json(actions)
|
|
264
264
|
_route_ids = ["*"]
|
|
265
|
-
if
|
|
265
|
+
if scope != nil && scope != "global"
|
|
266
266
|
if scope=="route"
|
|
267
267
|
_route_ids = protection_json.fetch("route_ids",[])
|
|
268
268
|
end
|
|
@@ -64,7 +64,7 @@ module TCellAgent
|
|
|
64
64
|
nil)
|
|
65
65
|
|
|
66
66
|
TCellAgent.send_event(event)
|
|
67
|
-
rescue
|
|
67
|
+
rescue StandardError => ie
|
|
68
68
|
TCellAgent.logger.error("uncaught exception while creating redirect event: #{ie.message}")
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -91,7 +91,7 @@ module TCellAgent
|
|
|
91
91
|
policy_data_json = policy_json["data"]
|
|
92
92
|
http_redirect_policy.enabled = policy_data_json.fetch("enabled", false)
|
|
93
93
|
http_redirect_policy.block = policy_data_json.fetch("block", false)
|
|
94
|
-
http_redirect_policy.data_scheme_allowed = policy_data_json.fetch("
|
|
94
|
+
http_redirect_policy.data_scheme_allowed = policy_data_json.fetch("data_scheme_allowed", false)
|
|
95
95
|
|
|
96
96
|
http_redirect_policy.whitelist = []
|
|
97
97
|
policy_data_json.fetch("whitelist", []).each do |regex_pattern|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'tcell_agent/appsensor/injections_reporter'
|
|
2
|
+
require 'tcell_agent/instrumentation'
|
|
3
|
+
require 'tcell_agent/policies/policy'
|
|
4
|
+
require 'tcell_agent/rust/models'
|
|
5
|
+
require 'tcell_agent/rust/whisperer'
|
|
6
|
+
require 'tcell_agent/sensor_events/command_injection'
|
|
7
|
+
require 'tcell_agent/sensor_events/patches'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module TCellAgent
|
|
11
|
+
module Policies
|
|
12
|
+
|
|
13
|
+
class RustPolicies < Policy
|
|
14
|
+
|
|
15
|
+
attr_reader :appfirewall_enabled, :patches_enabled, :cmdi_enabled
|
|
16
|
+
|
|
17
|
+
def initialize()
|
|
18
|
+
@appfirewall_enabled = false
|
|
19
|
+
@patches_enabled = false
|
|
20
|
+
@cmdi_enabled = false
|
|
21
|
+
@agent_ptr = nil
|
|
22
|
+
|
|
23
|
+
whisper = TCellAgent::Rust::Whisperer.create_agent()
|
|
24
|
+
if whisper["error"]
|
|
25
|
+
TCellAgent.logger.error("Error initializing policies: #{whisper["error"]}")
|
|
26
|
+
else
|
|
27
|
+
@agent_ptr = whisper["agent_ptr"]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update_policies(policies_json)
|
|
32
|
+
return unless @agent_ptr && policies_json
|
|
33
|
+
|
|
34
|
+
whisper = TCellAgent::Rust::Whisperer.update_policies(@agent_ptr, {"result" => policies_json})
|
|
35
|
+
if whisper["errors"]
|
|
36
|
+
whisper["errors"].each do |error|
|
|
37
|
+
TCellAgent.logger.error("Error updating policies: #{error}")
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
enablements = whisper["enablements"]
|
|
41
|
+
@appfirewall_enabled = enablements["appfirewall"]
|
|
42
|
+
@patches_enabled = enablements["patches"]
|
|
43
|
+
@cmdi_enabled = enablements["cmdi"]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def block_request?(appsensor_meta)
|
|
48
|
+
return false unless @agent_ptr && @patches_enabled
|
|
49
|
+
|
|
50
|
+
whisper = TCellAgent::Rust::Whisperer.apply_patches(@agent_ptr, appsensor_meta)
|
|
51
|
+
if whisper["error"]
|
|
52
|
+
TCellAgent.logger.error("Error processing patches: #{whisper["error"]}")
|
|
53
|
+
else
|
|
54
|
+
response = whisper["apply_response"]
|
|
55
|
+
if response && response["status"] == "Blocked"
|
|
56
|
+
patches_event = TCellAgent::SensorEvents::PatchesEvent.new(response, appsensor_meta)
|
|
57
|
+
TCellAgent.send_event(patches_event)
|
|
58
|
+
return true
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def check_appfirewall_injections(appsensor_meta)
|
|
66
|
+
return unless @agent_ptr && @appfirewall_enabled
|
|
67
|
+
|
|
68
|
+
TCellAgent::Instrumentation.safe_block("AppSensor inspection") do
|
|
69
|
+
whisper = TCellAgent::Rust::Whisperer.apply_appfirewall(@agent_ptr, appsensor_meta)
|
|
70
|
+
TCellAgent::AppSensor::InjectionsReporter.report_and_log(whisper["apply_response"])
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def block_command?(command, tcell_context)
|
|
75
|
+
return false unless @agent_ptr && @cmdi_enabled && TCellAgent.is_it_safe_to_send_cmdi_events?
|
|
76
|
+
|
|
77
|
+
whisper = TCellAgent::Rust::Whisperer.apply_cmdi(@agent_ptr, command)
|
|
78
|
+
apply_response = whisper.fetch("apply_response", {})
|
|
79
|
+
cmdi_event =
|
|
80
|
+
TCellAgent::SensorEvents::CommandInjectionEvent.build_from_native_lib_response_and_tcell_context(apply_response,
|
|
81
|
+
tcell_context)
|
|
82
|
+
if cmdi_event
|
|
83
|
+
TCellAgent.send_event(cmdi_event)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
apply_response.fetch("blocked", false)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -21,7 +21,7 @@ module TCellAgent
|
|
|
21
21
|
if !(name && value)
|
|
22
22
|
raise "Name and value were not set"
|
|
23
23
|
end
|
|
24
|
-
if
|
|
24
|
+
if !@@approved_headers.include?(name.downcase)
|
|
25
25
|
raise "Name was not included in approved_headers"
|
|
26
26
|
end
|
|
27
27
|
if value != value.gsub(/[^\p{L}\w\d\-_\ :\/,;.'\*"%?@#=$]/,'')
|
|
@@ -53,7 +53,7 @@ module TCellAgent
|
|
|
53
53
|
begin
|
|
54
54
|
security_header = SecurityHeader.new(header["name"], header["value"])
|
|
55
55
|
security_headers.push(security_header)
|
|
56
|
-
rescue
|
|
56
|
+
rescue StandardError => secure_header_exception
|
|
57
57
|
TCellAgent.logger.debug("Could not load secure header:" + secure_header_exception.message)
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# See the file "LICENSE" for the full license governing this code.
|
|
2
|
-
|
|
3
1
|
if TCellAgent.configuration.should_instrument_authlogic?
|
|
4
2
|
|
|
5
3
|
require 'tcell_agent/logger'
|
|
@@ -8,56 +6,76 @@ if TCellAgent.configuration.should_instrument_authlogic?
|
|
|
8
6
|
|
|
9
7
|
module TCellAgent
|
|
10
8
|
if defined?(Authlogic)
|
|
11
|
-
|
|
9
|
+
|
|
10
|
+
TCellAgent.logger.debug('Instrumenting Authlogic')
|
|
11
|
+
|
|
12
12
|
require 'tcell_agent/agent'
|
|
13
13
|
require 'tcell_agent/sensor_events/login_fraud'
|
|
14
|
+
|
|
14
15
|
Authlogic::Session::Base.class_eval do
|
|
15
|
-
alias_method :
|
|
16
|
+
alias_method :tcell_save, :save
|
|
16
17
|
def save(&block)
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
if TCellAgent.configuration.enabled &&
|
|
19
|
+
TCellAgent.configuration.should_intercept_requests?
|
|
20
|
+
|
|
21
|
+
user_logged_in_before = !user.nil?
|
|
22
|
+
success = tcell_save(&block)
|
|
23
|
+
user_logged_in_after = !user.nil?
|
|
22
24
|
|
|
25
|
+
TCellAgent::Instrumentation.safe_block('Authlogic login info') do
|
|
23
26
|
login_fraud_policy = TCellAgent.policy(TCellAgent::PolicyTypes::LoginFraud)
|
|
24
|
-
if
|
|
27
|
+
if login_fraud_policy && login_fraud_policy.enabled
|
|
25
28
|
user_id = nil
|
|
26
|
-
TCellAgent::Instrumentation.safe_block(
|
|
29
|
+
TCellAgent::Instrumentation.safe_block('getting userid for login form') do
|
|
27
30
|
user_id = self.send(self.class.login_field.to_sym)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
password = nil
|
|
34
|
+
|
|
35
|
+
if user_logged_in_before && user_logged_in_after
|
|
36
|
+
# password changed or logged in as another user
|
|
37
|
+
|
|
38
|
+
elsif !user_logged_in_before && !user_logged_in_after
|
|
39
|
+
if login_fraud_policy.login_failed_enabled
|
|
33
40
|
request = Authlogic::Session::Base.controller.request
|
|
34
41
|
tcell_data = request.env[TCellAgent::Instrumentation::TCELL_ID]
|
|
35
42
|
if tcell_data
|
|
36
|
-
event = TCellAgent::SensorEvents::LoginFailure.new(
|
|
43
|
+
event = TCellAgent::SensorEvents::LoginFailure.new(
|
|
44
|
+
request.env,
|
|
45
|
+
tcell_data,
|
|
46
|
+
user_id,
|
|
47
|
+
password
|
|
48
|
+
)
|
|
37
49
|
TCellAgent.send_event(event)
|
|
38
50
|
end
|
|
39
51
|
end
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
|
|
53
|
+
elsif !user_logged_in_before && user_logged_in_after
|
|
54
|
+
if login_fraud_policy.login_success_enabled
|
|
42
55
|
request = Authlogic::Session::Base.controller.request
|
|
43
56
|
tcell_data = request.env[TCellAgent::Instrumentation::TCELL_ID]
|
|
44
57
|
if tcell_data
|
|
45
|
-
event = TCellAgent::SensorEvents::LoginSuccess.new(
|
|
58
|
+
event = TCellAgent::SensorEvents::LoginSuccess.new(
|
|
59
|
+
request.env,
|
|
60
|
+
tcell_data,
|
|
61
|
+
user_id,
|
|
62
|
+
password
|
|
63
|
+
)
|
|
46
64
|
TCellAgent.send_event(event)
|
|
47
65
|
end
|
|
48
66
|
end
|
|
49
67
|
end
|
|
50
68
|
end
|
|
51
|
-
|
|
69
|
+
end
|
|
52
70
|
|
|
53
71
|
success
|
|
54
72
|
|
|
55
73
|
else
|
|
56
|
-
|
|
57
|
-
end
|
|
74
|
+
tcell_save(&block)
|
|
75
|
+
end
|
|
58
76
|
end
|
|
59
77
|
end
|
|
60
|
-
end
|
|
78
|
+
end
|
|
61
79
|
end
|
|
62
80
|
|
|
63
81
|
end
|