stratagem 0.1.7 → 0.1.8

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