sqreen 0.7.01461158029-java

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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +22 -0
  3. data/README.md +77 -0
  4. data/Rakefile +40 -0
  5. data/lib/sqreen.rb +67 -0
  6. data/lib/sqreen/binding_accessor.rb +184 -0
  7. data/lib/sqreen/ca.crt +72 -0
  8. data/lib/sqreen/callback_tree.rb +78 -0
  9. data/lib/sqreen/callbacks.rb +120 -0
  10. data/lib/sqreen/capped_queue.rb +23 -0
  11. data/lib/sqreen/condition_evaluator.rb +169 -0
  12. data/lib/sqreen/conditionable.rb +50 -0
  13. data/lib/sqreen/configuration.rb +151 -0
  14. data/lib/sqreen/context.rb +22 -0
  15. data/lib/sqreen/deliveries/batch.rb +80 -0
  16. data/lib/sqreen/deliveries/simple.rb +36 -0
  17. data/lib/sqreen/detect.rb +14 -0
  18. data/lib/sqreen/detect/shell_injection.rb +61 -0
  19. data/lib/sqreen/detect/sql_injection.rb +115 -0
  20. data/lib/sqreen/event.rb +16 -0
  21. data/lib/sqreen/events/attack.rb +60 -0
  22. data/lib/sqreen/events/remote_exception.rb +53 -0
  23. data/lib/sqreen/exception.rb +31 -0
  24. data/lib/sqreen/frameworks.rb +40 -0
  25. data/lib/sqreen/frameworks/generic.rb +243 -0
  26. data/lib/sqreen/frameworks/rails.rb +155 -0
  27. data/lib/sqreen/frameworks/rails3.rb +36 -0
  28. data/lib/sqreen/frameworks/sinatra.rb +34 -0
  29. data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
  30. data/lib/sqreen/instrumentation.rb +504 -0
  31. data/lib/sqreen/log.rb +116 -0
  32. data/lib/sqreen/metrics.rb +6 -0
  33. data/lib/sqreen/metrics/average.rb +39 -0
  34. data/lib/sqreen/metrics/base.rb +41 -0
  35. data/lib/sqreen/metrics/collect.rb +22 -0
  36. data/lib/sqreen/metrics/sum.rb +20 -0
  37. data/lib/sqreen/metrics_store.rb +94 -0
  38. data/lib/sqreen/parsers/sql.rb +98 -0
  39. data/lib/sqreen/parsers/sql_tokenizer.rb +266 -0
  40. data/lib/sqreen/parsers/unix.rb +110 -0
  41. data/lib/sqreen/payload_creator.rb +132 -0
  42. data/lib/sqreen/performance_notifications.rb +86 -0
  43. data/lib/sqreen/performance_notifications/log.rb +36 -0
  44. data/lib/sqreen/performance_notifications/metrics.rb +36 -0
  45. data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
  46. data/lib/sqreen/remote_command.rb +82 -0
  47. data/lib/sqreen/rule_attributes.rb +25 -0
  48. data/lib/sqreen/rule_callback.rb +97 -0
  49. data/lib/sqreen/rules.rb +116 -0
  50. data/lib/sqreen/rules_callbacks.rb +29 -0
  51. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
  52. data/lib/sqreen/rules_callbacks/count_http_codes.rb +18 -0
  53. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
  54. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +25 -0
  55. data/lib/sqreen/rules_callbacks/execjs.rb +136 -0
  56. data/lib/sqreen/rules_callbacks/headers_insert.rb +20 -0
  57. data/lib/sqreen/rules_callbacks/inspect_rule.rb +20 -0
  58. data/lib/sqreen/rules_callbacks/matcher_rule.rb +103 -0
  59. data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
  60. data/lib/sqreen/rules_callbacks/record_request_context.rb +23 -0
  61. data/lib/sqreen/rules_callbacks/reflected_xss.rb +40 -0
  62. data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
  63. data/lib/sqreen/rules_callbacks/shell.rb +33 -0
  64. data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
  65. data/lib/sqreen/rules_callbacks/sql.rb +41 -0
  66. data/lib/sqreen/rules_callbacks/system_shell.rb +25 -0
  67. data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
  68. data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
  69. data/lib/sqreen/rules_signature.rb +142 -0
  70. data/lib/sqreen/runner.rb +312 -0
  71. data/lib/sqreen/runtime_infos.rb +127 -0
  72. data/lib/sqreen/session.rb +340 -0
  73. data/lib/sqreen/stats.rb +18 -0
  74. data/lib/sqreen/version.rb +6 -0
  75. metadata +143 -0
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ module Sqreen
5
+ # Context
6
+ class Context
7
+ attr_accessor :bt
8
+
9
+ def initialize
10
+ @bt = get_current_backtrace
11
+ end
12
+
13
+ def get_current_backtrace
14
+ # Force caller to be resolved now
15
+ caller.map(&:to_s)
16
+ end
17
+
18
+ def ==(other)
19
+ other.bt == @bt
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,80 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/deliveries/simple'
5
+ require 'sqreen/events/remote_exception'
6
+ module Sqreen
7
+ module Deliveries
8
+ # Simple delivery method that batch event already seen in a batch
9
+ class Batch < Simple
10
+ attr_accessor :max_batch, :max_staleness
11
+ attr_accessor :current_batch
12
+
13
+ def initialize(session,
14
+ max_batch,
15
+ max_staleness,
16
+ randomize_staleness = true)
17
+ super(session)
18
+ self.max_batch = max_batch
19
+ self.max_staleness = max_staleness
20
+ @original_max_staleness = max_staleness
21
+ self.current_batch = []
22
+ @first_seen = {}
23
+ @randomize_staleness = randomize_staleness
24
+ end
25
+
26
+ def post_event(event)
27
+ current_batch.push(event)
28
+ post_batch if post_batch_needed?(event)
29
+ end
30
+
31
+ def drain
32
+ post_batch
33
+ end
34
+
35
+ def tick
36
+ post_batch if current_batch.size > 0 && stale?
37
+ end
38
+
39
+ protected
40
+
41
+ def stale?
42
+ min = @first_seen.values.min
43
+ (min + max_staleness) < Time.now
44
+ end
45
+
46
+ def post_batch_needed?(event)
47
+ key = event_key(event)
48
+ was = @first_seen[key]
49
+ now = Time.now
50
+ @first_seen[key] ||= now
51
+ return true if was.nil?
52
+ return true if current_batch.size > max_batch
53
+ return true if (was + max_staleness) < now
54
+ false
55
+ end
56
+
57
+ def post_batch
58
+ session.post_batch(current_batch)
59
+ current_batch.clear
60
+ now = Time.now
61
+ @first_seen.each_key do |key|
62
+ @first_seen[key] = now
63
+ end
64
+ return unless @randomize_staleness
65
+ self.max_staleness = @original_max_staleness
66
+ # Adds up to 10% of lateness
67
+ self.max_staleness += rand(@original_max_staleness / 10)
68
+ end
69
+
70
+ def event_key(event)
71
+ case event
72
+ when Sqreen::Attack
73
+ return "att-#{event.type}"
74
+ when Sqreen::RemoteException
75
+ return "rex-#{event.klass}"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/events/remote_exception'
5
+
6
+ module Sqreen
7
+ module Deliveries
8
+ # Simple delivery method that directly call session on event
9
+ class Simple
10
+ attr_accessor :session
11
+
12
+ def initialize(session)
13
+ self.session = session
14
+ end
15
+
16
+ def post_event(event)
17
+ case event
18
+ when Sqreen::Attack
19
+ session.post_attack(event)
20
+ when Sqreen::RemoteException
21
+ session.post_sqreen_exception(event)
22
+ else
23
+ session.post_event(event)
24
+ end
25
+ end
26
+
27
+ def drain
28
+ # Since everything is posted at once nothing needs to be done here
29
+ end
30
+
31
+ def tick
32
+ # Since everything is posted at once nothing needs to be done here
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/detect/sql_injection'
5
+ require 'sqreen/detect/shell_injection'
6
+
7
+ module Sqreen
8
+ module Detect
9
+ def sql_injection?(request, params, db_type, db_infos = {})
10
+ inj = SQLInjection.new(db_type, db_infos)
11
+ inj.user_escape?(request, params)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/parsers/unix'
5
+
6
+ module Sqreen
7
+ module Detect
8
+ # Detector class for shell injections
9
+ # Find instance of user parameters injections into executable commands
10
+ # It work by:
11
+ # 1 - Highlighting the cmd for executable sections
12
+ # 2 - Highlighting the cmd for traces of user parameters
13
+ # 3 - Comparing if there is any intersection
14
+ class ShellInjection
15
+ def initialize
16
+ @parser = Sqreen::Parsers::Unix.new
17
+ end
18
+
19
+ # Is there a user injection in cmd
20
+ # @param cmd [String] command to analyze
21
+ # @param params [Hash] Hash of user parameters
22
+ def user_escape?(cmd, params)
23
+ Sqreen.log.info format('escape? %s', [cmd, params].inspect)
24
+
25
+ # We found the user query inside the cmd. A risk exists.
26
+ @parser.parse(cmd)
27
+ execs = @parser.atoms.select(&:executable?)
28
+
29
+ each_param_scalar(params) do |v|
30
+ next unless v
31
+ value = v.to_s
32
+ next unless value.size > 0
33
+ offset = 0
34
+ loop do
35
+ match_start = cmd.index(value, offset)
36
+ break if match_start.nil?
37
+ match_end = match_start + value.size
38
+ offset = match_end
39
+ covered = execs.any? do |exec|
40
+ match_end >= exec.start && match_start < exec.end
41
+ end
42
+ next unless covered
43
+ Sqreen.log.info format('injection for parameter %s', value.inspect)
44
+ return true
45
+ end
46
+ end
47
+ false
48
+ end
49
+
50
+ # FIXME: deduplicate code
51
+ def each_param_scalar(params, &block)
52
+ case params
53
+ when Hash then params.each { |_k, v| each_param_scalar(v, &block) }
54
+ when Array then params.each { |v| each_param_scalar(v, &block) }
55
+ else
56
+ yield params
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,115 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/parsers/sql'
5
+ require 'strscan'
6
+
7
+ module Sqreen
8
+ module Detect
9
+ class SQLInjection
10
+ PARAM_SIZE_LIMIT = 0
11
+
12
+ def self.parser(db_type, db_infos)
13
+ @parsers ||= {}
14
+ res = nil
15
+ results = @parsers[db_type]
16
+ res = results.find { |infos, _| infos == db_infos } unless results.nil?
17
+ return res.last unless res.nil?
18
+ @parsers[db_type] ||= []
19
+ parser = Sqreen::Parsers::SQL.new(db_type, db_infos)
20
+ @parsers[db_type] << [db_infos, parser]
21
+ parser
22
+ end
23
+
24
+ attr_accessor :db_type, :db_infos
25
+ def initialize(db_type, db_infos)
26
+ @db_type = db_type
27
+ @db_infos = db_infos
28
+ @parser = SQLInjection.parser(db_type, db_infos)
29
+ end
30
+
31
+ # FIXME: we are likely to find false postive
32
+ # As is, high risk of false positive with extremely short parameters.
33
+ # E.g. if parameter value is 'e', it will be found in request, (e in
34
+ # select) but not in literals.
35
+ #
36
+ # We may want to skip:
37
+ # - too short parameters (e.g. < 10 letters?)
38
+ # - non suspicious parameters (e.g. without blanks or comments?)
39
+ def user_escape?(request, params)
40
+ included = count_user_params_in_request(request, params)
41
+ return false if included == {}
42
+
43
+ escape_found = false
44
+
45
+ # We found the user query inside the request. A risk exists.
46
+ @parser.parse(request)
47
+ literals = @parser.atoms.select(&:is_literal?)
48
+ included.each do |param, expected_count|
49
+ param_count = 0
50
+ literals.each do |literal|
51
+ # Count number of literals that fully include the user query
52
+ param_count += count_substring_nb(literal.val, param)
53
+ end
54
+
55
+ # puts "%s in raw request: %d, in atoms: %d" % [param, expected_count, param_count]
56
+ next unless param_count != expected_count
57
+ Sqreen.log.info format('injection for parameter %s', param.inspect)
58
+ # require request aborption
59
+ # log attack
60
+ escape_found = true
61
+ end
62
+ escape_found
63
+ end
64
+
65
+ # What if a string can be prefixed itself?
66
+ # E.g. substr = 'a b c a b c'
67
+ # If str = 'a b c a b c a b c' we will return 2:
68
+ # \----1----/
69
+ # \----2----/
70
+ def count_substring_nb(str, substr)
71
+ s = StringScanner.new(str)
72
+ nb = 0
73
+ quote = Regexp.quote(substr)
74
+ re = Regexp.new(quote)
75
+ nb += 1 while s.search_full(re, true, false)
76
+ nb
77
+ end
78
+
79
+ def each_param_scalar(params, &block)
80
+ case params
81
+ when Hash then params.each { |_k, v| each_param_scalar(v, &block) }
82
+ when Array then params.each { |v| each_param_scalar(v, &block) }
83
+ else
84
+ yield params
85
+ end
86
+ end
87
+
88
+ # FIXME: this work on params values. We might wnat to work on parameters
89
+ # names? High risk of false positive since a parameter name is often the
90
+ # database column name.
91
+ def count_user_params_in_request(request, params_hash)
92
+ res = {}
93
+ params_hash.each do |_type, params|
94
+ next if params.nil?
95
+ each_param_scalar(params) do |value|
96
+ next unless value
97
+ v = value.to_s
98
+ next if v.size <= PARAM_SIZE_LIMIT
99
+ next if v =~ /\A\.+\z/
100
+ next if v =~ /\A\s+\z/
101
+ next if v =~ /\A(\w+|\w[\w\.]+\w)\z/i
102
+
103
+ # We need to overwrite the count of equal parameters that
104
+ # came from different ways (e.g. Cookie and query).
105
+ next if res.key? v
106
+ nb = count_substring_nb(request, v)
107
+
108
+ res[v] = nb if nb > 0
109
+ end
110
+ end
111
+ res
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ module Sqreen
5
+ # Master interface for point in time events (e.g. Attack, RemoteException)
6
+ class Event
7
+ attr_reader :payload
8
+ def initialize(payload)
9
+ @payload = payload
10
+ end
11
+
12
+ def to_hash
13
+ payload.to_hash
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/event'
5
+
6
+ module Sqreen
7
+ # Attack
8
+ # When creating a new attack, it gets automatically pushed to the event's
9
+ # queue.
10
+ class Attack < Event
11
+ def self.record(payload)
12
+ attack = Attack.new(payload)
13
+ attack.enqueue
14
+ end
15
+
16
+ def infos
17
+ payload['infos']
18
+ end
19
+
20
+ def rulespack_id
21
+ return nil unless payload['rule']
22
+ payload['rule']['rulespack_id']
23
+ end
24
+
25
+ def type
26
+ return nil unless payload['rule']
27
+ payload['rule']['name']
28
+ end
29
+
30
+ def time
31
+ return nil unless payload['local']
32
+ payload['local']['time'].to_s
33
+ end
34
+
35
+ def backtrace
36
+ return nil unless payload['context']
37
+ payload['context']['backtrace']
38
+ end
39
+
40
+ def enqueue
41
+ Sqreen.queue.push(self)
42
+ end
43
+
44
+ def to_hash
45
+ res = {}
46
+ rule_p = payload['rule']
47
+ request_p = payload['request']
48
+ res[:rule_name] = rule_p['name'] if rule_p && rule_p['name']
49
+ res[:rulespack_id] = rule_p['rulespack_id'] if rule_p && rule_p['rulespack_id']
50
+ res[:test] = rule_p['test'] if rule_p && rule_p['test']
51
+ res[:infos] = payload['infos'] if payload['infos']
52
+ res[:time] = time if time
53
+ res[:client_ip] = request_p[:addr] if request_p && request_p[:addr]
54
+ res[:request] = request_p if request_p
55
+ res[:params] = payload['params'] if payload['params']
56
+ res[:context] = payload['context'] if payload['context']
57
+ res
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'json'
5
+ require 'sqreen/event'
6
+
7
+ module Sqreen
8
+ # When an exception arise it is automatically pushed to the event queue
9
+ class RemoteException < Sqreen::Event
10
+ def self.record(payload_or_exception)
11
+ exception = RemoteException.new(payload_or_exception)
12
+ exception.enqueue
13
+ end
14
+
15
+ def initialize(payload_or_exception)
16
+ payload = if payload_or_exception.is_a?(Hash)
17
+ payload_or_exception
18
+ else
19
+ { 'exception' => payload_or_exception }
20
+ end
21
+ super(payload)
22
+ end
23
+
24
+ def enqueue
25
+ Sqreen.queue.push(self)
26
+ end
27
+
28
+ def klass
29
+ payload['exception'].class.name
30
+ end
31
+
32
+ def to_hash
33
+ exception = payload['exception']
34
+ ev = {
35
+ :klass => exception.class.name,
36
+ :message => exception.message,
37
+ :params => payload['request_params'],
38
+ :time => payload['time'],
39
+ :infos => {
40
+ :client_ip => payload['client_ip'],
41
+ },
42
+ :request => payload['request_infos'],
43
+ :rule_name => payload['rule_name'],
44
+ :rulespack_id => payload['rulespack_id'],
45
+ }
46
+
47
+ ev[:infos].merge!(payload['infos']) if payload['infos']
48
+ return ev unless exception.backtrace
49
+ ev[:context] = { :backtrace => exception.backtrace.map(&:to_s) }
50
+ ev
51
+ end
52
+ end
53
+ end