tainted_love 0.1.5 → 0.4.0

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