sqreen 0.7.01461158029-java

Sign up to get free protection for your applications and to get access to all the features.
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