sqreen 1.8.5 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3f399192916d62e32ac7dc431d7f9567451b3b4c
4
- data.tar.gz: c50f2371cedb78ea513b13773da2fcab80786994
3
+ metadata.gz: bd6678aaddb8e5e15e00d1d91bd9acc54ad5b501
4
+ data.tar.gz: 1611328a970e0d555cf1c27318dd10578b244946
5
5
  SHA512:
6
- metadata.gz: 98569486b7c5344745d4542f0a856673db74431f703ee96c192c0b28efda36ddefc6ee88978ef93f4bea7a7a92264c9eba55c52a3b03de16ed461bec4d88b3a0
7
- data.tar.gz: 16bc37bfdbf917d013f66559cf34431e1ac39c62678cd3ad9aab2c66d2f6b8ac084dc15fea24b2f00e8da2b9aeff81938f2f6418c5e325e98d5c9a8b2be2c16f
6
+ metadata.gz: 10ef0a5054578df596b1c62b0aae786d81ba52715366c08797d772ee66312502e9e63b6afce333dcda5822f27524a147c3023faf9791d78219098d610fb1617d
7
+ data.tar.gz: 914169b9db37992430b91b92ac1778bf9209176e4d03a4263c1deabac4d0ca36efbe59ea10f688a5419fb7fde170cb87ef33ccc6eb5de3eaa693834299c0b76c
@@ -1,2 +1,2 @@
1
- <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Sqreen has detected an attack.</title> <style>html,body,div,span,h1,a{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1}svg,h1,p{display:block}svg{margin:0 auto 4vh}h1{font-family:sans-serif;font-weight:300;font-size:34px;color:#384886;line-height:normal}p{font-size:18px;line-height:normal;color:#b8bccc;font-family:sans-serif;font-weight:300}a{color:#b8bccc}.flex{text-align:center}</style></head><body> <div class="flex"> <svg xmlns="http://www.w3.org/2000/svg" width="230" height="250" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path id="b" d="M176.55 22.93v-.03c-.17-1.13-.8-2.1-1.9-2.77C170.93 18.07 136.8.03 88.98 0 40.25 0 4.8 19.28 3.28 20.14c-.98.6-1.63 1.6-1.77 2.72-14.8 123.1 84.7 176.2 85.7 176.7.6.3 1.2.44 1.8.44.6 0 1.2-.15 1.7-.42 1-.52 100.4-55.03 85.9-176.65z"/> <filter id="a" width="151.7%" height="146%" x="-25.8%" y="-16%" filterUnits="objectBoundingBox"> <feOffset dy="14" in="SourceAlpha" result="shadowOffsetOuter1"/> <feGaussianBlur stdDeviation="13" in="shadowOffsetOuter1" result="shadowBlurOuter1"/> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowBlurOuter1"/> </filter> </defs> <g fill="none" fill-rule="evenodd"> <g fill-rule="nonzero" transform="translate(26 12)"> <use fill="#000" filter="url(#a)" xlink:href="#b"/> <use fill="#FFF" fill-rule="evenodd" xlink:href="#b"/> </g> <path fill="#374886" d="M153.85 75.8s0 .68-.35 1.13c-.46.57-8.52 10.74-12.32 15.6-3.8 4.98-11.52 2.26-11.52 2.26s2.54-1.5 4.15-3.4c4.3-5.3 12.5-15.7 12.5-15.7s-8.2-10.2-12.4-15.6c-1.5-2-4.1-3.4-4.1-3.4s7.7-2.6 11.5 2.2c3.8 4.9 11.9 15.1 12.2 15.7.5.4.5 1.1.5 1.1m-78 .1s0 .6.4 1.1c.4.4 8.5 10.7 12.2 15.6 3.7 4.8 11.5 2.2 11.5 2.2s-2.5-1.5-4.1-3.4c-4.3-5.2-12.5-15.5-12.5-15.5l12.4-15.7c1.5-2 4.2-3.4 4.2-3.4s-7.7-2.6-11.5 2.2C84.8 63.7 76.9 74 76.5 74.6c-.32.55-.44 1.12-.44 1.12M115 62.82c-7.03 0-12.9 5.78-12.9 12.9s5.75 12.9 12.9 12.9c7.13 0 12.9-5.8 12.9-12.9s-5.9-12.9-12.9-12.9m-33.38 58.6c3.46 0 5.4-1.77 5.4-4.1 0-5.15-7.4-3.56-7.4-5.47 0-.8.78-1.3 1.97-1.3 1.5 0 2.9.6 3.7 1.5l1.3-2.3c-1.3-1-2.9-1.8-5.1-1.8-3.3 0-5.1 1.9-5.1 4 0 5 7.5 3.3 7.5 5.4 0 .8-.6 1.4-2.1 1.4s-3.4-.9-4.3-1.8l-1.4 2.4c1.4 1.2 3.4 2 5.6 2zm13.53-3c-1.8 0-3.1-1.5-3.1-3.7s1.3-3.7 3.2-3.7c1.1 0 2.3.6 2.8 1.44v4.5c-.5.8-1.7 1.46-2.8 1.46zm-1 3c1.5 0 2.9-.64 3.9-1.96v6.5h3.3v-17.63H98v1.6c-.96-1.23-2.33-1.92-3.86-1.92-3.2 0-5.52 2.5-5.52 6.7 0 4.3 2.33 6.7 5.53 6.7zm13.7-.32v-8.42c.6-.8 2-1.4 3.1-1.4.4 0 .7.1.9.1v-3.3c-1.5 0-3.1 1-3.9 2.1v-1.7h-3.3v12.8h3.3zm11.9.33c2 0 3.9-.6 5.2-1.77l-1.4-2.2c-.8.8-2.26 1.2-3.32 1.2-2.1 0-3.4-1.4-3.6-3h9.3v-.7c0-4.2-2.53-7.1-6.25-7.1-3.8 0-6.45 3-6.45 6.7 0 4.05 2.8 6.7 6.6 6.7zm2.9-7.9h-6.1c.2-1.27 1.07-2.83 3.1-2.83 2.18 0 3 1.6 3.08 2.87zm11.4 7.9c2 0 3.9-.6 5.2-1.77l-1.42-2.2c-.8.8-2.3 1.2-3.34 1.2-2.2 0-3.4-1.4-3.6-3h9.2v-.7c0-4.2-2.6-7.1-6.3-7.1-3.8 0-6.4 3-6.4 6.7 0 4.05 2.82 6.7 6.62 6.7zm2.9-7.9h-6.1c.17-1.27 1.05-2.83 3.1-2.83 2.2 0 3 1.6 3.1 2.87zm17.3 7.58v-9c0-2.5-1.3-4-4.03-4-2.08 0-3.6 1-4.4 2v-1.64h-3.3v12.76h3.3v-8.6c.53-.74 1.53-1.5 2.82-1.5 1.4 0 2.3.63 2.3 2.4v7.7h3.2z"/> </g> </svg> <h1>Uh Oh! Sqreen has detected an attack.</h1> <p>If you are the application owner, check the Sqreen <a href="https://my.sqreen.io/">dashboard</a> for more information.</p></div></body></html>
1
+ <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Sqreen has detected an attack.</title> <style>html, body, div, span, h1, a{margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline}body{background: -webkit-radial-gradient(26% 19%, circle, #fff, #f4f7f9); background: radial-gradient(circle at 26% 19%, #fff, #f4f7f9); display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -ms-flex-line-pack: center; align-content: center; width: 100%; min-height: 100vh; line-height: 1}svg, h1, p{display: block}svg{margin: 0 auto 4vh}h1{font-family: sans-serif; font-weight: 300; font-size: 34px; color: #384886; line-height: normal}p{font-size: 18px; line-height: normal; color: #b8bccc; font-family: sans-serif; font-weight: 300}a{color: #b8bccc}.flex{text-align: center}</style></head><body> <div class="flex"> <svg xmlns="http://www.w3.org/2000/svg" width="230" height="250" viewBox="0 0 230 250" enable-background="new 0 0 230 250"> <style>.st0{opacity: 0.4; filter: url(#a);}.st1{fill: #FFFFFF;}.st2{fill: #B0ACFF;}.st3{fill: #4842B7;}.st4{fill: #1E0936;}</style> <filter id="a" width="151.7%" height="146%" x="-25.8%" y="-16%" filterUnits="objectBoundingBox"> <feOffset dy="14" in="SourceAlpha" result="shadowOffsetOuter1"/> <feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="13"/> <feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/> </filter> <g class="st0"> <path id="b_2_" d="M202.6 34.9c-.2-1.2-.8-2.1-1.9-2.8-3.8-2-37.9-20.1-85.7-20.1-48.8 0-84.2 19.3-85.7 20.1-1 .6-1.6 1.6-1.8 2.7-14.8 123.2 84.7 176.3 85.7 176.8.6.3 1.2.4 1.8.4.6 0 1.2-.1 1.7-.4 1-.5 100.4-55 85.9-176.7z"/> </g> <path id="b_1_" d="M202.6 34.9c-.2-1.2-.8-2.1-1.9-2.8-3.8-2-37.9-20.1-85.7-20.1-48.8 0-84.2 19.3-85.7 20.1-1 .6-1.6 1.6-1.8 2.7-14.8 123.2 84.7 176.3 85.7 176.8.6.3 1.2.4 1.8.4.6 0 1.2-.1 1.7-.4 1-.5 100.4-55 85.9-176.7z" class="st1"/> <g id="nest-cmyk-indigo"> <ellipse id="sqreen" cx="115.5" cy="69.9" class="st2" rx="12.7" ry="12.7"/> <path id="app" d="M113.6 91.9V71.5L95.5 61.1v18l6.4-3.7c.5 1.1 1 2.2 1.7 3.2L97 82.3l16.6 9.6zm3.7 0l16.6-9.6-6.7-3.9c.7-1 1.3-2 1.7-3.2l6.4 3.7v-18l-18.1 10.5v20.5zM96.9 57.6l18.6 10.7L134 57.6 117.3 48v7.6c-.6-.1-1.2-.1-1.8-.1-.6 0-1.2 0-1.8.1V48l-16.8 9.6zm20.2-13.9l20.3 11.7c1 .6 1.6 1.7 1.6 2.8v23.5c0 1.2-.6 2.2-1.6 2.8l-20.3 11.7c-1 .6-2.3.6-3.3 0L93.5 84.5c-1-.6-1.6-1.7-1.6-2.8V58.2c0-1.2.6-2.2 1.6-2.8l20.3-11.7c1-.6 2.3-.6 3.3 0z" class="st3"/> </g> <path id="s" d="M74.6 113c-1.8-1-3.5-1.5-5.2-1.5-1.4 0-2.3.6-2.3 1.5 0 2.7 10.1.4 10.1 7.7 0 3.3-2.9 6-7.6 6-2.1 0-4.7-.5-6.4-1.4l-.1-.1c-.3-.2-.3-.5-.2-.8l1.2-2.7c.1-.3.5-.5.9-.3.1 0 .1.1.2.1 1.5.6 3.1 1 4.6 1 2.2 0 2.9-.6 2.9-1.7 0-3-10.1-.8-10.1-7.7 0-3.1 2.7-5.8 7-5.8 2.1 0 5 .7 6.9 1.8.1 0 .1.1.2.1.3.2.4.5.3.8l-1.2 2.7c-.1.3-.5.5-.9.3h-.3z" class="st4"/> <path id="q" d="M93.6 107.8h3.2c.4 0 .7.3.7.7v25.9c0 .4-.3.7-.7.7h-3.2c-.4 0-.7-.3-.7-.7v-9.1c-1.2.8-2.9 1.4-4.7 1.4-5.4 0-9.6-4.3-9.6-9.7 0-5.4 4.1-9.7 9.6-9.7 1.8 0 3.5.6 4.7 1.4v-.1c0-.5.3-.8.7-.8zm-.7 12.4v-6.5c-1.3-1.3-2.8-2.1-4.5-2.1-2.9 0-5.1 2.3-5.1 5.4s2.2 5.4 5.1 5.4c1.7-.1 3.2-.7 4.5-2.2z" class="st4"/> <path id="r" d="M112.5 107.8c-1-.4-2-.6-3-.6-1.8 0-3.5.6-4.9 1.4v-.2c0-.3-.2-.5-.5-.5h-3.4c-.3 0-.5.2-.5.5v17.8c0 .3.2.5.5.5h3.4c.3 0 .5-.2.5-.5v-12.6c1.1-1.2 2.8-1.9 4.6-1.9.4 0 .9 0 1.5.2.3.1.6-.1.7-.4l1.3-2.9c.1-.4 0-.7-.2-.8z" class="st4"/> <path id="e" d="M129 124.7c-1.7 1-4.2 2-6.7 2-6 0-10.3-4.4-10.3-9.9 0-5.3 3.7-9.6 9.4-9.6 5.2 0 8.4 4.4 8.4 9 0 .4 0 .9-.1 1.2 0 .3-.3.6-.7.6h-12.5c.5 2.8 2.8 4.5 5.8 4.5 1.7 0 3.4-.5 5.1-1.4.3-.2.6-.1.8.2l1.2 2.6c.1.2 0 .4-.2.6-.2.1-.2.2-.2.2zm-12.4-10h8.5c-.2-1.8-1.9-3.3-3.9-3.3-2.5-.1-4 1.4-4.6 3.3z" class="st4"/> <path id="e_1_" d="M148.7 124.7c-1.7 1-4.2 2-6.7 2-6 0-10.3-4.4-10.3-9.9 0-5.3 3.7-9.6 9.4-9.6 5.2 0 8.4 4.4 8.4 9 0 .4 0 .9-.1 1.2 0 .3-.3.6-.7.6h-12.5c.5 2.8 2.8 4.5 5.8 4.5 1.7 0 3.4-.5 5.1-1.4.3-.2.6-.1.8.2l1.2 2.6c.1.2 0 .4-.2.6-.2.1-.2.2-.2.2zm-12.4-10h8.5c-.2-1.8-1.9-3.3-3.9-3.3-2.5-.1-4 1.4-4.6 3.3z" class="st4"/> <path id="n" d="M151.5 108.5V126c0 .4.3.7.7.7h3.2c.4 0 .7-.3.7-.7v-12.5c1.1-1.2 2.8-1.9 4.6-1.9 2.9 0 4.5 1.6 4.5 4.7v9.7c0 .4.3.7.7.7h3.2c.4 0 .7-.3.7-.7v-10.2c0-5.2-2.9-8.5-8.8-8.5-1.8 0-3.5.6-4.9 1.4v-.1c0-.4-.3-.7-.7-.7h-3.2c-.4-.1-.7.2-.7.6z" class="st4"/> </svg> <h1>Uh Oh! Sqreen has detected an attack.</h1> <p>If you are the application owner, check the Sqreen <a href="https://my.sqreen.io/">dashboard</a> for more information.</p></div></body></html>
2
2
 
@@ -2,35 +2,9 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  require 'set'
5
+ require 'sqreen/shared_storage'
5
6
 
6
7
  module Sqreen
7
- module SharedStorage
8
- @@shared = {}
9
-
10
- def self::get(key, default = nil)
11
- h = @@shared[Thread.current]
12
- return h.fetch(key, default) if h
13
- default
14
- end
15
-
16
- def self::set(key, obj)
17
- main_key = Thread.current
18
- @@shared[main_key] = {} unless @@shared.key? main_key
19
- @@shared[main_key][key] = obj
20
- end
21
-
22
- def self.clear
23
- @@shared.delete(Thread.current)
24
- end
25
-
26
- def self.inc(value)
27
- set(value, get(value, 0) + 1)
28
- end
29
-
30
- def self.dec(value)
31
- set(value, get(value, 0) - 1)
32
- end
33
- end
34
8
 
35
9
  class CB
36
10
  # Callback class.
@@ -6,6 +6,10 @@ module Sqreen
6
6
  class Context
7
7
  attr_accessor :bt
8
8
 
9
+ def self.bt
10
+ Context.new.bt
11
+ end
12
+
9
13
  def initialize
10
14
  @bt = get_current_backtrace
11
15
  end
@@ -8,7 +8,7 @@ module Sqreen
8
8
  # Simple delivery method that batch event already seen in a batch
9
9
  class Batch < Simple
10
10
  attr_accessor :max_batch, :max_staleness
11
- attr_accessor :current_batch
11
+ attr_accessor :current_batch, :first_seen
12
12
 
13
13
  def initialize(session,
14
14
  max_batch,
@@ -40,18 +40,17 @@ module Sqreen
40
40
 
41
41
  def stale?
42
42
  min = @first_seen.values.min
43
+ return false if min.nil?
43
44
  (min + max_staleness) < Time.now
44
45
  end
45
46
 
46
47
  def post_batch_needed?(event)
47
- key = event_key(event)
48
- was = @first_seen[key]
49
48
  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
49
+ event_keys(event).map do |key|
50
+ was = @first_seen[key]
51
+ @first_seen[key] ||= now
52
+ was.nil? || current_batch.size > max_batch || (was + max_staleness) < now
53
+ end.any?
55
54
  end
56
55
 
57
56
  def post_batch
@@ -67,12 +66,17 @@ module Sqreen
67
66
  self.max_staleness += rand(@original_max_staleness / 10)
68
67
  end
69
68
 
69
+ def event_keys(event)
70
+ return [event_key(event)] unless event.is_a?(Sqreen::RequestRecord)
71
+ event.observed.fetch(:attacks, []).map { |e| "att-#{e[:rule_name]}" } + event.observed.fetch(:sqreen_exceptions, []).map { |e| "rex-#{e[:exception].class}" }
72
+ end
73
+
70
74
  def event_key(event)
71
75
  case event
72
76
  when Sqreen::Attack
73
- return "att-#{event.type}"
77
+ "att-#{event.type}"
74
78
  when Sqreen::RemoteException
75
- return "rex-#{event.klass}"
79
+ "rex-#{event.klass}"
76
80
  end
77
81
  end
78
82
  end
@@ -2,6 +2,7 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  require 'sqreen/events/remote_exception'
5
+ require 'sqreen/events/request_record'
5
6
 
6
7
  module Sqreen
7
8
  module Deliveries
@@ -19,6 +20,8 @@ module Sqreen
19
20
  session.post_attack(event)
20
21
  when Sqreen::RemoteException
21
22
  session.post_sqreen_exception(event)
23
+ when Sqreen::RequestRecord
24
+ session.post_request_record(event)
22
25
  else
23
26
  session.post_event(event)
24
27
  end
@@ -0,0 +1,54 @@
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 a request is deeemed worthy of being sent to the backend
9
+ class RequestRecord < Sqreen::Event
10
+ def observed
11
+ (payload && payload[:observed]) || {}
12
+ end
13
+
14
+ def to_hash
15
+ res = { :version => '20171208' }
16
+ if payload[:observed]
17
+ res[:observed] = payload[:observed]
18
+ rulespack = nil
19
+ observed.fetch(:attacks, []).each do |att|
20
+ rulespack = att.delete(:rulespack_id) || rulespack
21
+ end
22
+ observed.fetch(:sqreen_exceptions, []).each do |exc|
23
+ excp = exc.delete(:exception)
24
+ if excp
25
+ exc[:message] = excp.message
26
+ exc[:klass] = excp.class.name
27
+ end
28
+ rulespack = exc.delete(:rulespack_id) || rulespack
29
+ end
30
+ res[:rulespack_id] = rulespack unless rulespack.nil?
31
+ if observed[:observations]
32
+ res[:observed][:observations] = observed[:observations].map do |cat, key, value, time|
33
+ { :category => cat, :key => key, :value => value, :time => time }
34
+ end
35
+ end
36
+ if observed[:sdk]
37
+ payload[:observed][:sdk] = observed[:sdk].map do |meth, time, *args|
38
+ { :name => meth, :time => time, :args => args }
39
+ end
40
+ end
41
+ end
42
+ res[:local] = payload['local'] if payload['local']
43
+ if payload['request']
44
+ res[:request] = payload['request']
45
+ res[:client_ip] = res[:request].delete(:client_ip) if res[:request][:client_ip]
46
+ else
47
+ res[:request] = {}
48
+ end
49
+ res[:request][:parameters] = payload['params'] if payload['params']
50
+ res[:request][:headers] = payload['headers'] if payload['headers']
51
+ res
52
+ end
53
+ end
54
+ end
@@ -11,7 +11,7 @@ module Sqreen
11
11
  def self::framework
12
12
  return @@framework if @@framework
13
13
  klass = case
14
- when defined?(::Rails)
14
+ when defined?(::Rails) && defined?(::Rails::VERSION)
15
15
  case Rails::VERSION::MAJOR.to_i
16
16
  when 4, 5
17
17
  require 'sqreen/frameworks/rails'
@@ -6,11 +6,13 @@ require 'sqreen/events/remote_exception'
6
6
  require 'sqreen/callbacks'
7
7
  require 'sqreen/exception'
8
8
  require 'sqreen/log'
9
+ require 'sqreen/frameworks/request_recorder'
9
10
 
10
11
  module Sqreen
11
12
  module Frameworks
12
13
  # This is the base class for framework specific code
13
14
  class GenericFramework
15
+ include RequestRecorder
14
16
  attr_accessor :sqreen_configuration
15
17
 
16
18
  def initialize
@@ -19,6 +21,7 @@ module Sqreen
19
21
  else
20
22
  to_app_done(Process.pid)
21
23
  end
24
+ clean_request_record
22
25
  end
23
26
 
24
27
  # What kind of database is this
@@ -40,13 +43,16 @@ module Sqreen
40
43
  ENV['RACK_ENV'] == 'development'
41
44
  end
42
45
 
46
+ PREFFERED_IP_HEADERS = %w(HTTP_X_FORWARDED_FOR HTTP_X_REAL_IP
47
+ HTTP_CLIENT_IP HTTP_X_FORWARDED
48
+ HTTP_X_CLUSTER_CLIENT_IP HTTP_FORWARDED_FOR
49
+ HTTP_FORWARDED HTTP_VIA).freeze
50
+
43
51
  def ip_headers
44
52
  req = request
45
53
  return [] unless req
46
54
  ips = []
47
- %w(HTTP_X_FORWARDED_FOR HTTP_CLIENT_IP HTTP_X_REAL_IP HTTP_X_FORWARDED
48
- HTTP_X_CLUSTER_CLIENT_IP HTTP_FORWARDED_FOR HTTP_FORWARDED HTTP_VIA
49
- REMOTE_ADDR).each do |header|
55
+ (PREFFERED_IP_HEADERS + ['REMOTE_ADDR']).each do |header|
50
56
  v = req.env[header]
51
57
  ips << [header, v] unless v.nil?
52
58
  end
@@ -71,16 +77,22 @@ module Sqreen
71
77
  req = request
72
78
  return nil unless req
73
79
  # Look for an external address being forwarded
74
- forwarded = req.env['HTTP_X_FORWARDED_FOR']
75
- ips = split_ip_addresses(forwarded)
76
- last = ips.find { |ip| (ip !~ TRUSTED_PROXIES) && valid_ip?(ip) }
77
- return last unless last.nil?
80
+ split_ips = []
81
+ PREFFERED_IP_HEADERS.each do |header_name|
82
+ forwarded = req.env[header_name]
83
+ ips = split_ip_addresses(forwarded)
84
+ lip = ips.find { |ip| (ip !~ TRUSTED_PROXIES) && valid_ip?(ip) }
85
+ split_ips << ips unless ips.empty?
86
+ return lip unless lip.nil?
87
+ end
78
88
  # Else fall back to declared remote addr
79
89
  r = req.env['REMOTE_ADDR']
80
90
  # If this is localhost get the last hop before
81
- if !ips.empty? && r =~ LOCALHOST
82
- last = ips.find { |ip| (ip !~ LOCALHOST) && valid_ip?(ip) }
83
- return last unless last.nil?
91
+ if r.nil? || r =~ LOCALHOST
92
+ split_ips.each do |ips|
93
+ lip = ips.find { |ip| (ip !~ LOCALHOST) && valid_ip?(ip) }
94
+ return lip unless lip.nil?
95
+ end
84
96
  end
85
97
  r
86
98
  end
@@ -124,10 +136,11 @@ module Sqreen
124
136
  :verb => req.env['REQUEST_METHOD'],
125
137
  :host => hostname,
126
138
  :port => req.env['SERVER_PORT'],
127
- :rport => req.env['REMOTE_PORT'],
128
139
  :referer => req.env['HTTP_REFERER'],
129
140
  :path => request_path,
130
- :addr => client_ip,
141
+ :remote_port => req.env['REMOTE_PORT'],
142
+ :remote_ip => remote_addr,
143
+ :client_ip => client_ip,
131
144
  }
132
145
  end
133
146
 
@@ -221,6 +234,8 @@ module Sqreen
221
234
  # Cleanup request context
222
235
  def clean_request
223
236
  return unless SharedStorage.dec(:stored_requests) <= 0
237
+ payload_creator = Sqreen::PayloadCreator.new(self)
238
+ close_request_record(Sqreen.queue, Sqreen.observations_queue, payload_creator)
224
239
  SharedStorage.set(:request, nil)
225
240
  end
226
241
 
@@ -327,6 +342,11 @@ module Sqreen
327
342
  nil
328
343
  end
329
344
 
345
+ def remote_addr
346
+ return nil unless request
347
+ request.env['REMOTE_ADDR']
348
+ end
349
+
330
350
  protected
331
351
 
332
352
  # Is this a whitelisted path?
@@ -0,0 +1,68 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+ require 'set'
4
+ require 'sqreen/shared_storage'
5
+ require 'sqreen/events/request_record'
6
+
7
+ module Sqreen
8
+ # Store event/observations that happened in this request
9
+ module RequestRecorder
10
+ def observed_items
11
+ SharedStorage.get(:observed_items)
12
+ end
13
+
14
+ def observed_items=(value)
15
+ SharedStorage.set(:observed_items, value)
16
+ end
17
+
18
+ def payload_requests
19
+ SharedStorage.get(:payload_requests)
20
+ end
21
+
22
+ def payload_requests=(value)
23
+ SharedStorage.set(:payload_requests, value)
24
+ end
25
+
26
+ def only_metric_observation
27
+ SharedStorage.get(:only_metric_observation)
28
+ end
29
+
30
+ def only_metric_observation=(value)
31
+ SharedStorage.set(:only_metric_observation, value)
32
+ end
33
+
34
+ def clean_request_record
35
+ self.only_metric_observation = true
36
+ self.payload_requests = Set.new
37
+ self.observed_items = Hash.new { |hash, key| hash[key] = [] }
38
+ end
39
+
40
+ def observe(what, data, accessors = [], report = true)
41
+ clean_request_record if observed_items.nil?
42
+ self.only_metric_observation = false if report
43
+ observed_items[what] << data
44
+ payload_requests.merge(accessors)
45
+ end
46
+
47
+ def close_request_record(queue, observations_queue, payload_creator)
48
+ if only_metric_observation
49
+ push_metrics(observations_queue, queue)
50
+ return clean_request_record
51
+ end
52
+ payload = payload_creator.payload(payload_requests)
53
+ payload[:observed] = observed_items
54
+ queue.push RequestRecord.new(payload)
55
+ clean_request_record
56
+ end
57
+
58
+ protected
59
+
60
+ def push_metrics(observations_queue, event_queue)
61
+ observed_items[:observations].each do |obs|
62
+ observations_queue.push obs
63
+ end
64
+ return unless observations_queue.size > MAX_OBS_QUEUE_LENGTH / 2
65
+ event_queue.push Sqreen::METRICS_EVENT
66
+ end
67
+ end
68
+ end
@@ -1,7 +1,6 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
- require 'sqreen/context'
5
4
  require 'sqreen/runtime_infos'
6
5
  require 'sqreen/events/remote_exception'
7
6
 
@@ -15,8 +14,9 @@ module Sqreen
15
14
  # The payload will always be outputed as a
16
15
  # Hash of section => subsection.
17
16
  class PayloadCreator
18
- def initialize(query)
19
- self.query = query
17
+ attr_reader :framework
18
+ def initialize(framework)
19
+ @framework = framework
20
20
  end
21
21
 
22
22
  def query=(keys)
@@ -30,24 +30,25 @@ module Sqreen
30
30
  end
31
31
  end
32
32
 
33
- def payload(framework, rule = {})
33
+ def payload(query)
34
+ self.query = query
34
35
  ret = {}
35
36
  METHODS.each_key do |section|
36
- ret = fill(section, ret, framework, rule)
37
+ ret = fill(section, ret, @framework)
37
38
  end
38
39
  ret
39
40
  end
40
41
 
41
42
  protected
42
43
 
43
- def fill(key, base, framework, rule)
44
+ def fill(key, base, framework)
44
45
  subsection = @sections[key]
45
46
  return base if subsection.nil?
46
47
  if subsection == true
47
- return base.merge!(key => full_section(key, framework, rule))
48
+ return base.merge!(key => full_section(key, framework))
48
49
  end
49
50
  return base if subsection.empty?
50
- base[key] = fields(key, framework, rule)
51
+ base[key] = fields(key, framework)
51
52
  base
52
53
  end
53
54
 
@@ -72,33 +73,25 @@ module Sqreen
72
73
  'cookies' => 'cookies_params',
73
74
  'rails' => 'rails_params',
74
75
  },
75
- 'rule' => {},
76
76
  'headers' => {},
77
- 'context' => {
78
- 'backtrace' => 'get_current_backtrace',
79
- },
80
77
  }.freeze
81
78
 
82
- def section_object(section, framework, rule)
79
+ def section_object(section, framework)
83
80
  return RuntimeInfos if section == 'local'
84
- return rule if section == 'rule'
85
- return Context.new if section == 'context'
86
81
  return HeaderSection.new(framework) if section == 'headers'
87
82
  framework
88
83
  end
89
84
 
90
- def full_section(section, framework, rule)
91
- return section_rule(framework, rule) if section == 'rule'
92
- return section_context(framework, rule) if section == 'context'
85
+ def full_section(section, framework)
93
86
  # fast path prevent initializing a HeaderSection
94
87
  return framework.ip_headers if section == 'headers'
95
- so = section_object(section, framework, rule)
88
+ so = section_object(section, framework)
96
89
  so.send(FULL_SECTIONS[section])
97
90
  end
98
91
 
99
- def fields(section, framework, rule)
92
+ def fields(section, framework)
100
93
  out = {}
101
- object = section_object(section, framework, rule)
94
+ object = section_object(section, framework)
102
95
  remove = []
103
96
  @sections[section].each do |key|
104
97
  meth = METHODS[section][key]
@@ -119,21 +112,6 @@ module Sqreen
119
112
  Sqreen::RemoteException.record(e)
120
113
  end
121
114
 
122
- def section_context(framework, rule)
123
- obj = section_object('context', framework, rule)
124
- {
125
- 'backtrace' => obj.get_current_backtrace,
126
- }
127
- end
128
-
129
- def section_rule(_framework, rule)
130
- {
131
- 'name' => rule['name'],
132
- 'rulespack_id' => rule['rulespack_id'],
133
- 'test' => rule['test'],
134
- }
135
- end
136
-
137
115
  # object that default to call on framework header
138
116
  class HeaderSection
139
117
  def initialize(framework)
@@ -141,7 +119,7 @@ module Sqreen
141
119
  end
142
120
 
143
121
  def [](value)
144
- if %w(rack_client_ip rails_client_ip ip_headers).include?(value)
122
+ if %w[rack_client_ip rails_client_ip ip_headers].include?(value)
145
123
  return @framework.send(value)
146
124
  end
147
125
  @framework.header(value)
@@ -2,6 +2,7 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  require 'sqreen/callbacks'
5
+ require 'sqreen/context'
5
6
  require 'sqreen/conditionable'
6
7
  require 'sqreen/call_countable'
7
8
  require 'sqreen/rule_attributes'
@@ -18,8 +19,9 @@ module Sqreen
18
19
  include CallCountable
19
20
  # If nothing was asked by the rule we will ask for all sections available
20
21
  # These information will be pruned later when exporting in #to_hash
21
- DEFAULT_PAYLOAD = PayloadCreator::METHODS.keys.freeze
22
+ DEFAULT_PAYLOAD = (PayloadCreator::METHODS.keys - ['local'] + ['context']).freeze
22
23
  attr_reader :test
24
+ attr_reader :payload_tpl
23
25
  attr_reader :block
24
26
  attr_accessor :framework
25
27
 
@@ -32,8 +34,7 @@ module Sqreen
32
34
  @test = rule_hash[Attrs::TEST] == true
33
35
  @data = rule_hash[Attrs::DATA]
34
36
  @rule = rule_hash
35
- payload_tpl = @rule[Attrs::PAYLOAD] || DEFAULT_PAYLOAD
36
- @payload_generator = PayloadCreator.new(payload_tpl)
37
+ @payload_tpl = @rule[Attrs::PAYLOAD] || DEFAULT_PAYLOAD
37
38
  condition_callbacks(@rule[Attrs::CONDITIONS])
38
39
  count_callback_calls(@rule[Attrs::CALL_COUNT_INTERVAL])
39
40
  end
@@ -61,10 +62,19 @@ module Sqreen
61
62
 
62
63
  # Record an attack event into Sqreen system
63
64
  # @param infos [Hash] Additional information about request
64
- def record_event(infos)
65
- payload = @payload_generator.payload(framework, @rule)
66
- payload['infos'] = infos
67
- Attack.record(payload)
65
+ def record_event(infos, at = Time.now.utc)
66
+ return unless framework
67
+ payload = {
68
+ :infos => infos,
69
+ :rulespack_id => rulespack_id,
70
+ :rule_name => rule_name,
71
+ :test => test,
72
+ :time => at,
73
+ }
74
+ if payload_tpl.include?('context')
75
+ payload[:backtrace] = Sqreen::Context.new.bt
76
+ end
77
+ framework.observe(:attacks, payload, payload_tpl)
68
78
  end
69
79
 
70
80
  # Record a metric observation
@@ -73,44 +83,25 @@ module Sqreen
73
83
  # @param observation [Object] data observed
74
84
  # @param at [Time] time when observation was made
75
85
  def record_observation(category, key, observation, at = Time.now.utc)
76
- Sqreen.observations_queue.push [category, key, observation, at]
77
- if Sqreen.observations_queue.size > MAX_OBS_QUEUE_LENGTH / 2
78
- Sqreen.queue.push Sqreen::METRICS_EVENT
79
- end
86
+ return unless framework
87
+ framework.observe(:observations, [category, key, observation, at], [], false)
80
88
  end
81
89
 
82
90
  # Record an exception that just occurred
83
91
  # @param exception [Exception] Exception to send over
84
92
  # @param infos [Hash] Additional contextual information
85
- def record_exception(exception, infos = {})
86
- payload = {}
87
- payload['exception'] = exception
88
- payload['rulespack_id'] = rulespack_id
89
- payload['rule_name'] = rule_name
90
- begin
91
- payload['request_infos'] = framework.request_infos
92
- rescue => e
93
- Sqreen.log.debug("No framework request infos #{e}")
94
- end
95
- begin
96
- payload['request_params'] = framework.request_params
97
- rescue => e
98
- Sqreen.log.debug("No framework request params #{e}")
99
- end
100
- payload['time'] = Time.now.utc
101
- payload['infos'] = infos
102
- payload['backtrace'] = Sqreen::Context.new.bt
103
- begin
104
- payload['client_ip'] = framework.client_ip.to_s
105
- rescue => e
106
- Sqreen.log.debug("No framework client_ip #{e}")
107
- end
108
- begin
109
- payload['headers'] = framework.ip_headers
110
- rescue => e
111
- Sqreen.log.debug("No framework ip_headers #{e}")
112
- end
113
- RemoteException.record(payload)
93
+ def record_exception(exception, infos = {}, at = Time.now.utc)
94
+ return unless framework
95
+ payload = {
96
+ :exception => exception,
97
+ :infos => infos,
98
+ :rulespack_id => rulespack_id,
99
+ :rule_name => rule_name,
100
+ :test => test,
101
+ :time => at,
102
+ :backtrace => exception.backtrace || Sqreen::Context.bt,
103
+ }
104
+ framework.observe(:sqreen_exceptions, payload)
114
105
  end
115
106
  end
116
107
  end
@@ -1,6 +1,7 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
+ require 'sqreen/log'
4
5
  require 'sqreen/rule_attributes'
5
6
  require 'sqreen/rules_callbacks'
6
7
 
@@ -19,6 +19,7 @@ module Sqreen
19
19
  module Rules
20
20
  # Exec js callbacks
21
21
  class ExecJSCB < RuleCB
22
+ attr_accessor :restrict_max_depth
22
23
  def initialize(klass, method, rule_hash)
23
24
  super(klass, method, rule_hash)
24
25
  callbacks = @rule[Attrs::CALLBACKS]
@@ -32,6 +33,7 @@ module Sqreen
32
33
 
33
34
  build_runnable(callbacks)
34
35
  @compiled = ExecJS.compile(@source)
36
+ @restrict_max_depth = 20
35
37
  end
36
38
 
37
39
  def pre?
@@ -97,12 +99,11 @@ module Sqreen
97
99
  insert.reverse.each do |wh, ikey, ival|
98
100
  case wh
99
101
  when Array
100
- wh << ival unless ival.empty?
102
+ wh << ival unless ival.respond_to?(:empty?) && ival.empty?
101
103
  else
102
- wh[ikey] = ival unless ival.empty?
104
+ wh[ikey] = ival unless ival.respond_to?(:empty?) && ival.empty?
103
105
  end
104
106
  end
105
-
106
107
  new_obj
107
108
  end
108
109
 
@@ -184,7 +185,8 @@ module Sqreen
184
185
  arguments[haystack_idx] = ExecJSCB.hash_val_included(
185
186
  arguments[needed_idx],
186
187
  arguments[haystack_idx],
187
- min_length.to_i
188
+ min_length.to_i,
189
+ @restrict_max_depth
188
190
  )
189
191
  end
190
192
 
@@ -1,14 +1,22 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
+ # Sqreen Namespace
4
5
  module Sqreen
5
6
  # Sqreen SDK
6
7
  class << self
7
8
  # Authentication tracking method
8
- def auth_track(is_logged_in, authentication_keys)
9
- end
9
+ def auth_track(is_logged_in, authentication_keys); end
10
+
11
+ def signup_track(authentication_keys); end
10
12
 
11
- def signup_track(authentication_keys)
13
+ def identify(authentication_keys, traits = {})
14
+ return unless Sqreen.framework
15
+ Sqreen.framework.observe(
16
+ :sdk,
17
+ [:identify, Time.now, authentication_keys, traits],
18
+ [], false
19
+ )
12
20
  end
13
21
  end
14
22
  end
@@ -5,6 +5,8 @@ require 'sqreen/log'
5
5
  require 'sqreen/serializer'
6
6
  require 'sqreen/runtime_infos'
7
7
  require 'sqreen/events/remote_exception'
8
+ require 'sqreen/events/attack'
9
+ require 'sqreen/events/request_record'
8
10
  require 'sqreen/exception'
9
11
  require 'sqreen/safe_json'
10
12
 
@@ -159,8 +161,7 @@ module Sqreen
159
161
  connect unless connected?
160
162
  headers['X-Session-Key'] = @session_id if @session_id
161
163
  headers['X-Sqreen-Time'] = Time.now.utc.to_f.to_s
162
- headers['X-Sqreen-Agent'] = "Ruby/#{Sqreen::VERSION}"
163
- headers['User-Agent'] = "Ruby/#{Sqreen::VERSION}"
164
+ headers['User-Agent'] = "sqreen-ruby/#{Sqreen::VERSION}"
164
165
  headers['X-Sqreen-Beta'] = format('pid=%d;tid=%s;nb=%d;t=%f',
165
166
  Process.pid,
166
167
  thread_id,
@@ -263,6 +264,10 @@ module Sqreen
263
264
  'dependencies' => dependencies)
264
265
  end
265
266
 
267
+ def post_request_record(request_record)
268
+ resilient_post('request_record', request_record.to_hash)
269
+ end
270
+
266
271
  # Post an exception to Sqreen for analysis
267
272
  # @param exception [RemoteException] Exception and context to be sent over
268
273
  def post_sqreen_exception(exception)
@@ -305,6 +310,7 @@ module Sqreen
305
310
  case event
306
311
  when Sqreen::RemoteException then 'sqreen_exception'
307
312
  when Sqreen::Attack then 'attack'
313
+ when Sqreen::RequestRecord then 'request_record'
308
314
  end
309
315
  end
310
316
  end
@@ -0,0 +1,32 @@
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
+ module SharedStorage
6
+ @@shared = {}
7
+
8
+ def self::get(key, default = nil)
9
+ h = @@shared[Thread.current]
10
+ return h.fetch(key, default) if h
11
+ default
12
+ end
13
+
14
+ def self::set(key, obj)
15
+ main_key = Thread.current
16
+ @@shared[main_key] = {} unless @@shared.key? main_key
17
+ @@shared[main_key][key] = obj
18
+ end
19
+
20
+ def self.clear
21
+ @@shared.delete(Thread.current)
22
+ end
23
+
24
+ def self.inc(value)
25
+ set(value, get(value, 0) + 1)
26
+ end
27
+
28
+ def self.dec(value)
29
+ set(value, get(value, 0) - 1)
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
  module Sqreen
4
- VERSION = '1.8.5'.freeze
4
+ VERSION = '1.9.0'.freeze
5
5
  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: 1.8.5
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sqreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-18 00:00:00.000000000 Z
11
+ date: 2018-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: execjs
@@ -65,11 +65,13 @@ files:
65
65
  - lib/sqreen/event.rb
66
66
  - lib/sqreen/events/attack.rb
67
67
  - lib/sqreen/events/remote_exception.rb
68
+ - lib/sqreen/events/request_record.rb
68
69
  - lib/sqreen/exception.rb
69
70
  - lib/sqreen/frameworks.rb
70
71
  - lib/sqreen/frameworks/generic.rb
71
72
  - lib/sqreen/frameworks/rails.rb
72
73
  - lib/sqreen/frameworks/rails3.rb
74
+ - lib/sqreen/frameworks/request_recorder.rb
73
75
  - lib/sqreen/frameworks/sinatra.rb
74
76
  - lib/sqreen/frameworks/sqreen_test.rb
75
77
  - lib/sqreen/instrumentation.rb
@@ -116,6 +118,7 @@ files:
116
118
  - lib/sqreen/sdk.rb
117
119
  - lib/sqreen/serializer.rb
118
120
  - lib/sqreen/session.rb
121
+ - lib/sqreen/shared_storage.rb
119
122
  - lib/sqreen/stats.rb
120
123
  - lib/sqreen/version.rb
121
124
  homepage: https://www.sqreen.io/