sqreen 0.8.11465220943 → 1.0.0.pre1480953244

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.
@@ -45,7 +45,8 @@ module Sqreen
45
45
  end
46
46
 
47
47
  def time
48
- { :time => Time.now.to_s }
48
+ # FIXME: That should maybe be called local-time
49
+ { :time => Time.now }
49
50
  end
50
51
 
51
52
  def ssl
@@ -120,7 +121,7 @@ module Sqreen
120
121
  ret['remotes'] = opts['remotes'] if opts['remotes']
121
122
  ret['uri'] = opts['uri'] if opts['uri']
122
123
  # FIXME: scrub any auth data in uris
123
- ret['path'] = opts['path'] if opts['path']
124
+ ret['path'] = opts['path'].to_s if opts['path']
124
125
  ret
125
126
  end
126
127
  end
@@ -0,0 +1,46 @@
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
+ # Serialization functions: convert Hash -> simple ruby types
6
+ module Serializer
7
+ # Serialize a deep hash/array to more simple types
8
+ def self.serialize(obj, max_depth = 10)
9
+ if obj.is_a?(Array)
10
+ new_obj = []
11
+ i = -1
12
+ to_do = obj.map { |v| [new_obj, i += 1, v, 0] }
13
+ else
14
+ new_obj = {}
15
+ to_do = obj.map { |k, v| [new_obj, k, v, 0] }
16
+ end
17
+ until to_do.empty?
18
+ where, key, value, deepness = to_do.pop
19
+ safe_key = key.kind_of?(Integer) ? key : key.to_s
20
+ if value.is_a?(Hash) && deepness < max_depth
21
+ where[safe_key] = {}
22
+ to_do += value.map { |k, v| [where[safe_key], k, v, deepness + 1] }
23
+ elsif value.is_a?(Array) && deepness < max_depth
24
+ where[safe_key] = []
25
+ i = -1
26
+ to_do += value.map { |v| [where[safe_key], i += 1, v, deepness + 1] }
27
+ else
28
+ case value
29
+ when Symbol
30
+ where[safe_key] = value.to_s
31
+ when Rational
32
+ where[safe_key] = value.to_f
33
+ when Time
34
+ where[safe_key] = value.iso8601
35
+ when Numeric, String, TrueClass, FalseClass, NilClass
36
+ where[safe_key] = value
37
+ else
38
+ where[safe_key] = "#{value.class.name}:#{value.inspect}"
39
+ end
40
+ end
41
+ end
42
+
43
+ new_obj
44
+ end
45
+ end
46
+ end
@@ -2,8 +2,10 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  require 'sqreen/log'
5
+ require 'sqreen/serializer'
5
6
  require 'sqreen/runtime_infos'
6
7
  require 'sqreen/events/remote_exception'
8
+ require 'sqreen/exception'
7
9
 
8
10
  require 'net/https'
9
11
  require 'json'
@@ -46,6 +48,7 @@ module Sqreen
46
48
  @server_url = server_url
47
49
  @request_compression = false
48
50
  @connected = nil
51
+ @con = nil
49
52
 
50
53
  uri = parse_uri(server_url)
51
54
  use_ssl = (uri.scheme == 'https')
@@ -71,6 +74,7 @@ module Sqreen
71
74
  end
72
75
 
73
76
  def prefix_path(path)
77
+ return '/sqreen/v1/' + path if path == 'app-login' || path == 'app-beat'
74
78
  @@path_prefix + path
75
79
  end
76
80
 
@@ -180,7 +184,11 @@ module Sqreen
180
184
  when :GET
181
185
  @con.get(path, headers)
182
186
  when :POST
183
- json_data = compress(self.class.encode_payload(data))
187
+ json_data = nil
188
+ unless data.nil?
189
+ serialized = Serializer.serialize(data)
190
+ json_data = compress(self.class.encode_payload(serialized))
191
+ end
184
192
  @con.post(path, json_data, headers)
185
193
  else
186
194
  Sqreen.log.debug format('unknown method %s', method)
@@ -203,7 +211,7 @@ module Sqreen
203
211
 
204
212
  def self.encode_payload(data)
205
213
  JSON.generate(data)
206
- rescue JSON::GeneratorError
214
+ rescue JSON::GeneratorError, Encoding::UndefinedConversionError
207
215
  Sqreen.log.debug('Payload could not be encoded enforcing recode')
208
216
  JSON.generate(rencode_payload(data))
209
217
  end
@@ -242,12 +250,14 @@ module Sqreen
242
250
 
243
251
  def self.enforce_encoding(str)
244
252
  return str unless str.is_a?(String)
245
- return str if str.valid_encoding?
253
+ return str if str.ascii_only?
254
+ encoded8bit = str.encoding.name == 'ASCII-8BIT'
255
+ return str if !encoded8bit && str.valid_encoding?
246
256
  str.chars.map do |v|
247
- if v.valid_encoding?
248
- v
249
- else
257
+ if !v.valid_encoding? || (encoded8bit && !v.ascii_only?)
250
258
  v.bytes.map { |c| "\\x#{c.to_s(16).upcase}" }.join
259
+ else
260
+ v
251
261
  end
252
262
  end.join
253
263
  end
@@ -267,19 +277,19 @@ module Sqreen
267
277
  @session_id = res['session_id']
268
278
  Sqreen.log.debug "received session_id #{@session_id}"
269
279
  Sqreen.logged_in = true
270
- res.fetch('features', {})
280
+ res
271
281
  end
272
282
 
273
283
  def rules
274
284
  resilient_get('rulespack')
275
285
  end
276
286
 
277
- def heartbeat
278
- get('app-beat', {}, 5)
279
- end
287
+ def heartbeat(cmd_res = {}, metrics = [])
288
+ payload = {}
289
+ payload['metrics'] = metrics unless metrics.nil? || metrics.empty?
290
+ payload['command_results'] = cmd_res unless cmd_res.nil? || cmd_res.empty?
280
291
 
281
- def post_commands_result(res)
282
- resilient_post('commands', res)
292
+ post('app-beat', payload.empty? ? nil : payload, {}, 5)
283
293
  end
284
294
 
285
295
  def post_metrics(metrics)
@@ -2,5 +2,5 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
  # Warning This file is auto generated! DO NOT edit.
4
4
  module Sqreen
5
- VERSION = "0.8.11465220943".freeze
5
+ VERSION = "1.0.0.pre1480953244".freeze
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqreen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.11465220943
4
+ version: 1.0.0.pre1480953244
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sqreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-06 00:00:00.000000000 Z
11
+ date: 2016-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: execjs
@@ -60,9 +60,6 @@ files:
60
60
  - lib/sqreen/context.rb
61
61
  - lib/sqreen/deliveries/batch.rb
62
62
  - lib/sqreen/deliveries/simple.rb
63
- - lib/sqreen/detect.rb
64
- - lib/sqreen/detect/shell_injection.rb
65
- - lib/sqreen/detect/sql_injection.rb
66
63
  - lib/sqreen/event.rb
67
64
  - lib/sqreen/events/attack.rb
68
65
  - lib/sqreen/events/remote_exception.rb
@@ -81,9 +78,6 @@ files:
81
78
  - lib/sqreen/metrics/collect.rb
82
79
  - lib/sqreen/metrics/sum.rb
83
80
  - lib/sqreen/metrics_store.rb
84
- - lib/sqreen/parsers/sql.rb
85
- - lib/sqreen/parsers/sql_tokenizer.rb
86
- - lib/sqreen/parsers/unix.rb
87
81
  - lib/sqreen/payload_creator.rb
88
82
  - lib/sqreen/performance_notifications.rb
89
83
  - lib/sqreen/performance_notifications/log.rb
@@ -106,15 +100,13 @@ files:
106
100
  - lib/sqreen/rules_callbacks/record_request_context.rb
107
101
  - lib/sqreen/rules_callbacks/reflected_xss.rb
108
102
  - lib/sqreen/rules_callbacks/regexp_rule.rb
109
- - lib/sqreen/rules_callbacks/shell.rb
110
103
  - lib/sqreen/rules_callbacks/shell_env.rb
111
- - lib/sqreen/rules_callbacks/sql.rb
112
- - lib/sqreen/rules_callbacks/system_shell.rb
113
104
  - lib/sqreen/rules_callbacks/url_matches.rb
114
105
  - lib/sqreen/rules_callbacks/user_agent_matches.rb
115
106
  - lib/sqreen/rules_signature.rb
116
107
  - lib/sqreen/runner.rb
117
108
  - lib/sqreen/runtime_infos.rb
109
+ - lib/sqreen/serializer.rb
118
110
  - lib/sqreen/session.rb
119
111
  - lib/sqreen/stats.rb
120
112
  - lib/sqreen/version.rb
@@ -132,12 +124,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
124
  version: '0'
133
125
  required_rubygems_version: !ruby/object:Gem::Requirement
134
126
  requirements:
135
- - - ">="
127
+ - - ">"
136
128
  - !ruby/object:Gem::Version
137
- version: '0'
129
+ version: 1.3.1
138
130
  requirements: []
139
131
  rubyforge_project:
140
- rubygems_version: 2.6.4
132
+ rubygems_version: 2.5.1
141
133
  signing_key:
142
134
  specification_version: 4
143
135
  summary: Sqreen Ruby agent
@@ -1,14 +0,0 @@
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
@@ -1,61 +0,0 @@
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
@@ -1,115 +0,0 @@
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
@@ -1,98 +0,0 @@
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_tokenizer'
5
- require 'sqreen/exception'
6
-
7
- module Sqreen
8
- module Parsers
9
- # SELECT * FROM contacts WHERE name = #{name}
10
- # name = 'a' 'b'
11
-
12
- class SQLAtom
13
- attr_reader :val, :type, :delim
14
- def initialize(val, typ = :unkown, delim = nil, token_type = nil)
15
- @val = val
16
- @type = typ
17
- @token_type = token_type
18
- @delim = delim
19
- end
20
-
21
- def ==(other)
22
- return false if other.nil?
23
- @val == other.val &&
24
- @type == other.type &&
25
- @delim == other.delim
26
- end
27
-
28
- def is_literal?
29
- [:literal_string, :literal_number].include? @type
30
- end
31
-
32
- def to_s
33
- res = ":#{@type || @token_type}: #{@val}"
34
- res += " (#{@delim})" if delim
35
- res
36
- end
37
- end
38
-
39
- class SQL
40
- attr_reader :atoms, :tokenizer
41
-
42
- def initialize(db_type, db_infos)
43
- klass = case db_type
44
- when :mysql
45
- MySQLTokenizer
46
- when :sqlite
47
- SQLiteTokenizer
48
- else
49
- Sqreen.log.warn format('warning: db_type %s not found, falling back to default SQL type', db_type)
50
- SQLTokenizer
51
- end
52
- @tokenizer = klass.new(db_type, db_infos)
53
- end
54
-
55
- def to_s
56
- res = "Parsed: #{@req}\n"
57
- nb = 0
58
- @atoms.each do |atom|
59
- res << "\t" + nb.to_s + ': ' + atom.to_s + "\n"
60
- nb += 1
61
- end
62
- res
63
- end
64
-
65
- def parse(req)
66
- @req = req
67
- @atoms = []
68
- @tokenizer.tokenize(req)
69
- @tokenizer.each_token do |token, meta_type|
70
- value = token[1]
71
- type = token[0]
72
- delim = case type
73
- when :SINGLE_QUOTED_STRING
74
- "'"
75
- when :DOUBLE_QUOTED_STRING
76
- '"'
77
- end
78
- atom = SQLAtom.new(value, meta_type, delim, type)
79
- @atoms << atom
80
- end
81
- end
82
-
83
- def to_atoms(ary, type = :unknown)
84
- ary.map { |frag| to_atom(frag, nil, type) }
85
- end
86
-
87
- def to_atom(_frag, _type = :unknown, _delim = nil)
88
- end
89
- end
90
- end
91
- end
92
-
93
- if $0 == __FILE__
94
- s = Sqreen::Parsers::SQL.new :mysql, {}
95
- s.parse(ARGV[0])
96
- puts s
97
-
98
- end