sqreen 0.1.0.pre → 0.7.01461158029

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 +4 -4
  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 +95 -34
@@ -0,0 +1,29 @@
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/rules_callbacks/regexp_rule'
5
+ require 'sqreen/rules_callbacks/matcher_rule'
6
+
7
+ require 'sqreen/rules_callbacks/record_request_context'
8
+ require 'sqreen/rules_callbacks/rails_parameters'
9
+
10
+ require 'sqreen/rules_callbacks/headers_insert'
11
+
12
+ require 'sqreen/rules_callbacks/inspect_rule'
13
+
14
+ require 'sqreen/rules_callbacks/shell'
15
+ require 'sqreen/rules_callbacks/system_shell'
16
+ require 'sqreen/rules_callbacks/shell_env'
17
+
18
+ require 'sqreen/rules_callbacks/sql'
19
+
20
+ require 'sqreen/rules_callbacks/url_matches'
21
+ require 'sqreen/rules_callbacks/user_agent_matches'
22
+ require 'sqreen/rules_callbacks/crawler_user_agent_matches'
23
+
24
+ require 'sqreen/rules_callbacks/reflected_xss'
25
+ require 'sqreen/rules_callbacks/execjs'
26
+
27
+ require 'sqreen/rules_callbacks/binding_accessor_metrics'
28
+ require 'sqreen/rules_callbacks/count_http_codes'
29
+ require 'sqreen/rules_callbacks/crawler_user_agent_matches_metrics'
@@ -0,0 +1,79 @@
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/rule_callback'
5
+ require 'sqreen/binding_accessor'
6
+ require 'sqreen/events/remote_exception'
7
+
8
+ module Sqreen
9
+ module Rules
10
+ # Publish metrics about data taken from the binding accessor
11
+ class BindingAccessorMetrics < RuleCB
12
+ # Exception thrown when no expression are present
13
+ class NoExpressions < Sqreen::Exception
14
+ def initialize(expr)
15
+ super("No valid expressions defined in #{expr.inspect}")
16
+ end
17
+ end
18
+
19
+ def initialize(klass, method, rule_hash)
20
+ super(klass, method, rule_hash)
21
+ @expr = {}
22
+ build_expressions(rule_hash[Attrs::CALLBACKS])
23
+ end
24
+
25
+ PRE_CB = 'pre'.freeze
26
+ POST_CB = 'post'.freeze
27
+ FAILING_CB = 'failing'.freeze
28
+
29
+ def pre?
30
+ @expr[PRE_CB]
31
+ end
32
+
33
+ def post?
34
+ @expr[POST_CB]
35
+ end
36
+
37
+ def failing?
38
+ @expr[FAILING_CB]
39
+ end
40
+
41
+ def pre(inst, *args, &_block)
42
+ return unless pre?
43
+
44
+ add_metrics(PRE_CB, inst, args)
45
+ end
46
+
47
+ def post(rv, inst, *args, &_block)
48
+ return unless post?
49
+
50
+ add_metrics(POST_CB, inst, args, rv)
51
+ end
52
+
53
+ def failing(exception, inst, *args, &_block)
54
+ return unless failing?
55
+
56
+ add_metrics(FAILING_CB, inst, args, exception)
57
+ end
58
+
59
+ protected
60
+
61
+ def add_metrics(name, inst, args, rv = nil)
62
+ category, key, value, = @expr[name].map do |accessor|
63
+ accessor.resolve(binding, framework, inst, args, @data, rv)
64
+ end
65
+ record_observation(category, key, value)
66
+ nil
67
+ end
68
+
69
+ def build_expressions(callbacks)
70
+ raise NoExpressions, callbacks if callbacks.nil? || callbacks.empty?
71
+ [PRE_CB, POST_CB, FAILING_CB].each do |c|
72
+ next if callbacks[c].nil? || callbacks[c].size < 3
73
+ @expr[c] = callbacks[c].map { |req| BindingAccessor.new(req, true) }
74
+ end
75
+ raise NoExpressions, callbacks if @expr.empty?
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,18 @@
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/rule_callback'
5
+
6
+ module Sqreen
7
+ module Rules
8
+ # Save request context for handling further down
9
+ class CountHTTPCodes < RuleCB
10
+ METRIC_CATEGORY = 'http_code'.freeze
11
+ def post(rv, _inst, *_args, &_block)
12
+ return unless rv.is_a?(Array) && !rv.empty?
13
+ record_observation(METRIC_CATEGORY, rv[0], 1)
14
+ nil
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
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/rules_callbacks/matcher_rule'
5
+ require 'sqreen/frameworks'
6
+
7
+ module Sqreen
8
+ module Rules
9
+ # FIXME: Factor with UserAgentMatchesCB
10
+ # Look for crawlers
11
+ class CrawlerUserAgentMatchesCB < MatcherRuleCB
12
+ def pre(_inst, *_args, &_block)
13
+ ua = framework.client_user_agent
14
+ return unless ua
15
+ found = match(ua)
16
+ return unless found
17
+ Sqreen.log.debug { "Found UA #{ua} - found: #{found}" }
18
+ infos = { :found => found }
19
+ record_event(infos)
20
+ nil
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
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/rules_callbacks/matcher_rule'
5
+ require 'sqreen/frameworks'
6
+
7
+ module Sqreen
8
+ module Rules
9
+ # Look for crawlers and post them in metrics
10
+ class CrawlerUserAgentMatchesMetricsCB < MatcherRuleCB
11
+ CRAWLER_CATEGORY = 'crawler'.freeze
12
+
13
+ def pre(_inst, *_args, &_block)
14
+ ua = framework.client_user_agent
15
+ return unless ua
16
+ ua = ua.freeze
17
+ found = match(ua)
18
+ return unless found
19
+ Sqreen.log.debug { "Found UA #{ua} - found: #{found}" }
20
+ record_observation(CRAWLER_CATEGORY, ua, 1)
21
+ nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,136 @@
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
+ if defined?(::JRUBY_VERSION)
5
+ require 'rhino'
6
+ else
7
+ require 'therubyracer'
8
+ end
9
+
10
+ require 'execjs'
11
+
12
+ require 'sqreen/rule_callback'
13
+ require 'sqreen/binding_accessor'
14
+ require 'sqreen/events/remote_exception'
15
+
16
+ module Sqreen
17
+ module Rules
18
+ # Exec js callbacks
19
+ class ExecJSCB < RuleCB
20
+ def initialize(klass, method, rule_hash)
21
+ super(klass, method, rule_hash)
22
+ callbacks = @rule['callbacks']
23
+
24
+ if callbacks['pre'].nil? &&
25
+ callbacks['post'].nil? &&
26
+ callbacks['failing'].nil?
27
+ raise(Sqreen::Exception, 'no JS CB provided')
28
+ end
29
+
30
+ build_runnable(callbacks)
31
+ @compiled = ExecJS.compile(@source)
32
+ end
33
+
34
+ def pre?
35
+ @js_pre
36
+ end
37
+
38
+ def post?
39
+ @js_post
40
+ end
41
+
42
+ def failing?
43
+ @js_failing
44
+ end
45
+
46
+ def pre(inst, *args, &_block)
47
+ return unless pre?
48
+
49
+ call_callback('pre', inst, args)
50
+ end
51
+
52
+ def post(rv, inst, *args, &_block)
53
+ return unless post?
54
+
55
+ call_callback('post', inst, args, rv)
56
+ end
57
+
58
+ def failing(rv, inst, *args, &_block)
59
+ return unless failing?
60
+
61
+ call_callback('failing', inst, args, rv)
62
+ end
63
+
64
+ protected
65
+
66
+ def record_and_continue?(ret)
67
+ case ret
68
+ when NilClass
69
+ return false
70
+ when Hash
71
+ ret.keys.each do |k|
72
+ ret[(begin
73
+ k.to_sym
74
+ rescue
75
+ k
76
+ end)] = ret[k] end
77
+ record_event(ret[:record]) unless ret[:record].nil?
78
+ return !ret[:call].nil?
79
+ else
80
+ raise Sqreen::Exception, "Invalid return type #{ret.inspect}"
81
+ end
82
+ end
83
+
84
+ def call_callback(name, inst, args, rv = nil)
85
+ ret = nil
86
+ args_override = nil
87
+ arguments = nil
88
+ loop do
89
+ arguments = (args_override || @argument_requirements[name]).map do |accessor|
90
+ accessor.resolve(binding, framework, inst, args, @data, rv)
91
+ end
92
+ Sqreen.log.debug { [name, arguments].inspect }
93
+ ret = @compiled.call("callbacks.#{name}", *arguments)
94
+ return ret unless record_and_continue?(ret)
95
+ name = ret[:call]
96
+ rv = ret[:data]
97
+ args_override = ret[:args]
98
+ args_override = build_accessor(args_override) if args_override
99
+ end
100
+ rescue => e
101
+ Sqreen.log.error "we catch a JScb exception: #{e.inspect}"
102
+ Sqreen.log.error e.backtrace
103
+ record_exception(e, :cb => name, :args => arguments)
104
+ nil
105
+ end
106
+
107
+ def build_accessor(reqs)
108
+ reqs.map do |req|
109
+ BindingAccessor.new(req, true)
110
+ end
111
+ end
112
+
113
+ def build_runnable(callbacks)
114
+ @argument_requirements = {}
115
+ @source = 'var callbacks = {'
116
+ @js_pre = !callbacks['pre'].nil?
117
+ @js_post = !callbacks['post'].nil?
118
+ @js_failing = !callbacks['failing'].nil?
119
+ callbacks.each do |name, args_or_func|
120
+ @source << name
121
+ @source << ': '
122
+ if args_or_func.is_a?(Array)
123
+ @source << args_or_func.pop
124
+ @argument_requirements[name] = build_accessor(args_or_func)
125
+ else
126
+ @source << args_or_func
127
+ @argument_requirements[name] = []
128
+ end
129
+ @source << ",\n"
130
+ end
131
+ @source << "\n"
132
+ @source << '}'
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,20 @@
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/rule_callback'
5
+
6
+ module Sqreen
7
+ module Rules
8
+ SQREEN_HEADER_NAME = 'X-Protected-By'.freeze
9
+ SQREEN_HEADER_VALUE = 'Sqreen'.freeze
10
+
11
+ # Display sqreen presence
12
+ class HeadersInsertCB < RuleCB
13
+ def post(rv, _inst, *_args, &_block)
14
+ return unless rv && rv.respond_to?(:[]) && rv[1].is_a?(Hash)
15
+ rv[1][SQREEN_HEADER_NAME] = SQREEN_HEADER_VALUE
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
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/rule_callback'
5
+
6
+ module Sqreen
7
+ module Rules
8
+ class InspectRuleCB < RuleCB
9
+ def pre(_inst, *args, &_block)
10
+ Sqreen.log.debug { "<< #{@klass} #{@method} #{Thread.current}" }
11
+ Sqreen.log.debug { args.join ' ' }
12
+ end
13
+
14
+ def post(_rv, _inst, *_args, &_block)
15
+ Sqreen.log.debug { ">> #{@klass} #{@method} #{Thread.current}" }
16
+ byebug if defined? byebug and @data.is_a?(Hash) and @data[:break] == 1
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,103 @@
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/rule_callback'
5
+
6
+ module Sqreen
7
+ module Rules
8
+ # A configurable matcher rule
9
+ class MatcherRuleCB < RuleCB
10
+ def initialize(*args)
11
+ super(*args)
12
+ prepare
13
+ end
14
+
15
+ def self.prepare_re_pattern(value, options, case_sensitive)
16
+ res = 0
17
+ res |= Regexp::MULTILINE if options.include?('multiline')
18
+ res |= Regexp::IGNORECASE unless case_sensitive
19
+ Regexp.compile(value, res)
20
+ end
21
+
22
+ def prepare
23
+ @string = {}
24
+ @regex_patterns = []
25
+
26
+ patterns = @data['values']
27
+ if patterns.nil?
28
+ msg = "no key 'values' in data (had #{@data.keys})"
29
+ raise Sqreen::Exception, msg
30
+ end
31
+
32
+ @funs = {
33
+ 'anywhere' => lambda { |value, str| str.include?(value) },
34
+ 'starts_with' => lambda { |value, str| str.start_with?(value) },
35
+ 'ends_with' => lambda { |value, str| str.end_with?(value) },
36
+ 'equals' => lambda { |value, str| str == value },
37
+ }
38
+
39
+ patterns.each do |entry|
40
+ next unless entry
41
+ type = entry['type']
42
+ val = entry['value']
43
+ opts = entry['options']
44
+ opt = (opts && opts.first && opts.first != '') ? opts.first : 'anywhere'
45
+ case_sensitive = entry['case_sensitive'] || false
46
+ case type
47
+ when 'string'
48
+ if case_sensitive
49
+ case_type = :cs
50
+ else
51
+ case_type = :ci
52
+ val = val.downcase
53
+ end
54
+
55
+ unless @funs.keys.include?(opt)
56
+ Sqreen.log.debug { "Error: unknown string option '#{opt}' " }
57
+ next
58
+ end
59
+ @string[opt] = { :ci => [], :cs => [] } unless @string.key?(opt)
60
+ @string[opt][case_type] << val
61
+
62
+ when 'regex'
63
+ pattern = MatcherRuleCB.prepare_re_pattern(val, opt, case_sensitive)
64
+ next unless pattern
65
+ @regex_patterns << pattern
66
+ end
67
+ end
68
+
69
+ if [@regex_patterns, @string].map { |s| s == 0 }.all?
70
+ msg = "no key 'regex' nor 'match' in data (had #{@data.keys})"
71
+ raise Sqreen::Exception, msg
72
+ end
73
+ end
74
+
75
+ def match(str)
76
+ return if str.nil? || str.empty?
77
+ istr = str.downcase
78
+
79
+ @string.each do |type, cases|
80
+ fun = @funs[type]
81
+ if fun.nil?
82
+ Sqreen.log.debug { "no matching function found for type #{type}" }
83
+ end
84
+ cases.each do |case_type, patterns|
85
+ input_str = if case_type == :ci
86
+ istr
87
+ else
88
+ str
89
+ end
90
+ patterns.each do |pat|
91
+ return pat if fun.call(pat, input_str)
92
+ end
93
+ end
94
+ end
95
+
96
+ @regex_patterns.each do |p|
97
+ return p if p.match(str)
98
+ end
99
+ nil
100
+ end
101
+ end
102
+ end
103
+ end