tainted_love 0.1.5 → 0.4.0

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +2 -0
  4. data/bin/setup +3 -3
  5. data/bin/test +6 -2
  6. data/dev.yml +1 -1
  7. data/lib/tainted_love.rb +2 -2
  8. data/lib/tainted_love/replacer/base.rb +5 -1
  9. data/lib/tainted_love/replacer/replace_action_controller.rb +0 -4
  10. data/lib/tainted_love/replacer/replace_active_record.rb +21 -1
  11. data/lib/tainted_love/replacer/replace_graphql.rb +27 -0
  12. data/lib/tainted_love/replacer/replace_kernel.rb +1 -1
  13. data/lib/tainted_love/replacer/replace_object.rb +8 -2
  14. data/lib/tainted_love/replacer/replace_rack_builder.rb +51 -0
  15. data/lib/tainted_love/replacer/replace_rack_file.rb +25 -0
  16. data/lib/tainted_love/replacer/replace_rack_query_parser.rb +50 -0
  17. data/lib/tainted_love/replacer/replace_rails_user_input.rb +12 -27
  18. data/lib/tainted_love/replacer/replace_string.rb +69 -0
  19. data/lib/tainted_love/replacer/replace_tag_builder.rb +16 -0
  20. data/lib/tainted_love/reporter/base.rb +4 -1
  21. data/lib/tainted_love/reporter/stdout_reporter.rb +1 -0
  22. data/lib/tainted_love/utils.rb +4 -19
  23. data/lib/tainted_love/utils/proxy.rb +95 -0
  24. data/lib/tainted_love/validator/action_dispatch_diagnostics.rb +20 -0
  25. data/lib/tainted_love/validator/active_record_find.rb +15 -0
  26. data/lib/tainted_love/validator/erb_eval.rb +1 -3
  27. data/lib/tainted_love/validator/haml_eval.rb +25 -0
  28. data/lib/tainted_love/validator/i18n_load.rb +17 -0
  29. data/lib/tainted_love/validator/ignore.rb +21 -0
  30. data/lib/tainted_love/version.rb +1 -1
  31. data/service.yml +6 -0
  32. data/{example → tests/rails}/.gitignore +0 -0
  33. data/{example → tests/rails}/.ruby-version +0 -0
  34. data/{example → tests/rails}/Gemfile +5 -4
  35. data/{example → tests/rails}/Gemfile.lock +29 -32
  36. data/{example → tests/rails}/README.md +0 -0
  37. data/{example → tests/rails}/Rakefile +0 -0
  38. data/{example → tests/rails}/app/assets/config/manifest.js +0 -0
  39. data/{example → tests/rails}/app/assets/images/.keep +0 -0
  40. data/{example → tests/rails}/app/assets/javascripts/application.js +0 -0
  41. data/{example → tests/rails}/app/assets/javascripts/cable.js +0 -0
  42. data/{example → tests/rails}/app/assets/javascripts/channels/.keep +0 -0
  43. data/{example → tests/rails}/app/assets/javascripts/products.coffee +0 -0
  44. data/{example → tests/rails}/app/assets/stylesheets/application.css +0 -0
  45. data/{example → tests/rails}/app/assets/stylesheets/products.scss +0 -0
  46. data/{example → tests/rails}/app/assets/stylesheets/scaffolds.scss +0 -0
  47. data/{example → tests/rails}/app/channels/application_cable/channel.rb +0 -0
  48. data/{example → tests/rails}/app/channels/application_cable/connection.rb +0 -0
  49. data/{example → tests/rails}/app/controllers/application_controller.rb +0 -0
  50. data/{example → tests/rails}/app/controllers/concerns/.keep +0 -0
  51. data/tests/rails/app/controllers/graphql_controller.rb +43 -0
  52. data/{example → tests/rails}/app/controllers/products_controller.rb +0 -0
  53. data/tests/rails/app/controllers/test_cases_controller.rb +43 -0
  54. data/tests/rails/app/graphql/example_schema.rb +4 -0
  55. data/{example/app/models/concerns → tests/rails/app/graphql/mutations}/.keep +0 -0
  56. data/{example/lib/assets → tests/rails/app/graphql/types}/.keep +0 -0
  57. data/tests/rails/app/graphql/types/base_enum.rb +4 -0
  58. data/tests/rails/app/graphql/types/base_input_object.rb +4 -0
  59. data/tests/rails/app/graphql/types/base_interface.rb +5 -0
  60. data/tests/rails/app/graphql/types/base_object.rb +4 -0
  61. data/tests/rails/app/graphql/types/base_scalar.rb +4 -0
  62. data/tests/rails/app/graphql/types/base_union.rb +4 -0
  63. data/tests/rails/app/graphql/types/mutation_type.rb +10 -0
  64. data/tests/rails/app/graphql/types/product_type.rb +10 -0
  65. data/tests/rails/app/graphql/types/query_type.rb +46 -0
  66. data/tests/rails/app/graphql/types/taint_test_case_input.rb +8 -0
  67. data/{example → tests/rails}/app/helpers/application_helper.rb +0 -0
  68. data/{example → tests/rails}/app/helpers/products_helper.rb +0 -0
  69. data/{example → tests/rails}/app/helpers/test_cases_helper.rb +0 -0
  70. data/{example → tests/rails}/app/jobs/application_job.rb +0 -0
  71. data/{example → tests/rails}/app/mailers/application_mailer.rb +0 -0
  72. data/{example → tests/rails}/app/models/application_record.rb +0 -0
  73. data/{example/lib/tasks → tests/rails/app/models/concerns}/.keep +0 -0
  74. data/{example → tests/rails}/app/models/product.rb +0 -0
  75. data/{example → tests/rails}/app/views/layouts/application.html.erb +0 -0
  76. data/{example → tests/rails}/app/views/layouts/mailer.html.erb +0 -0
  77. data/{example → tests/rails}/app/views/layouts/mailer.text.erb +0 -0
  78. data/{example → tests/rails}/app/views/products/_form.html.erb +0 -0
  79. data/{example → tests/rails}/app/views/products/_product.json.jbuilder +0 -0
  80. data/{example → tests/rails}/app/views/products/edit.html.erb +0 -0
  81. data/{example → tests/rails}/app/views/products/index.html.erb +0 -0
  82. data/{example → tests/rails}/app/views/products/index.json.jbuilder +0 -0
  83. data/{example → tests/rails}/app/views/products/new.html.erb +0 -0
  84. data/{example → tests/rails}/app/views/products/show.html.erb +0 -0
  85. data/{example → tests/rails}/app/views/products/show.json.jbuilder +0 -0
  86. data/{example → tests/rails}/app/views/test_cases/xss.html.erb +0 -0
  87. data/{example → tests/rails}/bin/bundle +0 -0
  88. data/{example → tests/rails}/bin/rails +0 -0
  89. data/{example → tests/rails}/bin/rake +0 -0
  90. data/{example → tests/rails}/bin/setup +0 -0
  91. data/{example → tests/rails}/bin/spring +0 -0
  92. data/{example → tests/rails}/bin/update +0 -0
  93. data/{example → tests/rails}/bin/yarn +0 -0
  94. data/{example → tests/rails}/config.ru +0 -0
  95. data/{example → tests/rails}/config/application.rb +0 -0
  96. data/{example → tests/rails}/config/boot.rb +0 -0
  97. data/{example → tests/rails}/config/cable.yml +0 -0
  98. data/{example → tests/rails}/config/credentials.yml.enc +0 -0
  99. data/{example → tests/rails}/config/database.yml +0 -0
  100. data/{example → tests/rails}/config/environment.rb +0 -0
  101. data/{example → tests/rails}/config/environments/development.rb +0 -0
  102. data/{example → tests/rails}/config/environments/production.rb +0 -0
  103. data/{example → tests/rails}/config/environments/test.rb +0 -0
  104. data/{example → tests/rails}/config/initializers/application_controller_renderer.rb +0 -0
  105. data/{example → tests/rails}/config/initializers/assets.rb +0 -0
  106. data/{example → tests/rails}/config/initializers/backtrace_silencers.rb +0 -0
  107. data/{example → tests/rails}/config/initializers/content_security_policy.rb +0 -0
  108. data/{example → tests/rails}/config/initializers/cookies_serializer.rb +0 -0
  109. data/{example → tests/rails}/config/initializers/filter_parameter_logging.rb +0 -0
  110. data/{example → tests/rails}/config/initializers/inflections.rb +0 -0
  111. data/{example → tests/rails}/config/initializers/mime_types.rb +0 -0
  112. data/{example → tests/rails}/config/initializers/tainted_love.rb +0 -0
  113. data/{example → tests/rails}/config/initializers/wrap_parameters.rb +0 -0
  114. data/{example → tests/rails}/config/locales/en.yml +0 -0
  115. data/{example → tests/rails}/config/puma.rb +0 -0
  116. data/{example → tests/rails}/config/routes.rb +6 -0
  117. data/{example → tests/rails}/config/spring.rb +0 -0
  118. data/{example → tests/rails}/config/storage.yml +0 -0
  119. data/{example → tests/rails}/db/migrate/20190311220346_create_products.rb +0 -0
  120. data/{example → tests/rails}/db/schema.rb +0 -0
  121. data/{example → tests/rails}/db/seeds.rb +0 -0
  122. data/{example/log → tests/rails/lib/assets}/.keep +0 -0
  123. data/{example/storage → tests/rails/lib/tasks}/.keep +0 -0
  124. data/{example/test/controllers → tests/rails/log}/.keep +0 -0
  125. data/{example → tests/rails}/package.json +0 -0
  126. data/{example → tests/rails}/public/404.html +0 -0
  127. data/{example → tests/rails}/public/422.html +0 -0
  128. data/{example → tests/rails}/public/500.html +0 -0
  129. data/{example → tests/rails}/public/apple-touch-icon-precomposed.png +0 -0
  130. data/{example → tests/rails}/public/apple-touch-icon.png +0 -0
  131. data/{example → tests/rails}/public/favicon.ico +0 -0
  132. data/{example → tests/rails}/public/robots.txt +0 -0
  133. data/{example/test/fixtures → tests/rails/storage}/.keep +0 -0
  134. data/tests/rails/test.sh +1 -0
  135. data/{example → tests/rails}/test/application_system_test_case.rb +0 -0
  136. data/{example/test/fixtures/files → tests/rails/test/controllers}/.keep +0 -0
  137. data/tests/rails/test/controllers/graphql_controller_test.rb +28 -0
  138. data/{example → tests/rails}/test/controllers/products_controller_test.rb +0 -0
  139. data/tests/rails/test/controllers/test_cases_controller_test.rb +54 -0
  140. data/{example/test/helpers → tests/rails/test/fixtures}/.keep +0 -0
  141. data/{example/test/integration → tests/rails/test/fixtures/files}/.keep +0 -0
  142. data/{example → tests/rails}/test/fixtures/products.yml +0 -0
  143. data/{example/test/mailers → tests/rails/test/helpers}/.keep +0 -0
  144. data/{example/test/models → tests/rails/test/integration}/.keep +0 -0
  145. data/{example/test/system → tests/rails/test/mailers}/.keep +0 -0
  146. data/{example/tmp → tests/rails/test/models}/.keep +0 -0
  147. data/{example → tests/rails}/test/models/product_test.rb +0 -0
  148. data/{example → tests/rails}/test/replacers/replace_active_record_test.rb +28 -0
  149. data/tests/rails/test/replacers/replace_rails_user_input_test.rb +13 -0
  150. data/{example → tests/rails}/test/replacers/replace_sprokets_test.rb +0 -0
  151. data/{example/vendor → tests/rails/test/system}/.keep +0 -0
  152. data/{example → tests/rails}/test/system/products_test.rb +0 -0
  153. data/{example → tests/rails}/test/test_helper.rb +0 -0
  154. data/tests/rails/tmp/.keep +0 -0
  155. data/tests/rails/vendor/.keep +0 -0
  156. data/tests/sinatra/Gemfile +3 -0
  157. data/tests/sinatra/Gemfile.lock +29 -0
  158. data/tests/sinatra/app.rb +26 -0
  159. data/tests/sinatra/test.sh +1 -0
  160. data/tests/sinatra/views/xss.erb +1 -0
  161. data/tools/web/Gemfile +1 -1
  162. data/tools/web/application.rb +17 -2
  163. data/tools/web/public/application.css +38 -2
  164. data/tools/web/views/index.erb +5 -11
  165. data/tools/web/views/input.erb +4 -0
  166. data/tools/web/views/line.erb +2 -2
  167. metadata +146 -111
  168. data/example/app/controllers/test_cases_controller.rb +0 -20
  169. data/example/test/controllers/test_cases_controller_test.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1fb2f3caa7c9d1c2ad65cd39008d98e7b3d4b90f2fefaa6271651b33a279ba3
4
- data.tar.gz: 56a20ce9ecde810993e9d62475097c8187f14143ea9e6f7ab8d7e79ecd4b1f90
3
+ metadata.gz: ea2bb8edc59c047e25dfca2ee1e8375a008ccf93c4e98e4746854c404cc7d11d
4
+ data.tar.gz: c23dd71cd581ab0a27e3df8d9e137f91cf203cefabb304b7761bb86f7a44f1b3
5
5
  SHA512:
6
- metadata.gz: 31b432b1a5745084e7c3bc4efcae14a1d20bfba8ad8ac47ea5412d1895cc6c648717bc00ca3463e2192f53108dadba9feb74701909e09778c33be621046ed938
7
- data.tar.gz: f6903f74b1d35abda62cb44879bc45b745c7f3bdae96a02eed6bcda7cbf8d0e39a4402328238f7a984c62393ca6a9bbc121cf661f32c488b5b61659d1947e7e6
6
+ metadata.gz: b8ae7c54209f62bc4ecf0257b470bdc2733b93b69f35635b40555cd6f42eb479755a69e0a971016a1d164071801db4af7e7ae5f933b44b211140ab3c4ca5f688
7
+ data.tar.gz: c5e76a2da036357ba6a73c3a01c01d717362077adfd29bbfe0b5b2ed4ce0b95fa6fe13e3c05bad779669c8eaf024ea32184e8b5b79aedbdcc435565e94eee6c0
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tainted_love (0.1.5)
4
+ tainted_love (0.4.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -76,6 +76,7 @@ Model.where(tainted_input)
76
76
  Model.select(tainted_input)
77
77
  Model.find_by_sql(tainted_input)
78
78
  Model.count_by_sql(tainted_input)
79
+ Model.order(tainted_input)
79
80
  ```
80
81
 
81
82
  ## Development
@@ -83,6 +84,7 @@ Model.count_by_sql(tainted_input)
83
84
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
84
85
 
85
86
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
87
+ To install this gem onto your local machine, run `bundle exec rake install`.
86
88
 
87
89
  ## Contributing
88
90
 
data/bin/setup CHANGED
@@ -5,6 +5,6 @@ set -vx
5
5
 
6
6
  bundle install
7
7
 
8
- cd example
9
-
10
- bundle install
8
+ for path in tests/*/Gemfile; do
9
+ bundle install --gemfile=$path
10
+ done
data/bin/test CHANGED
@@ -3,5 +3,9 @@
3
3
  set -e
4
4
 
5
5
  bundle exec rake
6
- cd example
7
- rails test
6
+
7
+ for path in tests/*; do
8
+ cd $path
9
+ ./test.sh
10
+ cd ../..
11
+ done
data/dev.yml CHANGED
@@ -8,7 +8,7 @@ up:
8
8
  - ruby: 2.5.3
9
9
  - bundler
10
10
  - bundler:
11
- gemfile: example/Gemfile
11
+ gemfile: tests/rails/Gemfile
12
12
 
13
13
  commands:
14
14
  console:
@@ -11,7 +11,7 @@ module TaintedLove
11
11
  # Enables TaintedLove. Use a block to configure the TaintedLove::Configuration
12
12
  #
13
13
  # @yield [TaintedLove::Configuration]
14
- # @returns [TaintedLove::Configuration]
14
+ # @return [TaintedLove::Configuration]
15
15
  def enable!
16
16
  configuration = TaintedLove::Configuration.new
17
17
 
@@ -48,7 +48,7 @@ module TaintedLove
48
48
  warning.message = message
49
49
 
50
50
  should_remove = @configuration.validators.any? do |validator|
51
- validator.new.remove?(warning)
51
+ validator.new.remove?(warning) == true
52
52
  end
53
53
 
54
54
  @configuration.reporter.add_warning(warning) unless should_remove
@@ -15,10 +15,14 @@ module TaintedLove
15
15
  #
16
16
  # @return [Array<Class>]
17
17
  def self.replacers
18
- TaintedLove::Replacer.constants.map do |const|
18
+ replacers = TaintedLove::Replacer.constants.map do |const|
19
19
  cls = TaintedLove::Replacer.const_get(const)
20
20
  cls if cls.method_defined?(:replace!)
21
21
  end.compact
22
+
23
+ replacers -= [TaintedLove::Replacer::ReplaceObject]
24
+
25
+ [TaintedLove::Replacer::ReplaceObject] + replacers
22
26
  end
23
27
  end
24
28
  end
@@ -51,10 +51,6 @@ module TaintedLove
51
51
  end
52
52
  end
53
53
  end
54
-
55
- TaintedLove.proxy_method('ActionController::Base', :redirect_to) do |_, *args|
56
- TaintedLove.report(:ReplaceActionController, args.first, [:open_redirect]) if args.first.tainted?
57
- end
58
54
  end
59
55
  end
60
56
  end
@@ -19,11 +19,20 @@ module TaintedLove
19
19
  end
20
20
  end
21
21
 
22
+ TaintedLove.proxy_method('ActiveRecord::QueryMethods', :order) do |_, *args|
23
+ unless args.empty?
24
+ f = args.first
25
+ if f.is_a?(String) && f.tainted?
26
+ TaintedLove.report(:ReplaceActiveRecord, f, [:sqli], 'Model.order using tainted string')
27
+ end
28
+ end
29
+ end
30
+
22
31
  TaintedLove.proxy_method('ActiveRecord::QueryMethods', :select) do |_, *args|
23
32
  unless args.empty?
24
33
  f = args.first
25
34
  if f.is_a?(String) && f.tainted?
26
- TaintedLove.report(:ReplaceActiveRecord, f, [:sqli], 'Model#select using tainted string')
35
+ TaintedLove.report(:ReplaceActiveRecord, f, [:sqli], 'Model.select using tainted string')
27
36
  end
28
37
  end
29
38
  end
@@ -38,6 +47,17 @@ module TaintedLove
38
47
  super(*args)
39
48
  end
40
49
  end
50
+
51
+ # Removes taint on string have been sanitized, unless the first argument is tainted
52
+ def sanitize_sql_array(ary)
53
+ return_value = super(ary)
54
+
55
+ if ary.first.tainted?
56
+ return_value.taint
57
+ else
58
+ return_value.untaint
59
+ end
60
+ end
41
61
  end
42
62
 
43
63
  ActiveRecord::Base.extend(mod)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TaintedLove
4
+ module Replacer
5
+ class ReplaceGraphQL < Base
6
+ def should_replace?
7
+ Gem.loaded_specs.has_key?('graphql') # fixme: very bundler specific
8
+ end
9
+
10
+ def replace!
11
+ require 'graphql'
12
+
13
+ GraphQL::Query::Arguments::ArgumentValue.class_eval do
14
+ def value
15
+ return @value if default_used?
16
+
17
+ @tainted_value ||= @value.dup.taint
18
+
19
+ TaintedLove.tag(@tainted_value, { source: "GraphQL argument #{key.inspect}", value: @tainted_value })
20
+
21
+ @tainted_value
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -10,7 +10,7 @@ module TaintedLove
10
10
  :ReplaceKernel,
11
11
  args.first,
12
12
  [:rce],
13
- 'Command execution using tainted input'
13
+ "Kernel##{method} execution using tainted input"
14
14
  ) if args.first&.tainted?
15
15
  end
16
16
  end
@@ -3,6 +3,8 @@
3
3
  module TaintedLove
4
4
  module Replacer
5
5
  class ReplaceObject < Base
6
+ TAGS = {}
7
+
6
8
  def replace!
7
9
  mod = Module.new do
8
10
  def send(*args, &block)
@@ -18,8 +20,12 @@ module TaintedLove
18
20
  super(*args, &block)
19
21
  end
20
22
 
21
- def tainted_love_tracking
22
- @tainted_love_tracking ||= []
23
+ def tainted_love_tags
24
+ TAGS[object_id] ||= []
25
+ end
26
+
27
+ def tainted_love_tags=(tags)
28
+ TAGS[object_id] = tags
23
29
  end
24
30
  end
25
31
 
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TaintedLove
4
+ module Replacer
5
+ class ReplaceRackBuilder < Base
6
+ def should_replace?
7
+ Object.const_defined?('Rack::Builder')
8
+ end
9
+
10
+ def replace!
11
+ # Register a middleware that will be the first the receive call and prepare the
12
+ # env to be correctly tainted. This should be enough for all Rack-based apps
13
+ TaintedLove.proxy_method('Rack::Builder', :run) do |_, app, builder|
14
+ builder.use(TaintedLove::Replacer::ReplaceRackBuilder::TaintedLoveRackMiddleware)
15
+ end
16
+ end
17
+
18
+ class TaintedLoveRackMiddleware
19
+ def initialize(app)
20
+ @app = app
21
+ end
22
+
23
+ def call(env)
24
+ @app.call(taint_env(env))
25
+ end
26
+
27
+ def taint_env(env)
28
+ uppercase_keys = env.to_h.keys.select { |k| k[/[A-Z]/] }
29
+
30
+ values = {}
31
+ uppercase_keys.each do |key|
32
+ new_key = key.dup.taint
33
+ new_value = env[key].dup.taint
34
+
35
+ TaintedLove.tag(new_key, source: "Key #{key.inspect} Rack env", value: new_key)
36
+ TaintedLove.tag(new_value, source: "Rack env[#{key.inspect}]", value: new_value)
37
+
38
+ values[new_key] = new_value
39
+
40
+ env.delete(key)
41
+ end
42
+
43
+ env.merge!(values)
44
+
45
+ env
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TaintedLove
4
+ module Replacer
5
+
6
+ class ReplaceRackFile < Base
7
+ def replace!
8
+ # Assume that Rack::File is used path that are safe
9
+ TaintedLove::Utils::Proxy.new('Rack::File', :call) do
10
+ def before
11
+ env['PATH_INFO'].untaint
12
+ end
13
+
14
+ def env
15
+ arguments.first
16
+ end
17
+ end
18
+
19
+ TaintedLove.proxy_method('Rack::File', :initialize) do |_, *args|
20
+ args.first.untaint
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TaintedLove
4
+ module Replacer
5
+ class ReplaceRackRequest < Base
6
+
7
+ def replace!
8
+ block = method(:taint_params)
9
+
10
+ TaintedLove.proxy_method('Rack::QueryParser', :parse_nested_query, &block)
11
+ TaintedLove.proxy_method('Rack::QueryParser', :parse_query, &block)
12
+ end
13
+
14
+ def taint_params(return_value, *args)
15
+ # Assume that if tainted input uses this method, it's to parse a query string
16
+ # It can also be cookies that are being parsed.
17
+ return unless args.first.tainted?
18
+
19
+ # figure out what is being parsed from the the method that called it
20
+ name = if Thread.current.backtrace(4).first["`parse_cookies_header'"]
21
+ 'cookies'
22
+ else
23
+ 'params'
24
+ end
25
+
26
+ taint = lambda do |params, path|
27
+ source = name + path.map { |p| "[#{p.inspect}]" }.join
28
+
29
+ if params.is_a?(String)
30
+ TaintedLove.tag(params.taint, source: source, value: params)
31
+ end
32
+
33
+ if params.is_a?(Array)
34
+ params.each.with_index do |value, index|
35
+ taint.(value, path + [index])
36
+ end
37
+ end
38
+
39
+ if params.is_a?(Hash)
40
+ params.each do |key, value|
41
+ taint.(value, path + [key])
42
+ end
43
+ end
44
+ end
45
+
46
+ taint.(return_value, [])
47
+ end
48
+ end
49
+ end
50
+ end
@@ -9,50 +9,35 @@ module TaintedLove
9
9
  end
10
10
 
11
11
  def replace!
12
- # taint headers
13
- TaintedLove.proxy_method('ActionDispatch::Http::Headers', :[]) do |return_value, *_args|
14
- return_value.taint
15
- end
16
12
 
17
13
  # taint the values loaded from the database
18
14
  if Object.const_defined?('ActiveRecord::Base')
19
15
  ActiveRecord::Base.after_find do
20
- attributes.values.each do |value|
21
- value.taint unless value.frozen?
16
+ attributes.each do |key, value|
17
+ TaintedLove.tag(value.taint, source: "ActiveRecord attribute #{self.class.to_s}##{key}", value: value)
22
18
  end
23
19
  end
24
20
  end
25
21
 
26
- if Object.const_defined?('ActionController::Base')
27
- ActionController::Base.class_eval do
28
- before_action :taint_params
29
- before_action :taint_cookies
30
-
31
- private
32
-
33
- def taint_params(value = params)
34
- if value.is_a?(ActionController::Parameters) || value.is_a?(ActiveSupport::HashWithIndifferentAccess)
35
- value.values.map { |x| x.taint unless x.frozen? }
36
- value.values.each { |x| taint_params(x) }
37
- else
38
- value.taint unless value.frozen?
39
- end
40
- end
41
-
42
- def taint_cookies
43
- request.cookies.values.each(&:taint)
44
- end
45
- end
22
+ TaintedLove.proxy_method('ActionDispatch::Http::Headers', :[]) do |return_value, *args|
23
+ TaintedLove.tag(return_value.taint, source: "headers[#{args.first.inspect}]", value: return_value)
46
24
  end
47
25
 
48
26
  # taint params keys
49
27
  if Object.const_defined?('ActionController::Parameters')
50
28
  ActionController::Parameters.class_eval do
51
29
  def keys
52
- @parameters.keys.map { |key| key.dup.taint }
30
+ @parameters.keys.map { |key|
31
+ TaintedLove.tag(key.dup.taint, source: "Parameter name #{key.inspect}", value: key)
32
+ }
53
33
  end
54
34
  end
55
35
  end
36
+
37
+ # Transfer tags from String to SafeBuffer
38
+ TaintedLove.proxy_method('ActiveSupport::SafeBuffer', :initialize) do |return_value, str|
39
+ return_value.tainted_love_tags = str.tainted_love_tags
40
+ end
56
41
  end
57
42
  end
58
43
  end
@@ -0,0 +1,69 @@
1
+ module TaintedLove
2
+ module Replacer
3
+ class ReplaceString < Base
4
+ WRAP_METHODS = [
5
+ :+, :*, :[], :[]= , :sub, :replace, :strip, :strip!, :inspect
6
+ ]
7
+
8
+ def replace!
9
+ mod = Module.new do
10
+ def self.wrap_call(name)
11
+ define_method(name) do |*args, &block|
12
+ return super(*args, &block) unless tainted? || args.any?(&:tainted?)
13
+
14
+ result = super(*args, &block)
15
+
16
+ result.tainted_love_tags += tainted_love_tags if tainted?
17
+
18
+ args.select(&:tainted?).each do |arg|
19
+ result.tainted_love_tags += arg.tainted_love_tags
20
+ end
21
+
22
+ result
23
+ end
24
+ end
25
+
26
+ WRAP_METHODS.each do |sym|
27
+ wrap_call(sym)
28
+ end
29
+
30
+ def gsub(*args, &block)
31
+ # Context for this hack: https://stackoverflow.com/a/52783055/3349159
32
+
33
+ match(args.first)
34
+
35
+ unless block.nil?
36
+ block.binding.tap do |b|
37
+ b.local_variable_set(:_tainted_love_tilde_variable, $~)
38
+ b.eval("$~ = _tainted_love_tilde_variable")
39
+ end
40
+ end
41
+
42
+ result = super(*args, &block)
43
+
44
+ result.tainted_love_tags += tainted_love_tags if tainted?
45
+ args.select(&:tainted?).each do |arg|
46
+ result.tainted_love_tags += arg.tainted_love_tags
47
+ end
48
+
49
+ result
50
+ end
51
+
52
+ def split(*args)
53
+ result = super(*args)
54
+
55
+ if tainted?
56
+ result.each do |value|
57
+ value.taint.tainted_love_tags += tainted_love_tags
58
+ end
59
+ end
60
+
61
+ result
62
+ end
63
+ end
64
+
65
+ String.prepend(mod)
66
+ end
67
+ end
68
+ end
69
+ end