tcell_agent 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +4 -0
- data/README.md +43 -0
- data/Rakefile +7 -0
- data/bin/tcell_agent +171 -0
- data/config/initializers/authlogic_auth.rb +51 -0
- data/config/initializers/devise_auth.rb +167 -0
- data/config/initializers/init.rb +8 -0
- data/lib/tcell_agent.rb +33 -0
- data/lib/tcell_agent/agent.rb +79 -0
- data/lib/tcell_agent/agent/event_processor.rb +133 -0
- data/lib/tcell_agent/agent/policy_manager.rb +138 -0
- data/lib/tcell_agent/agent/policy_types.rb +42 -0
- data/lib/tcell_agent/agent/static_agent.rb +22 -0
- data/lib/tcell_agent/api.rb +101 -0
- data/lib/tcell_agent/appsensor.rb +42 -0
- data/lib/tcell_agent/appsensor/cmdi.rb +32 -0
- data/lib/tcell_agent/appsensor/path_traversal.rb +33 -0
- data/lib/tcell_agent/appsensor/sqli.rb +55 -0
- data/lib/tcell_agent/appsensor/xss.rb +40 -0
- data/lib/tcell_agent/authlogic.rb +26 -0
- data/lib/tcell_agent/configuration.rb +148 -0
- data/lib/tcell_agent/dataloss.rb +0 -0
- data/lib/tcell_agent/devise.rb +83 -0
- data/lib/tcell_agent/instrumentation.rb +44 -0
- data/lib/tcell_agent/logger.rb +46 -0
- data/lib/tcell_agent/policies/add_script_tag_policy.rb +47 -0
- data/lib/tcell_agent/policies/appsensor_policy.rb +76 -0
- data/lib/tcell_agent/policies/clickjacking_policy.rb +113 -0
- data/lib/tcell_agent/policies/content_security_policy.rb +119 -0
- data/lib/tcell_agent/policies/dataloss_policy.rb +175 -0
- data/lib/tcell_agent/policies/honeytokens_policy.rb +67 -0
- data/lib/tcell_agent/policies/http_redirect_policy.rb +84 -0
- data/lib/tcell_agent/policies/http_tx_policy.rb +60 -0
- data/lib/tcell_agent/policies/login_fraud_policy.rb +42 -0
- data/lib/tcell_agent/policies/secure_headers_policy.rb +64 -0
- data/lib/tcell_agent/rails.rb +146 -0
- data/lib/tcell_agent/rails/devise.rb +0 -0
- data/lib/tcell_agent/rails/dlp.rb +204 -0
- data/lib/tcell_agent/rails/middleware/body_filter_middleware.rb +69 -0
- data/lib/tcell_agent/rails/middleware/context_middleware.rb +50 -0
- data/lib/tcell_agent/rails/middleware/global_middleware.rb +53 -0
- data/lib/tcell_agent/rails/middleware/headers_middleware.rb +176 -0
- data/lib/tcell_agent/rails/routes.rb +130 -0
- data/lib/tcell_agent/rails/settings_reporter.rb +40 -0
- data/lib/tcell_agent/sensor_events/app_config.rb +16 -0
- data/lib/tcell_agent/sensor_events/app_sensor.rb +240 -0
- data/lib/tcell_agent/sensor_events/dlp.rb +58 -0
- data/lib/tcell_agent/sensor_events/honeytokens.rb +16 -0
- data/lib/tcell_agent/sensor_events/login_fraud.rb +43 -0
- data/lib/tcell_agent/sensor_events/metrics.rb +24 -0
- data/lib/tcell_agent/sensor_events/sensor.rb +85 -0
- data/lib/tcell_agent/sensor_events/server_agent.rb +101 -0
- data/lib/tcell_agent/sensor_events/util/redirect_utils.rb +22 -0
- data/lib/tcell_agent/sensor_events/util/sanitizer_utilities.rb +153 -0
- data/lib/tcell_agent/sensor_events/util/utils.rb +21 -0
- data/lib/tcell_agent/sinatra.rb +41 -0
- data/lib/tcell_agent/start_background_thread.rb +63 -0
- data/lib/tcell_agent/userinfo.rb +8 -0
- data/lib/tcell_agent/utils/queue_with_timeout.rb +60 -0
- data/lib/tcell_agent/version.rb +5 -0
- data/spec/controllers/application_controller.rb +12 -0
- data/spec/lib/tcell_agent/api/api_spec.rb +36 -0
- data/spec/lib/tcell_agent/appsensor_spec.rb +66 -0
- data/spec/lib/tcell_agent/policies/add_script_tag_policy_spec.rb +37 -0
- data/spec/lib/tcell_agent/policies/appsensor_policy_spec.rb +40 -0
- data/spec/lib/tcell_agent/policies/clickjacking_policy_spec.rb +71 -0
- data/spec/lib/tcell_agent/policies/content_security_policy_spec.rb +71 -0
- data/spec/lib/tcell_agent/policies/dataloss_policy_spec.rb +88 -0
- data/spec/lib/tcell_agent/policies/honeytokens_policy_spec.rb +22 -0
- data/spec/lib/tcell_agent/policies/http_redirect_policy_spec.rb +62 -0
- data/spec/lib/tcell_agent/policies/http_tx_policy_spec.rb +22 -0
- data/spec/lib/tcell_agent/policies/login_policy_spec.rb +42 -0
- data/spec/lib/tcell_agent/policies/secure_headers_policy_spec.rb +67 -0
- data/spec/lib/tcell_agent/rails/middleware/global_middleware_spec.rb +187 -0
- data/spec/lib/tcell_agent/rails_spec.rb +57 -0
- data/spec/lib/tcell_agent/sensor_events/dlp_spec.rb +14 -0
- data/spec/lib/tcell_agent/sensor_events/util/redirect_utils_spec.rb +25 -0
- data/spec/lib/tcell_agent/sensor_events/util/sanitizer_utilities_spec.rb +57 -0
- data/spec/lib/tcell_agent_spec.rb +22 -0
- data/spec/resources/normal_config.json +13 -0
- data/spec/spec_helper.rb +4 -0
- data/tcell_agent.gemspec +29 -0
- metadata +249 -0
File without changes
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# See the file "LICENSE" for the full license governing this code.
|
2
|
+
|
3
|
+
require 'devise'
|
4
|
+
require 'devise/rails'
|
5
|
+
require 'devise/strategies/database_authenticatable'
|
6
|
+
require 'tcell_agent/userinfo'
|
7
|
+
require 'tcell_agent/logger'
|
8
|
+
#Warden::Manager.after_authentication do |user,auth,opts|
|
9
|
+
# p "<M>M>M>M>M>M>M>M>M>M>M>M>M>M>M>M>M>M>M"
|
10
|
+
# p user
|
11
|
+
# # do something with user
|
12
|
+
#end
|
13
|
+
|
14
|
+
require 'tcell_agent/sensor_events/honeytokens'
|
15
|
+
|
16
|
+
module TCellAgent
|
17
|
+
if defined?(Devise)
|
18
|
+
TCellAgent::UserInformation.class_eval do
|
19
|
+
class << self
|
20
|
+
alias_method :original_getUserFromRequest, :getUserFromRequest
|
21
|
+
def getUserFromRequest(request)
|
22
|
+
orig_user_id = original_getUserFromRequest(request)
|
23
|
+
begin
|
24
|
+
if request.session and request.session.has_key?("warden.user.user.key")
|
25
|
+
userkey = request.session["warden.user.user.key"]
|
26
|
+
if (userkey.length == 2)
|
27
|
+
user_id = userkey[0][0]
|
28
|
+
else
|
29
|
+
user_id = userkey[1][0]
|
30
|
+
end
|
31
|
+
if user_id.is_a? Integer
|
32
|
+
return user_id.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue Exception => e
|
36
|
+
return orig_user_id
|
37
|
+
end
|
38
|
+
return orig_user_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
Devise::Strategies::DatabaseAuthenticatable.class_eval do
|
43
|
+
alias_method :original_authenticate!, :authenticate!
|
44
|
+
def authenticate!
|
45
|
+
begin
|
46
|
+
tcell_background_worker = TCellAgent.thread_agent
|
47
|
+
# if (tcell_background_worker && tcell_background_worker.honeytokens_policy)
|
48
|
+
# credstring = ""
|
49
|
+
# authentication_keys.each do |authentication_key|
|
50
|
+
# credstring = credstring + authentication_hash[authentication_key] + "::"
|
51
|
+
# end
|
52
|
+
# credstring = credstring + password[1..-3] # Chop off first and last 2 characters
|
53
|
+
# token_id = tcell_background_worker.honeytokens_policy.id_for_credentialstring(credstring)
|
54
|
+
# if token_id
|
55
|
+
# begin
|
56
|
+
# event = TCellAgent::SensorEvents::HoneytokensSensorEvent.new(request, token_id)
|
57
|
+
# TCellAgent.send_event(event)
|
58
|
+
# rescue
|
59
|
+
# #pass
|
60
|
+
# end
|
61
|
+
# return
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
rescue Exception => e
|
65
|
+
TCellAgent.logger.error("uncaught exception while processing honeytokens: #{e.message}")
|
66
|
+
end
|
67
|
+
original_authenticate!
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
module Devise
|
73
|
+
module Models
|
74
|
+
module Recoverable
|
75
|
+
extend ActiveSupport::Concern
|
76
|
+
alias_method :original_send_reset_password_instructions, :send_reset_password_instructions
|
77
|
+
def send_reset_password_instructions
|
78
|
+
x = original_send_reset_password_instructions
|
79
|
+
x
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# See the file "LICENSE" for the full license governing this code.
|
3
|
+
require 'rest-client'
|
4
|
+
require 'tcell_agent/logger'
|
5
|
+
require 'tcell_agent/configuration'
|
6
|
+
require 'tcell_agent/version'
|
7
|
+
require 'date'
|
8
|
+
|
9
|
+
module TCellAgent
|
10
|
+
module Instrumentation
|
11
|
+
|
12
|
+
class TCellData
|
13
|
+
attr_accessor :transaction_id
|
14
|
+
attr_accessor :session_id
|
15
|
+
attr_accessor :hmac_session_id
|
16
|
+
attr_accessor :user_id
|
17
|
+
attr_accessor :route_id
|
18
|
+
attr_accessor :uri
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.instrument_frameworks
|
22
|
+
require 'tcell_agent/authlogic' if defined?(Authlogic)
|
23
|
+
require 'tcell_agent/devise' if defined?(Devise)
|
24
|
+
require 'tcell_agent/rails' if defined?(Rails)
|
25
|
+
require 'tcell_agent/sinatra' if defined?(Sinatra)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.safe_block(message, &block)
|
29
|
+
begin
|
30
|
+
block.call()
|
31
|
+
rescue Exception => ex
|
32
|
+
TCellAgent.logger.debug "Exception in safe_block #{message}: #{ex.class} happened, message is #{ex.message}"
|
33
|
+
TCellAgent.logger.debug(ex.backtrace)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.safe_block_no_log(message, &block)
|
38
|
+
begin
|
39
|
+
block.call()
|
40
|
+
rescue Exception => ex
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# See the file "LICENSE" for the full license governing this code.
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'tcell_agent/configuration'
|
5
|
+
|
6
|
+
module TCellAgent
|
7
|
+
def self.loggingLevelFromString(levelString)
|
8
|
+
if (levelString == "DEBUG")
|
9
|
+
return Logger::DEBUG
|
10
|
+
elsif (levelString == "WARN")
|
11
|
+
return Logger::WARN
|
12
|
+
elsif (levelString == "INFO")
|
13
|
+
return Logger::INFO
|
14
|
+
elsif (levelString == "ERROR")
|
15
|
+
return Logger::ERROR
|
16
|
+
elsif (levelString == "FATAL")
|
17
|
+
return Logger::FATAL
|
18
|
+
end
|
19
|
+
return Logger::ERROR
|
20
|
+
end
|
21
|
+
def self.logger
|
22
|
+
if defined?(@logger)
|
23
|
+
return @logger
|
24
|
+
end
|
25
|
+
logging_options = TCellAgent.configuration.logging_options
|
26
|
+
if logging_options && logging_options["enabled"]
|
27
|
+
level = loggingLevelFromString(logging_options["level"])
|
28
|
+
logging_file = logging_options["filename"] || TCellAgent.configuration.default_log_filename
|
29
|
+
# limit the total log file to about 9 * 5 = 45 mb
|
30
|
+
@logger = Logger.new(logging_file, shift_age=9, shift_size=5242880)
|
31
|
+
@logger.level = level
|
32
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
33
|
+
# ISO 8601 format
|
34
|
+
date_format = datetime.strftime("%Y-%m-%d %H:%M:%S,%L%z")
|
35
|
+
"[#{date_format}] #{severity}: #{msg}\n"
|
36
|
+
end
|
37
|
+
return @logger
|
38
|
+
end
|
39
|
+
logger = Logger.new(TCellAgent.configuration.default_log_filename)
|
40
|
+
logger.level = Logger::ERROR
|
41
|
+
return logger
|
42
|
+
end
|
43
|
+
def self.logger=(logger)
|
44
|
+
@logger = logger
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'tcell_agent/configuration'
|
2
|
+
|
3
|
+
module TCellAgent
|
4
|
+
module Policies
|
5
|
+
class AddScriptTagPolicy
|
6
|
+
attr_accessor :enabled
|
7
|
+
attr_accessor :policy_id
|
8
|
+
attr_accessor :js_agent_api_key
|
9
|
+
def initialize
|
10
|
+
self.init_options
|
11
|
+
end
|
12
|
+
def init_options
|
13
|
+
@enabled = false
|
14
|
+
@policy_id = nil
|
15
|
+
@js_agent_api_key = nil
|
16
|
+
end
|
17
|
+
def js_agent_app_id
|
18
|
+
return TCellAgent.configuration.app_id
|
19
|
+
end
|
20
|
+
def js_agent_api_base_url
|
21
|
+
return TCellAgent.configuration.js_agent_api_base_url
|
22
|
+
end
|
23
|
+
def js_agent_url
|
24
|
+
return TCellAgent.configuration.js_agent_url
|
25
|
+
end
|
26
|
+
def self.fromJson(policy_json)
|
27
|
+
if (!policy_json)
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
policy = AddScriptTagPolicy.new
|
31
|
+
if policy_json.has_key?("policy_id")
|
32
|
+
policy.policy_id = policy_json["policy_id"]
|
33
|
+
else
|
34
|
+
raise "Policy ID missing"
|
35
|
+
end
|
36
|
+
if policy_json.has_key?("data")
|
37
|
+
data_json = policy_json["data"]
|
38
|
+
policy.js_agent_api_key = data_json.fetch("js_agent_api_key", nil)
|
39
|
+
if policy.js_agent_api_key != nil
|
40
|
+
policy.enabled = true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
return policy
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module TCellAgent
|
4
|
+
module Policies
|
5
|
+
class AppSensorPolicy
|
6
|
+
MAX_NORMAL_REQUEST_BYTES = 1024*512
|
7
|
+
MAX_NORMAL_RESPONSE_BYTES = 1024*1024*2
|
8
|
+
|
9
|
+
DP_XSS = "xss"
|
10
|
+
DP_SQLI = "sqli"
|
11
|
+
DP_CMDI = "cmdi"
|
12
|
+
|
13
|
+
DP_LOGIN_FAILURE = "lgnFlr"
|
14
|
+
DP_LOGIN_SUCCESS = "lgnSccss"
|
15
|
+
|
16
|
+
DP_UNUSUAL_REQUEST_SIZE = "reqsz"
|
17
|
+
DP_UNUSUAL_RESPONSE_SIZE = "rspsz"
|
18
|
+
DP_RESPONSE_401 = "s401"
|
19
|
+
DP_RESPONSE_403 = "s403"
|
20
|
+
DP_RESPONSE_404 = "s404"
|
21
|
+
DP_RESPONSE_4xx = "s4xx"
|
22
|
+
DP_RESPONSE_500 = "s500"
|
23
|
+
DP_RESPONSE_5xx = "s5xx"
|
24
|
+
|
25
|
+
@@detection_point_options = [
|
26
|
+
"req_res_size",
|
27
|
+
"resp_codes",
|
28
|
+
"xss",
|
29
|
+
"sqli",
|
30
|
+
"cmdi",
|
31
|
+
"fpt",
|
32
|
+
"login_failure"]
|
33
|
+
attr_accessor :enabled
|
34
|
+
attr_accessor :policy_id
|
35
|
+
attr_accessor :options
|
36
|
+
def initialize
|
37
|
+
self.init_options
|
38
|
+
end
|
39
|
+
def self.detection_point_options
|
40
|
+
return @@detection_point_options
|
41
|
+
end
|
42
|
+
def init_options
|
43
|
+
@enabled = false
|
44
|
+
@policy_id = nil
|
45
|
+
@options = Hash.new
|
46
|
+
end
|
47
|
+
def option_enabled?(option_name)
|
48
|
+
@options.fetch(option_name, false)
|
49
|
+
end
|
50
|
+
def self.fromJson(policy_json)
|
51
|
+
if (!policy_json)
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
sensor_policy = AppSensorPolicy.new
|
55
|
+
if policy_json.has_key?("policy_id")
|
56
|
+
sensor_policy.policy_id = policy_json["policy_id"]
|
57
|
+
else
|
58
|
+
raise "Policy ID missing"
|
59
|
+
end
|
60
|
+
if policy_json.has_key?("data")
|
61
|
+
data_json = policy_json["data"]
|
62
|
+
if data_json.has_key?("options")
|
63
|
+
options_json = data_json["options"]
|
64
|
+
AppSensorPolicy::detection_point_options.each { |option_name|
|
65
|
+
sensor_policy.options[option_name] = options_json.fetch(option_name,false)
|
66
|
+
if (sensor_policy.option_enabled?(option_name) == true)
|
67
|
+
sensor_policy.enabled = true
|
68
|
+
end
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
return sensor_policy
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# See the file "LICENSE" for the full license governing this code.
|
3
|
+
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module TCellAgent
|
7
|
+
module Policies
|
8
|
+
class ClickjackingPolicy
|
9
|
+
class ContentSecurityPolicyHeader
|
10
|
+
@@approved_headers = [
|
11
|
+
"csp"
|
12
|
+
]
|
13
|
+
attr_accessor :type
|
14
|
+
attr_accessor :raw_value
|
15
|
+
attr_accessor :report_uri
|
16
|
+
def initialize(type, value, report_uri=nil)
|
17
|
+
if !(type && value)
|
18
|
+
raise "Type and value were not set"
|
19
|
+
end
|
20
|
+
if type.downcase == "content-security-policy"
|
21
|
+
type = "csp"
|
22
|
+
end
|
23
|
+
if not @@approved_headers.include?(type.downcase)
|
24
|
+
raise "Type was not included in approved_headers"
|
25
|
+
end
|
26
|
+
if value != value.gsub(/[^\p{L}\w\d\-_\ :\/,;.'\*"%?@#=$]/,'')
|
27
|
+
raise "Value is not valid"
|
28
|
+
end
|
29
|
+
self.type = type
|
30
|
+
self.raw_value = value
|
31
|
+
self.report_uri = report_uri
|
32
|
+
end
|
33
|
+
def value(transaction_id=nil, session_id=nil, user_id=nil)
|
34
|
+
if !self.report_uri
|
35
|
+
return self.raw_value
|
36
|
+
end
|
37
|
+
begin
|
38
|
+
uri = URI.parse(self.report_uri)
|
39
|
+
new_query_ar = URI.decode_www_form(uri.query || '')
|
40
|
+
if transaction_id
|
41
|
+
new_query_ar << ["tid", transaction_id]
|
42
|
+
end
|
43
|
+
if session_id
|
44
|
+
new_query_ar << ["sid", session_id]
|
45
|
+
end
|
46
|
+
if user_id
|
47
|
+
new_query_ar << ["uid", user_id.to_s]
|
48
|
+
end
|
49
|
+
if new_query_ar != []
|
50
|
+
uri.query = URI.encode_www_form(new_query_ar)
|
51
|
+
end
|
52
|
+
report_uri = uri.to_s
|
53
|
+
return "#{self.raw_value}; report-uri #{report_uri}"
|
54
|
+
rescue Exception=>e
|
55
|
+
return self.raw_value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_accessor :headers
|
61
|
+
attr_accessor :policy_id
|
62
|
+
|
63
|
+
def each(transaction_id=nil, hmac_session_id=nil, user_id=nil, &block)
|
64
|
+
result = []
|
65
|
+
headers.each do | header |
|
66
|
+
header_value = header.value(transaction_id, hmac_session_id, user_id)
|
67
|
+
header_names = ClickjackingPolicy.cspHeadersForType(header.type)
|
68
|
+
header_names.each do | header_name |
|
69
|
+
result.push( {"name"=>header_name, "value"=>header_value} )
|
70
|
+
end #doloop
|
71
|
+
end
|
72
|
+
result.each(&block)
|
73
|
+
end
|
74
|
+
def self.fromJson(policy_json)
|
75
|
+
if (!policy_json)
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
csp = ClickjackingPolicy.new
|
79
|
+
if policy_json.has_key?("policy_id")
|
80
|
+
csp.policy_id = policy_json["policy_id"]
|
81
|
+
else
|
82
|
+
raise "Policy ID missing"
|
83
|
+
end
|
84
|
+
if policy_json.has_key?("headers")
|
85
|
+
headers = policy_json["headers"]
|
86
|
+
csp_headers = []
|
87
|
+
headers.each do |header|
|
88
|
+
if header.has_key?("name") && header.has_key?("value")
|
89
|
+
begin
|
90
|
+
csp_header = ContentSecurityPolicyHeader.new(header["name"], header["value"], header["report-uri"])
|
91
|
+
csp_headers.push(csp_header)
|
92
|
+
rescue Exception => secure_header_exception
|
93
|
+
#pass
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
csp.headers = csp_headers
|
98
|
+
end
|
99
|
+
return csp
|
100
|
+
end
|
101
|
+
def self.cspHeadersForType(csp_type)
|
102
|
+
if (!csp_type)
|
103
|
+
return []
|
104
|
+
end
|
105
|
+
if csp_type == "csp"
|
106
|
+
return ["Content-Security-Policy"]#,"X-Content-Security-Policy","X-WebKit-CSP"]
|
107
|
+
else
|
108
|
+
return []
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# See the file "LICENSE" for the full license governing this code.
|
3
|
+
|
4
|
+
require 'uri'
|
5
|
+
require 'tcell_agent/sensor_events/util/sanitizer_utilities'
|
6
|
+
|
7
|
+
module TCellAgent
|
8
|
+
module Policies
|
9
|
+
class ContentSecurityPolicy
|
10
|
+
class ContentSecurityPolicyHeader
|
11
|
+
@@approved_headers = [
|
12
|
+
"csp",
|
13
|
+
"csp-report"
|
14
|
+
]
|
15
|
+
attr_accessor :type
|
16
|
+
attr_accessor :raw_value
|
17
|
+
attr_accessor :report_uri
|
18
|
+
def initialize(type, value, report_uri=nil)
|
19
|
+
if !(type && value)
|
20
|
+
raise "Type and value were not set"
|
21
|
+
end
|
22
|
+
if type.downcase == "content-security-policy"
|
23
|
+
type = "csp"
|
24
|
+
elsif type.downcase == "content-security-policy-report-only"
|
25
|
+
type = "csp-report"
|
26
|
+
end
|
27
|
+
if not @@approved_headers.include?(type.downcase)
|
28
|
+
raise "Type was not included in approved_headers"
|
29
|
+
end
|
30
|
+
if value != value.gsub(/[^\p{L}\w\d\-_\ :\/,;.'\*"%?@#=$]/,'')
|
31
|
+
raise "Value is not valid"
|
32
|
+
end
|
33
|
+
self.type = type
|
34
|
+
self.raw_value = value
|
35
|
+
self.report_uri = report_uri
|
36
|
+
end
|
37
|
+
def value(transaction_id=nil, session_id=nil, user_id=nil)
|
38
|
+
if !self.report_uri
|
39
|
+
return self.raw_value
|
40
|
+
end
|
41
|
+
begin
|
42
|
+
uri = URI.parse(self.report_uri)
|
43
|
+
new_query_ar = URI.decode_www_form(uri.query || '')
|
44
|
+
if transaction_id
|
45
|
+
new_query_ar << ["tid", transaction_id]
|
46
|
+
end
|
47
|
+
if session_id
|
48
|
+
new_query_ar << ["sid", session_id]
|
49
|
+
end
|
50
|
+
if user_id
|
51
|
+
new_query_ar << ["uid", user_id.to_s]
|
52
|
+
end
|
53
|
+
if new_query_ar != []
|
54
|
+
uri.query = URI.encode_www_form(new_query_ar)
|
55
|
+
end
|
56
|
+
report_uri = uri.to_s
|
57
|
+
return "#{self.raw_value}; report-uri #{report_uri}"
|
58
|
+
rescue Exception=>e
|
59
|
+
return self.raw_value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_accessor :headers
|
65
|
+
attr_accessor :policy_id
|
66
|
+
|
67
|
+
def each(transaction_id=nil, hmac_session_id=nil, user_id=nil, &block)
|
68
|
+
result = []
|
69
|
+
headers.each do | header |
|
70
|
+
header_value = header.value(transaction_id, hmac_session_id, user_id)
|
71
|
+
header_names = ContentSecurityPolicy.cspHeadersForType(header.type)
|
72
|
+
header_names.each do | header_name |
|
73
|
+
result.push( {"name"=>header_name, "value"=>header_value} )
|
74
|
+
end #doloop
|
75
|
+
end
|
76
|
+
result.each(&block)
|
77
|
+
end
|
78
|
+
def self.fromJson(policy_json)
|
79
|
+
if (!policy_json)
|
80
|
+
return nil
|
81
|
+
end
|
82
|
+
csp = ContentSecurityPolicy.new
|
83
|
+
if policy_json.has_key?("policy_id")
|
84
|
+
csp.policy_id = policy_json["policy_id"]
|
85
|
+
else
|
86
|
+
raise "Policy ID missing"
|
87
|
+
end
|
88
|
+
if policy_json.has_key?("headers")
|
89
|
+
headers = policy_json["headers"]
|
90
|
+
csp_headers = []
|
91
|
+
headers.each do |header|
|
92
|
+
if header.has_key?("name") && header.has_key?("value")
|
93
|
+
begin
|
94
|
+
csp_header = ContentSecurityPolicyHeader.new(header["name"], header["value"], header["report-uri"])
|
95
|
+
csp_headers.push(csp_header)
|
96
|
+
rescue Exception => secure_header_exception
|
97
|
+
#pass
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
csp.headers = csp_headers
|
102
|
+
end
|
103
|
+
return csp
|
104
|
+
end
|
105
|
+
def self.cspHeadersForType(csp_type)
|
106
|
+
if (!csp_type)
|
107
|
+
return []
|
108
|
+
end
|
109
|
+
if csp_type == "csp"
|
110
|
+
return ["Content-Security-Policy"]#,"X-Content-Security-Policy","X-WebKit-CSP"]
|
111
|
+
elsif csp_type == "csp-report"
|
112
|
+
return ["Content-Security-Policy-Report-Only"]#,"X-Content-Security-Policy-Report-Only","X-WebKit-CSP-Report-Only"]
|
113
|
+
else
|
114
|
+
return []
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|