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.
- 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
|