sqreen 0.8.11465220943 → 1.0.0.pre1480953244

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