stratagem 0.1.7 → 0.1.8

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 (51) hide show
  1. data/Manifest +16 -4
  2. data/Rakefile +2 -2
  3. data/lib/bootstrap.rb +1 -0
  4. data/lib/stratagem/auto_mock/aquifer.rb +15 -7
  5. data/lib/stratagem/auto_mock/factory.rb +12 -2
  6. data/lib/stratagem/auto_mock/value_generator.rb +1 -1
  7. data/lib/stratagem/commands.rb +0 -1
  8. data/lib/stratagem/crawler/authentication.rb +116 -54
  9. data/lib/stratagem/crawler/form.rb +12 -0
  10. data/lib/stratagem/crawler/html_utils.rb +19 -7
  11. data/lib/stratagem/crawler/session.rb +156 -68
  12. data/lib/stratagem/crawler/site_model.rb +21 -7
  13. data/lib/stratagem/crawler/trace_utils.rb +3 -1
  14. data/lib/stratagem/extensions/trace_compression.rb +52 -0
  15. data/lib/stratagem/extensions.rb +1 -0
  16. data/lib/stratagem/framework_extensions/models/adapters/active_model/metadata.rb +3 -8
  17. data/lib/stratagem/framework_extensions/models/adapters/active_model/tracing.rb +21 -2
  18. data/lib/stratagem/framework_extensions/models/adapters/common/detect.rb +7 -0
  19. data/lib/stratagem/framework_extensions/models/adapters/common/extensions.rb +0 -0
  20. data/lib/stratagem/framework_extensions/models/adapters/common/metadata.rb +36 -0
  21. data/lib/stratagem/framework_extensions/models/adapters/common/tracing.rb +4 -0
  22. data/lib/stratagem/framework_extensions/models/adapters/{common → util}/authentication_metadata.rb +0 -0
  23. data/lib/stratagem/framework_extensions/models/annotations.rb +23 -1
  24. data/lib/stratagem/framework_extensions/models/metadata.rb +3 -3
  25. data/lib/stratagem/framework_extensions/models/tracing.rb +32 -10
  26. data/lib/stratagem/framework_extensions/models.rb +2 -2
  27. data/lib/stratagem/model/application.rb +8 -4
  28. data/lib/stratagem/model/components/base.rb +3 -0
  29. data/lib/stratagem/model/components/controller.rb +22 -23
  30. data/lib/stratagem/model/components/model.rb +3 -2
  31. data/lib/stratagem/model/components/reference.rb +24 -13
  32. data/lib/stratagem/model/components/route.rb +0 -3
  33. data/lib/stratagem/model/components/view.rb +1 -0
  34. data/lib/stratagem/model_builder.rb +9 -11
  35. data/lib/stratagem/site_crawler.rb +14 -19
  36. data/lib/stratagem.rb +1 -1
  37. data/spec/model/component_spec.rb +43 -0
  38. data/spec/model/components/view_spec.rb +43 -0
  39. data/spec/model/test_spec.rb +10 -0
  40. data/spec/samples/404.html.erb +30 -0
  41. data/spec/samples/_form.html.erb +8 -0
  42. data/spec/samples/index.html.erb +77 -0
  43. data/spec/samples/sample_model.rb +5 -0
  44. data/spec/samples/signup.html.erb +14 -0
  45. data/spec/scan/checks/email_address_spec.rb +24 -0
  46. data/spec/scan/checks/error_pages_spec.rb +22 -0
  47. data/stratagem.gemspec +7 -4
  48. metadata +50 -21
  49. data/lib/stratagem/commands/devel_crawl.rb +0 -27
  50. data/lib/stratagem/scan/checks/ssl/secure_login_page.rb +0 -19
  51. data/lib/stratagem/scan/checks/ssl/secure_login_submit.rb +0 -18
data/Manifest CHANGED
@@ -15,7 +15,6 @@ lib/stratagem/command.rb
15
15
  lib/stratagem/commands.rb
16
16
  lib/stratagem/commands/analyze.rb
17
17
  lib/stratagem/commands/base.rb
18
- lib/stratagem/commands/devel_crawl.rb
19
18
  lib/stratagem/commands/devel_mock.rb
20
19
  lib/stratagem/crawler.rb
21
20
  lib/stratagem/crawler/authentication.rb
@@ -31,6 +30,7 @@ lib/stratagem/extensions/module.rb
31
30
  lib/stratagem/extensions/object.rb
32
31
  lib/stratagem/extensions/red_parse.rb
33
32
  lib/stratagem/extensions/string.rb
33
+ lib/stratagem/extensions/trace_compression.rb
34
34
  lib/stratagem/framework_extensions.rb
35
35
  lib/stratagem/framework_extensions/controllers.rb
36
36
  lib/stratagem/framework_extensions/controllers/action_controller.rb
@@ -44,11 +44,15 @@ lib/stratagem/framework_extensions/models/adapters/authlogic/detect.rb
44
44
  lib/stratagem/framework_extensions/models/adapters/authlogic/extensions.rb
45
45
  lib/stratagem/framework_extensions/models/adapters/authlogic/metadata.rb
46
46
  lib/stratagem/framework_extensions/models/adapters/authlogic/tracing.rb
47
- lib/stratagem/framework_extensions/models/adapters/common/authentication_metadata.rb
47
+ lib/stratagem/framework_extensions/models/adapters/common/detect.rb
48
+ lib/stratagem/framework_extensions/models/adapters/common/extensions.rb
49
+ lib/stratagem/framework_extensions/models/adapters/common/metadata.rb
50
+ lib/stratagem/framework_extensions/models/adapters/common/tracing.rb
48
51
  lib/stratagem/framework_extensions/models/adapters/restful_authentication/detect.rb
49
52
  lib/stratagem/framework_extensions/models/adapters/restful_authentication/extensions.rb
50
53
  lib/stratagem/framework_extensions/models/adapters/restful_authentication/metadata.rb
51
54
  lib/stratagem/framework_extensions/models/adapters/restful_authentication/tracing.rb
55
+ lib/stratagem/framework_extensions/models/adapters/util/authentication_metadata.rb
52
56
  lib/stratagem/framework_extensions/models/annotations.rb
53
57
  lib/stratagem/framework_extensions/models/detect.rb
54
58
  lib/stratagem/framework_extensions/models/metadata.rb
@@ -90,10 +94,18 @@ lib/stratagem/scan/checks/filter_parameter_logging.rb
90
94
  lib/stratagem/scan/checks/mongo_mapper/base.rb
91
95
  lib/stratagem/scan/checks/mongo_mapper/foreign_keys_exposed.rb
92
96
  lib/stratagem/scan/checks/routes.rb
93
- lib/stratagem/scan/checks/ssl/secure_login_page.rb
94
- lib/stratagem/scan/checks/ssl/secure_login_submit.rb
95
97
  lib/stratagem/scan/result.rb
96
98
  lib/stratagem/scanner.rb
97
99
  lib/stratagem/site_crawler.rb
98
100
  lib/stratagem/snapshot.rb
99
101
  lib/tasks/_old_stratagem.rake
102
+ spec/model/component_spec.rb
103
+ spec/model/components/view_spec.rb
104
+ spec/model/test_spec.rb
105
+ spec/samples/404.html.erb
106
+ spec/samples/_form.html.erb
107
+ spec/samples/index.html.erb
108
+ spec/samples/sample_model.rb
109
+ spec/samples/signup.html.erb
110
+ spec/scan/checks/email_address_spec.rb
111
+ spec/scan/checks/error_pages_spec.rb
data/Rakefile CHANGED
@@ -2,14 +2,14 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('stratagem', '0.1.7') do |p|
5
+ Echoe.new('stratagem', '0.1.8') do |p|
6
6
  p.description = "Intuitive security analysis of your Rails applications"
7
7
  p.url = "http://github.com/stratagem/stratagem"
8
8
  p.author = "Charles Grimes"
9
9
  p.email = "cj@stratagemapp.com"
10
10
  p.executable_pattern = ['bin/*']
11
11
  p.ignore_pattern = ["tmp/*", "script/*", "spec/*", "webapp/*"]
12
- p.runtime_dependencies = ["launchy >=0.3.5", "redparse >=0.8.4", "haml >=3.0.0"]
12
+ p.runtime_dependencies = ["launchy >=0.3.5", "redparse >=0.8.4", "haml >=3.0.0", "nokogiri >=1.4.3"]
13
13
  p.development_dependencies = ["launchy >=0.3.5", "redparse >=0.8.4", "sinatra =1.0", "haml >=3.0.0", "webrat >=0.4.3"]
14
14
  # p.requirements ["Install the stratagem-ui gem for the web browser interface."]
15
15
  end
data/lib/bootstrap.rb CHANGED
@@ -24,6 +24,7 @@ Rails::Initializer.class_eval do
24
24
  require 'stratagem'
25
25
 
26
26
  Rails.stratagem_production_env
27
+ ActionController::Base.allow_forgery_protection = true
27
28
  rails_load_application_classes
28
29
  Rails.stratagem_restore_env
29
30
  end
@@ -35,31 +35,39 @@ module Stratagem::AutoMock
35
35
  end
36
36
 
37
37
  def instances_of(model_klass)
38
- repo[model_klass].clone
38
+ objects = (repo[model_klass.name] || []).clone
39
+ puts "found #{objects.size} instances in well"
40
+ objects
39
41
  end
40
42
 
41
43
  def random_instance(model_klass)
42
- objects = repo[model_klass]
44
+ objects = repo[model_klass.name]
43
45
  puts "found #{objects.size} instances in well"
44
46
  instance = objects[rand objects.size]
45
47
  instance
46
48
  end
47
49
 
48
- def fill
50
+ def fill(model_count=nil)
49
51
  Stratagem.logger.phase "mocking_models"
50
52
  application.models.each do |meta_model|
51
- models = mock_model(meta_model.klass) if (meta_model.stratagem?)
53
+ models = mock_model(meta_model.klass, model_count) if (meta_model.stratagem?)
52
54
  end
53
55
  puts "aquifer full"
56
+ print
57
+ self
58
+ end
59
+
60
+ def print
54
61
  application.models.each do |meta_model|
55
62
  puts "#{meta_model.klass.name}"
56
- (repo[meta_model.klass] || []).each do |instance|
57
- puts "\t#{instance.id}"
63
+ (repo[meta_model.klass.name] || []).each do |instance|
64
+ puts "\t#{instance.class} - #{instance}"
58
65
  end
59
66
  end
60
67
  end
61
68
 
62
- def mock_model(klass, count=2)
69
+ def mock_model(klass, count=nil)
70
+ count ||= 6
63
71
  count.times do |i|
64
72
  instance,valid = mock(klass)
65
73
  end
@@ -11,6 +11,13 @@ module Stratagem::AutoMock
11
11
  module Factory
12
12
  include ValueGenerator
13
13
 
14
+ def mock_attributes(model)
15
+ object = model.new
16
+ populate_attributes(object, [])
17
+ correct_invalid_columns(object, [])
18
+ object.stratagem.mock_attributes
19
+ end
20
+
14
21
  def mock(model,mock_chain=[], belongs_to=nil)
15
22
  return if mock_chain.select {|m| m == model }.size > 1
16
23
  mock_chain << model
@@ -47,13 +54,13 @@ module Stratagem::AutoMock
47
54
  protected
48
55
 
49
56
  def add_mocked(instance)
50
- (mocked[instance.class] ||= []) << instance
57
+ (mocked(instance.class)) << instance
51
58
  end
52
59
 
53
60
  def mocked(model=nil)
54
61
  @mocked ||= {}
55
62
  if (model)
56
- @mocked[model] ||= []
63
+ @mocked[model.name] ||= []
57
64
  else
58
65
  @mocked
59
66
  end
@@ -174,6 +181,9 @@ module Stratagem::AutoMock
174
181
  end
175
182
  puts $!.backtrace unless valid
176
183
  end
184
+
185
+ puts "\t#{object.stratagem.mock_attributes.inspect}" if (valid)
186
+
177
187
  valid
178
188
  end
179
189
 
@@ -51,7 +51,7 @@ module Stratagem::AutoMock
51
51
  else
52
52
  raise Stratagem::AutoMock::UnsupportedColumnTypeError.new("Attribute #{attribute_name} of type #{attribute_type} is not supported")
53
53
  end
54
- value = nil if (rand(20) == 1) && !NUMERIC_TYPES.include?(attribute_type)
54
+ value = nil if (rand(20) == 1) && (!NUMERIC_TYPES.include?(attribute_type)) && (attribute_name.to_s !~ /password/)
55
55
  value
56
56
  end
57
57
 
@@ -3,5 +3,4 @@ end
3
3
 
4
4
  require 'stratagem/commands/base'
5
5
  require 'stratagem/commands/analyze'
6
- require 'stratagem/commands/devel_crawl'
7
6
  require 'stratagem/commands/devel_mock'
@@ -7,73 +7,104 @@ module Stratagem::Crawler
7
7
  module Authentication
8
8
  include Stratagem::Crawler::TraceUtils
9
9
 
10
+ def users
11
+ page = find_login_form
12
+ users = []
13
+ if (page)
14
+ form = page.login_form
15
+ attr_names = form.inputs.map {|input| input.guess_attribute.to_sym }
16
+ model = guess_login_model(attr_names)
17
+ if (model)
18
+ users = aquifer.instances_of(model.klass)
19
+ else
20
+ log "ERROR: Unable to determine user model"
21
+ end
22
+ else
23
+ log "ERROR: Could not find login form"
24
+ end
25
+ users
26
+ end
27
+
28
+ def reset_authentication
29
+ @authentication_data = nil
30
+ end
31
+
10
32
  def authentication
11
- @authentication_data ||= AuthenticationData.new()
33
+ unless @authentication_data
34
+ @authentication_data = AuthenticationData.new()
35
+ site_model.authentication = @authentication_data
36
+ end
37
+ @authentication_data
12
38
  end
13
39
 
14
- def authenticate
15
- page, form = find_login_form
16
- if (page && form)
17
- authentication.login_page = page
18
- login(form)
19
- form.submit {|action,params|
20
- post(action, params)
21
- response
22
- }
23
-
24
- route = application_model.routes.recognize(response.request.path, :post)
25
- page = site_model.add(route, response) {|response| }
26
- authentication.response_page = page
27
-
28
- begin
40
+ def authenticate(user, recursion_count=0)
41
+ reset_authentication
42
+
43
+ login(user)
44
+ route = application_model.routes.recognize(response.request.path, :post)
45
+
46
+ redirected_to = nil
47
+ page = site_model.add(route, response) {|redirect_url| redirected_to = redirect_url }
48
+ authentication.response_page = page
49
+
50
+ begin
51
+ if (response.request.url == (redirected_to || '')) || (![200,302].include?(response.code.to_i))
52
+ authentication.success = false
53
+ else
29
54
  authentication.success = authentication.response_page.login_form.nil?
30
- rescue
31
- puts $!.message
32
- puts $!.backtrace
33
55
  end
34
- puts "authenticated? #{authentication.success}"
35
- else
36
- puts "Authentication Error: Unable to locate sign in form"
56
+ rescue
57
+ puts $!.message
58
+ puts $!.backtrace
37
59
  end
38
60
 
39
- if (response)
61
+ puts "authenticated? #{authentication.success}"
62
+ if (response && authentication.success)
40
63
  authentication.ssl = response.request.ssl?
41
- authentication.success
64
+ yield
65
+ logout
42
66
  else
43
67
  false
44
68
  end
45
69
  end
46
70
 
47
71
  def find_login_form
48
- site_model.pages.sort {|a,b| b.inbound_edges(:redirect).size <=> a.inbound_edges(:redirect).size }.each do |page|
49
- puts "Testing page #{page.url} for sign in form"
50
- page.reload {|url| get url; response }
51
- form = page.login_form
52
- return [page, form] if (form)
72
+ puts "finding login form"
73
+ if authentication.login_page.nil?
74
+ puts "locating login page"
75
+ puts "testing #{site_models.first.pages.size} pages"
76
+ site_models.first.pages.sort {|a,b| b.inbound_edges(:redirect).size <=> a.inbound_edges(:redirect).size }.each do |page|
77
+ puts "Testing page #{page.url} for sign in form"
78
+ # page.reload {|url| get url; response }
79
+ # form = page.login_form
80
+ if (page.login_form)
81
+ puts "FOUND! - #{page.login_form}"
82
+ authentication.login_page = page
83
+ return page
84
+ end
85
+ end
86
+ else
87
+ return authentication.login_page
53
88
  end
54
- []
89
+ nil
55
90
  end
56
91
 
57
- def login(form)
58
- attr_names = form.inputs.map {|input| input.guess_attribute.to_sym }
59
- model = guess_login_model(attr_names)
60
-
61
- if model
62
- record = Stratagem::AutoMock::Aquifer.instance.random_instance(model.klass)
92
+ def logout
93
+ get "/signout"
94
+ get "/logout"
95
+ delete "/user_sessions/1"
96
+ end
63
97
 
64
- if (record)
65
- puts "populating login form"
66
- populate_login_form(model, form, record)
67
- else
68
- log "ERROR: Unable to find suitable model to populate authentication form with."
69
- end
70
- else
71
- raise "Unable to infer model from inputs"
72
- end
98
+ def login(user)
99
+ populate_login_form(user).submit {|action,params|
100
+ post(action, params)
101
+ puts response.body
102
+ }
73
103
  end
74
104
 
75
105
  def guess_login_model(attr_names)
76
106
  selections = application_model.models.select {|model|
107
+ puts "#{model.klass.name} - #{model.model_attributes.keys.inspect}"
77
108
  intersect = (model.model_attributes.keys & attr_names)
78
109
  intersect.size > 0
79
110
  }.sort {|a,b|
@@ -81,28 +112,59 @@ module Stratagem::Crawler
81
112
  b_intersect = (b.model_attributes.keys & attr_names)
82
113
  b_intersect.size <=> a_intersect.size
83
114
  }
84
- puts "selecting model #{selections.first.klass.name} for authentication"
115
+
116
+ explicit_model = application_model.models.find {|model| model.klass.name == 'User' }
117
+ selections.unshift explicit_model if explicit_model
118
+
119
+ puts "selecting model #{selections.first.klass.name} for authentication" if (selections.size > 0)
85
120
  selections.first
86
121
  end
87
122
 
88
123
 
89
- def populate_login_form(model, form, record)
124
+ def populate_login_form(user)
125
+ # set up the form
126
+ page = find_login_form
127
+ page.reload {|url| get url; response }
128
+ form = page.login_form
129
+
130
+ # map the input values
90
131
  form.inputs.each do |input|
132
+ # try the expected
91
133
  attribute_name = input.guess_attribute.to_sym
92
- attribute_value = record.stratagem.read_mock_attribute(attribute_name)
134
+ attribute_value = user.stratagem.read_mock_attribute(attribute_name) || input.value
93
135
 
94
- puts "authentication field: #{attribute_name} -> #{attribute_value}"
136
+ # try again
137
+ if (attribute_value.nil? || attribute_value == '')
138
+ attribute_name = input.guess_alternate_attribute.to_sym
139
+ attribute_value = user.stratagem.read_mock_attribute(attribute_name) || input.value
140
+ end
141
+
142
+ # if it still failed is it a confirmation value? if so, try the original
143
+ if (attribute_value.nil? || attribute_value == '')
144
+ if (attribute_name.to_s =~ /confirm/)
145
+ possible_match = attribute_name.to_s.split('_').select {|a| a !~ /confirm/ }.join('_')
146
+ if user.stratagem.mock_attributes.keys.include?(possible_match)
147
+ attribute_value = user.stratagem.read_mock_attribute(possible_match) || input.value
148
+ end
149
+ end
150
+ end
151
+
95
152
 
96
153
  if (input.kind_of? Stratagem::Crawler::Toggle)
97
154
  input.check
98
- elsif (record.stratagem.mock_attributes.keys.include?(attribute_name))
99
- input.value = record.stratagem.read_mock_attribute(attribute_name) unless input.hidden?
155
+ elsif (user.stratagem.mock_attributes.keys.include?(attribute_name))
156
+ input.value = user.stratagem.read_mock_attribute(attribute_name) unless input.hidden?
157
+ elsif (attribute_name.to_s == 'authenticity_token')
158
+ puts input.value
100
159
  else
101
- puts record.stratagem.mock_attributes.inspect
102
- puts "ERROR: Cannot find attribute #{attribute_name} in model #{record.class.name}"
160
+ puts user.stratagem.mock_attributes.inspect
161
+ puts "ERROR: Cannot find attribute #{attribute_name} in model #{user.class.name}"
103
162
  end
163
+
164
+ puts "3 authentication field: #{input.name} -> #{input.value}"
165
+
104
166
  end
105
- form.generate_parameters
167
+ form
106
168
  end
107
169
 
108
170
  end
@@ -58,6 +58,14 @@ module Stratagem::Crawler
58
58
  end
59
59
  end
60
60
 
61
+ def guess_alternate_attribute
62
+ if (name =~ /(.*)\[(.*)\]/)
63
+ $1.to_sym
64
+ else
65
+ name.to_sym
66
+ end
67
+ end
68
+
61
69
  def guess_model
62
70
  return $1.camelize if (name =~ /(.*)?\[/)
63
71
  return nil
@@ -96,6 +104,10 @@ module Stratagem::Crawler
96
104
 
97
105
  def <<(value)
98
106
  @options << value
107
+ self.value = value if self.value.nil?
99
108
  end
100
109
  end
110
+
111
+ class Radio < Select
112
+ end
101
113
  end
@@ -6,6 +6,7 @@ module Stratagem::Crawler
6
6
  INPUT_TEXT = ['text', 'password', 'hidden']
7
7
  INPUT_BUTTON = ['button', 'submit', 'reset', 'image', 'src']
8
8
  INPUT_TOGGLE = ['checkbox']
9
+ INPUT_RADIO = ['radio']
9
10
 
10
11
  def find_login_form(document)
11
12
  possibilities = parse_forms(document).select {|form|
@@ -65,16 +66,27 @@ module Stratagem::Crawler
65
66
  input = Button.new()
66
67
  elsif (INPUT_TOGGLE.include?(type))
67
68
  input = Toggle.new()
69
+ elsif (INPUT_RADIO.include?(type))
70
+ name = form_load_attribute(input_tag, 'name', false)
71
+ input = form.inputs.find {|i| i.name == name } || Radio.new()
68
72
  else
69
- raise FormParseError.new("Unsupported <input> type: '#{type}'", :html => input_tag.to_html)
73
+ #raise FormParseError.new("Unsupported <input> type: '#{type}'", :html => input_tag.to_html)
74
+ puts "ERROR: Unsupported input type: #{type}"
70
75
  end
71
76
 
72
- input.id = form_load_attribute(input_tag, 'id', false)
73
- input.type = form_load_attribute(input_tag, 'type', false) || 'text'
74
- input.name = form_load_attribute(input_tag, 'name', false)
75
- input.value = form_load_attribute(input_tag, 'value', false)
77
+ if (input)
78
+ input.id = form_load_attribute(input_tag, 'id', false)
79
+ input.type = form_load_attribute(input_tag, 'type', false) || 'text'
80
+ input.name = form_load_attribute(input_tag, 'name', false)
81
+ if (input.class.ancestors.include?(Select))
82
+ input << form_load_attribute(input_tag, 'value', false)
83
+ else
84
+ input.value = form_load_attribute(input_tag, 'value', false)
85
+ end
76
86
 
77
- form << input
87
+ form << input
88
+ end
89
+ form
78
90
  end
79
91
 
80
92
  def form_load_attribute(node, attribute_name, raise_error = true)
@@ -82,7 +94,7 @@ module Stratagem::Crawler
82
94
  attr = node.attributes[attribute_name]
83
95
  value = nil
84
96
  if (attr)
85
- value = attr.value.strip.downcase
97
+ value = attr.value.strip
86
98
  elsif (raise_error)
87
99
  raise FormParseError.new("#{attribute_name} attribute not found in tag - #{node.to_html}")
88
100
  end