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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +2 -0
- data/bin/setup +3 -3
- data/bin/test +6 -2
- data/dev.yml +1 -1
- data/lib/tainted_love.rb +2 -2
- data/lib/tainted_love/replacer/base.rb +5 -1
- data/lib/tainted_love/replacer/replace_action_controller.rb +0 -4
- data/lib/tainted_love/replacer/replace_active_record.rb +21 -1
- data/lib/tainted_love/replacer/replace_graphql.rb +27 -0
- data/lib/tainted_love/replacer/replace_kernel.rb +1 -1
- data/lib/tainted_love/replacer/replace_object.rb +8 -2
- data/lib/tainted_love/replacer/replace_rack_builder.rb +51 -0
- data/lib/tainted_love/replacer/replace_rack_file.rb +25 -0
- data/lib/tainted_love/replacer/replace_rack_query_parser.rb +50 -0
- data/lib/tainted_love/replacer/replace_rails_user_input.rb +12 -27
- data/lib/tainted_love/replacer/replace_string.rb +69 -0
- data/lib/tainted_love/replacer/replace_tag_builder.rb +16 -0
- data/lib/tainted_love/reporter/base.rb +4 -1
- data/lib/tainted_love/reporter/stdout_reporter.rb +1 -0
- data/lib/tainted_love/utils.rb +4 -19
- data/lib/tainted_love/utils/proxy.rb +95 -0
- data/lib/tainted_love/validator/action_dispatch_diagnostics.rb +20 -0
- data/lib/tainted_love/validator/active_record_find.rb +15 -0
- data/lib/tainted_love/validator/erb_eval.rb +1 -3
- data/lib/tainted_love/validator/haml_eval.rb +25 -0
- data/lib/tainted_love/validator/i18n_load.rb +17 -0
- data/lib/tainted_love/validator/ignore.rb +21 -0
- data/lib/tainted_love/version.rb +1 -1
- data/service.yml +6 -0
- data/{example → tests/rails}/.gitignore +0 -0
- data/{example → tests/rails}/.ruby-version +0 -0
- data/{example → tests/rails}/Gemfile +5 -4
- data/{example → tests/rails}/Gemfile.lock +29 -32
- data/{example → tests/rails}/README.md +0 -0
- data/{example → tests/rails}/Rakefile +0 -0
- data/{example → tests/rails}/app/assets/config/manifest.js +0 -0
- data/{example → tests/rails}/app/assets/images/.keep +0 -0
- data/{example → tests/rails}/app/assets/javascripts/application.js +0 -0
- data/{example → tests/rails}/app/assets/javascripts/cable.js +0 -0
- data/{example → tests/rails}/app/assets/javascripts/channels/.keep +0 -0
- data/{example → tests/rails}/app/assets/javascripts/products.coffee +0 -0
- data/{example → tests/rails}/app/assets/stylesheets/application.css +0 -0
- data/{example → tests/rails}/app/assets/stylesheets/products.scss +0 -0
- data/{example → tests/rails}/app/assets/stylesheets/scaffolds.scss +0 -0
- data/{example → tests/rails}/app/channels/application_cable/channel.rb +0 -0
- data/{example → tests/rails}/app/channels/application_cable/connection.rb +0 -0
- data/{example → tests/rails}/app/controllers/application_controller.rb +0 -0
- data/{example → tests/rails}/app/controllers/concerns/.keep +0 -0
- data/tests/rails/app/controllers/graphql_controller.rb +43 -0
- data/{example → tests/rails}/app/controllers/products_controller.rb +0 -0
- data/tests/rails/app/controllers/test_cases_controller.rb +43 -0
- data/tests/rails/app/graphql/example_schema.rb +4 -0
- data/{example/app/models/concerns → tests/rails/app/graphql/mutations}/.keep +0 -0
- data/{example/lib/assets → tests/rails/app/graphql/types}/.keep +0 -0
- data/tests/rails/app/graphql/types/base_enum.rb +4 -0
- data/tests/rails/app/graphql/types/base_input_object.rb +4 -0
- data/tests/rails/app/graphql/types/base_interface.rb +5 -0
- data/tests/rails/app/graphql/types/base_object.rb +4 -0
- data/tests/rails/app/graphql/types/base_scalar.rb +4 -0
- data/tests/rails/app/graphql/types/base_union.rb +4 -0
- data/tests/rails/app/graphql/types/mutation_type.rb +10 -0
- data/tests/rails/app/graphql/types/product_type.rb +10 -0
- data/tests/rails/app/graphql/types/query_type.rb +46 -0
- data/tests/rails/app/graphql/types/taint_test_case_input.rb +8 -0
- data/{example → tests/rails}/app/helpers/application_helper.rb +0 -0
- data/{example → tests/rails}/app/helpers/products_helper.rb +0 -0
- data/{example → tests/rails}/app/helpers/test_cases_helper.rb +0 -0
- data/{example → tests/rails}/app/jobs/application_job.rb +0 -0
- data/{example → tests/rails}/app/mailers/application_mailer.rb +0 -0
- data/{example → tests/rails}/app/models/application_record.rb +0 -0
- data/{example/lib/tasks → tests/rails/app/models/concerns}/.keep +0 -0
- data/{example → tests/rails}/app/models/product.rb +0 -0
- data/{example → tests/rails}/app/views/layouts/application.html.erb +0 -0
- data/{example → tests/rails}/app/views/layouts/mailer.html.erb +0 -0
- data/{example → tests/rails}/app/views/layouts/mailer.text.erb +0 -0
- data/{example → tests/rails}/app/views/products/_form.html.erb +0 -0
- data/{example → tests/rails}/app/views/products/_product.json.jbuilder +0 -0
- data/{example → tests/rails}/app/views/products/edit.html.erb +0 -0
- data/{example → tests/rails}/app/views/products/index.html.erb +0 -0
- data/{example → tests/rails}/app/views/products/index.json.jbuilder +0 -0
- data/{example → tests/rails}/app/views/products/new.html.erb +0 -0
- data/{example → tests/rails}/app/views/products/show.html.erb +0 -0
- data/{example → tests/rails}/app/views/products/show.json.jbuilder +0 -0
- data/{example → tests/rails}/app/views/test_cases/xss.html.erb +0 -0
- data/{example → tests/rails}/bin/bundle +0 -0
- data/{example → tests/rails}/bin/rails +0 -0
- data/{example → tests/rails}/bin/rake +0 -0
- data/{example → tests/rails}/bin/setup +0 -0
- data/{example → tests/rails}/bin/spring +0 -0
- data/{example → tests/rails}/bin/update +0 -0
- data/{example → tests/rails}/bin/yarn +0 -0
- data/{example → tests/rails}/config.ru +0 -0
- data/{example → tests/rails}/config/application.rb +0 -0
- data/{example → tests/rails}/config/boot.rb +0 -0
- data/{example → tests/rails}/config/cable.yml +0 -0
- data/{example → tests/rails}/config/credentials.yml.enc +0 -0
- data/{example → tests/rails}/config/database.yml +0 -0
- data/{example → tests/rails}/config/environment.rb +0 -0
- data/{example → tests/rails}/config/environments/development.rb +0 -0
- data/{example → tests/rails}/config/environments/production.rb +0 -0
- data/{example → tests/rails}/config/environments/test.rb +0 -0
- data/{example → tests/rails}/config/initializers/application_controller_renderer.rb +0 -0
- data/{example → tests/rails}/config/initializers/assets.rb +0 -0
- data/{example → tests/rails}/config/initializers/backtrace_silencers.rb +0 -0
- data/{example → tests/rails}/config/initializers/content_security_policy.rb +0 -0
- data/{example → tests/rails}/config/initializers/cookies_serializer.rb +0 -0
- data/{example → tests/rails}/config/initializers/filter_parameter_logging.rb +0 -0
- data/{example → tests/rails}/config/initializers/inflections.rb +0 -0
- data/{example → tests/rails}/config/initializers/mime_types.rb +0 -0
- data/{example → tests/rails}/config/initializers/tainted_love.rb +0 -0
- data/{example → tests/rails}/config/initializers/wrap_parameters.rb +0 -0
- data/{example → tests/rails}/config/locales/en.yml +0 -0
- data/{example → tests/rails}/config/puma.rb +0 -0
- data/{example → tests/rails}/config/routes.rb +6 -0
- data/{example → tests/rails}/config/spring.rb +0 -0
- data/{example → tests/rails}/config/storage.yml +0 -0
- data/{example → tests/rails}/db/migrate/20190311220346_create_products.rb +0 -0
- data/{example → tests/rails}/db/schema.rb +0 -0
- data/{example → tests/rails}/db/seeds.rb +0 -0
- data/{example/log → tests/rails/lib/assets}/.keep +0 -0
- data/{example/storage → tests/rails/lib/tasks}/.keep +0 -0
- data/{example/test/controllers → tests/rails/log}/.keep +0 -0
- data/{example → tests/rails}/package.json +0 -0
- data/{example → tests/rails}/public/404.html +0 -0
- data/{example → tests/rails}/public/422.html +0 -0
- data/{example → tests/rails}/public/500.html +0 -0
- data/{example → tests/rails}/public/apple-touch-icon-precomposed.png +0 -0
- data/{example → tests/rails}/public/apple-touch-icon.png +0 -0
- data/{example → tests/rails}/public/favicon.ico +0 -0
- data/{example → tests/rails}/public/robots.txt +0 -0
- data/{example/test/fixtures → tests/rails/storage}/.keep +0 -0
- data/tests/rails/test.sh +1 -0
- data/{example → tests/rails}/test/application_system_test_case.rb +0 -0
- data/{example/test/fixtures/files → tests/rails/test/controllers}/.keep +0 -0
- data/tests/rails/test/controllers/graphql_controller_test.rb +28 -0
- data/{example → tests/rails}/test/controllers/products_controller_test.rb +0 -0
- data/tests/rails/test/controllers/test_cases_controller_test.rb +54 -0
- data/{example/test/helpers → tests/rails/test/fixtures}/.keep +0 -0
- data/{example/test/integration → tests/rails/test/fixtures/files}/.keep +0 -0
- data/{example → tests/rails}/test/fixtures/products.yml +0 -0
- data/{example/test/mailers → tests/rails/test/helpers}/.keep +0 -0
- data/{example/test/models → tests/rails/test/integration}/.keep +0 -0
- data/{example/test/system → tests/rails/test/mailers}/.keep +0 -0
- data/{example/tmp → tests/rails/test/models}/.keep +0 -0
- data/{example → tests/rails}/test/models/product_test.rb +0 -0
- data/{example → tests/rails}/test/replacers/replace_active_record_test.rb +28 -0
- data/tests/rails/test/replacers/replace_rails_user_input_test.rb +13 -0
- data/{example → tests/rails}/test/replacers/replace_sprokets_test.rb +0 -0
- data/{example/vendor → tests/rails/test/system}/.keep +0 -0
- data/{example → tests/rails}/test/system/products_test.rb +0 -0
- data/{example → tests/rails}/test/test_helper.rb +0 -0
- data/tests/rails/tmp/.keep +0 -0
- data/tests/rails/vendor/.keep +0 -0
- data/tests/sinatra/Gemfile +3 -0
- data/tests/sinatra/Gemfile.lock +29 -0
- data/tests/sinatra/app.rb +26 -0
- data/tests/sinatra/test.sh +1 -0
- data/tests/sinatra/views/xss.erb +1 -0
- data/tools/web/Gemfile +1 -1
- data/tools/web/application.rb +17 -2
- data/tools/web/public/application.css +38 -2
- data/tools/web/views/index.erb +5 -11
- data/tools/web/views/input.erb +4 -0
- data/tools/web/views/line.erb +2 -2
- metadata +146 -111
- data/example/app/controllers/test_cases_controller.rb +0 -20
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea2bb8edc59c047e25dfca2ee1e8375a008ccf93c4e98e4746854c404cc7d11d
|
|
4
|
+
data.tar.gz: c23dd71cd581ab0a27e3df8d9e137f91cf203cefabb304b7761bb86f7a44f1b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b8ae7c54209f62bc4ecf0257b470bdc2733b93b69f35635b40555cd6f42eb479755a69e0a971016a1d164071801db4af7e7ae5f933b44b211140ab3c4ca5f688
|
|
7
|
+
data.tar.gz: c5e76a2da036357ba6a73c3a01c01d717362077adfd29bbfe0b5b2ed4ce0b95fa6fe13e3c05bad779669c8eaf024ea32184e8b5b79aedbdcc435565e94eee6c0
|
data/Gemfile.lock
CHANGED
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
data/bin/test
CHANGED
data/dev.yml
CHANGED
data/lib/tainted_love.rb
CHANGED
|
@@ -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
|
-
# @
|
|
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
|
|
@@ -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
|
|
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
|
|
@@ -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
|
|
22
|
-
|
|
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.
|
|
21
|
-
value.taint
|
|
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
|
-
|
|
27
|
-
|
|
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|
|
|
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
|