tainted_love 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/probots.yml +2 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +1188 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +73 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +57 -0
- data/LICENSE.txt +21 -0
- data/README.md +85 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +10 -0
- data/bin/test +7 -0
- data/dev.yml +28 -0
- data/docs/TaintedLove.html +482 -0
- data/docs/TaintedLove/Configuration.html +499 -0
- data/docs/TaintedLove/Replacer.html +129 -0
- data/docs/TaintedLove/Replacer/ActionViewHelpersMod.html +230 -0
- data/docs/TaintedLove/Replacer/Base.html +320 -0
- data/docs/TaintedLove/Replacer/HelperMod.html +226 -0
- data/docs/TaintedLove/Replacer/HelpersMod.html +230 -0
- data/docs/TaintedLove/Replacer/MarshalMod.html +178 -0
- data/docs/TaintedLove/Replacer/ObjectMod.html +282 -0
- data/docs/TaintedLove/Replacer/ReplaceActionController.html +329 -0
- data/docs/TaintedLove/Replacer/ReplaceActionView.html +317 -0
- data/docs/TaintedLove/Replacer/ReplaceActiveRecord.html +341 -0
- data/docs/TaintedLove/Replacer/ReplaceDigest.html +369 -0
- data/docs/TaintedLove/Replacer/ReplaceFile.html +245 -0
- data/docs/TaintedLove/Replacer/ReplaceKernel.html +211 -0
- data/docs/TaintedLove/Replacer/ReplaceMarshal.html +219 -0
- data/docs/TaintedLove/Replacer/ReplaceObject.html +231 -0
- data/docs/TaintedLove/Replacer/ReplaceRailsUserInput.html +374 -0
- data/docs/TaintedLove/Replacer/ReplaceSprokets.html +297 -0
- data/docs/TaintedLove/Replacer/SprocketsHelperMod.html +226 -0
- data/docs/TaintedLove/Reporter.html +117 -0
- data/docs/TaintedLove/Reporter/Base.html +466 -0
- data/docs/TaintedLove/Reporter/RackReporter.html +309 -0
- data/docs/TaintedLove/Reporter/SinatraReporter.html +402 -0
- data/docs/TaintedLove/Reporter/SinatraReporter/App.html +210 -0
- data/docs/TaintedLove/Reporter/StdoutReporter.html +305 -0
- data/docs/TaintedLove/SinatraReporter.html +387 -0
- data/docs/TaintedLove/SinatraReporter/App.html +210 -0
- data/docs/TaintedLove/StackTrace.html +650 -0
- data/docs/TaintedLove/Utils.html +550 -0
- data/docs/TaintedLove/Validator.html +129 -0
- data/docs/TaintedLove/Validator/ActionViewObjectSend.html +233 -0
- data/docs/TaintedLove/Validator/Base.html +200 -0
- data/docs/TaintedLove/Validator/ErbEval.html +229 -0
- data/docs/TaintedLove/Validator/RedisStoreSerialization.html +238 -0
- data/docs/TaintedLove/Validator/SproketsMarshal.html +233 -0
- data/docs/TaintedLove/Warning.html +665 -0
- data/docs/_index.html +371 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +496 -0
- data/docs/file.README.html +134 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +134 -0
- data/docs/js/app.js +292 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +523 -0
- data/docs/top-level-namespace.html +110 -0
- data/example/.gitignore +31 -0
- data/example/.ruby-version +1 -0
- data/example/Gemfile +67 -0
- data/example/Gemfile.lock +226 -0
- data/example/README.md +24 -0
- data/example/Rakefile +8 -0
- data/example/app/assets/config/manifest.js +3 -0
- data/example/app/assets/images/.keep +0 -0
- data/example/app/assets/javascripts/application.js +16 -0
- data/example/app/assets/javascripts/cable.js +13 -0
- data/example/app/assets/javascripts/channels/.keep +0 -0
- data/example/app/assets/javascripts/products.coffee +3 -0
- data/example/app/assets/stylesheets/application.css +15 -0
- data/example/app/assets/stylesheets/products.scss +3 -0
- data/example/app/assets/stylesheets/scaffolds.scss +84 -0
- data/example/app/channels/application_cable/channel.rb +6 -0
- data/example/app/channels/application_cable/connection.rb +6 -0
- data/example/app/controllers/application_controller.rb +4 -0
- data/example/app/controllers/concerns/.keep +0 -0
- data/example/app/controllers/products_controller.rb +77 -0
- data/example/app/controllers/test_cases_controller.rb +20 -0
- data/example/app/helpers/application_helper.rb +4 -0
- data/example/app/helpers/products_helper.rb +4 -0
- data/example/app/helpers/test_cases_helper.rb +4 -0
- data/example/app/jobs/application_job.rb +4 -0
- data/example/app/mailers/application_mailer.rb +6 -0
- data/example/app/models/application_record.rb +5 -0
- data/example/app/models/concerns/.keep +0 -0
- data/example/app/models/product.rb +4 -0
- data/example/app/views/layouts/application.html.erb +15 -0
- data/example/app/views/layouts/mailer.html.erb +13 -0
- data/example/app/views/layouts/mailer.text.erb +1 -0
- data/example/app/views/products/_form.html.erb +32 -0
- data/example/app/views/products/_product.json.jbuilder +4 -0
- data/example/app/views/products/edit.html.erb +6 -0
- data/example/app/views/products/index.html.erb +31 -0
- data/example/app/views/products/index.json.jbuilder +3 -0
- data/example/app/views/products/new.html.erb +5 -0
- data/example/app/views/products/show.html.erb +19 -0
- data/example/app/views/products/show.json.jbuilder +3 -0
- data/example/app/views/test_cases/xss.html.erb +10 -0
- data/example/bin/bundle +5 -0
- data/example/bin/rails +11 -0
- data/example/bin/rake +11 -0
- data/example/bin/setup +38 -0
- data/example/bin/spring +18 -0
- data/example/bin/update +33 -0
- data/example/bin/yarn +11 -0
- data/example/config.ru +7 -0
- data/example/config/application.rb +21 -0
- data/example/config/boot.rb +6 -0
- data/example/config/cable.yml +10 -0
- data/example/config/credentials.yml.enc +1 -0
- data/example/config/database.yml +25 -0
- data/example/config/environment.rb +7 -0
- data/example/config/environments/development.rb +63 -0
- data/example/config/environments/production.rb +96 -0
- data/example/config/environments/test.rb +48 -0
- data/example/config/initializers/application_controller_renderer.rb +10 -0
- data/example/config/initializers/assets.rb +16 -0
- data/example/config/initializers/backtrace_silencers.rb +9 -0
- data/example/config/initializers/content_security_policy.rb +27 -0
- data/example/config/initializers/cookies_serializer.rb +7 -0
- data/example/config/initializers/filter_parameter_logging.rb +6 -0
- data/example/config/initializers/inflections.rb +18 -0
- data/example/config/initializers/mime_types.rb +6 -0
- data/example/config/initializers/tainted_love.rb +7 -0
- data/example/config/initializers/wrap_parameters.rb +16 -0
- data/example/config/locales/en.yml +33 -0
- data/example/config/puma.rb +36 -0
- data/example/config/routes.rb +10 -0
- data/example/config/spring.rb +8 -0
- data/example/config/storage.yml +34 -0
- data/example/db/migrate/20190311220346_create_products.rb +13 -0
- data/example/db/schema.rb +23 -0
- data/example/db/seeds.rb +9 -0
- data/example/lib/assets/.keep +0 -0
- data/example/lib/tasks/.keep +0 -0
- data/example/log/.keep +0 -0
- data/example/package.json +5 -0
- data/example/public/404.html +67 -0
- data/example/public/422.html +67 -0
- data/example/public/500.html +66 -0
- data/example/public/apple-touch-icon-precomposed.png +0 -0
- data/example/public/apple-touch-icon.png +0 -0
- data/example/public/favicon.ico +0 -0
- data/example/public/robots.txt +1 -0
- data/example/storage/.keep +0 -0
- data/example/test/application_system_test_case.rb +7 -0
- data/example/test/controllers/.keep +0 -0
- data/example/test/controllers/products_controller_test.rb +66 -0
- data/example/test/controllers/test_cases_controller_test.rb +39 -0
- data/example/test/fixtures/.keep +0 -0
- data/example/test/fixtures/files/.keep +0 -0
- data/example/test/fixtures/products.yml +11 -0
- data/example/test/helpers/.keep +0 -0
- data/example/test/integration/.keep +0 -0
- data/example/test/mailers/.keep +0 -0
- data/example/test/models/.keep +0 -0
- data/example/test/models/product_test.rb +9 -0
- data/example/test/replacers/replace_active_record_test.rb +31 -0
- data/example/test/replacers/replace_sprokets_test.rb +8 -0
- data/example/test/system/.keep +0 -0
- data/example/test/system/products_test.rb +49 -0
- data/example/test/test_helper.rb +37 -0
- data/example/tmp/.keep +0 -0
- data/example/vendor/.keep +0 -0
- data/lib/tainted_love.rb +57 -0
- data/lib/tainted_love/configuration.rb +16 -0
- data/lib/tainted_love/replacer/base.rb +25 -0
- data/lib/tainted_love/replacer/replace_action_controller.rb +61 -0
- data/lib/tainted_love/replacer/replace_action_view.rb +39 -0
- data/lib/tainted_love/replacer/replace_active_record.rb +47 -0
- data/lib/tainted_love/replacer/replace_digest.rb +39 -0
- data/lib/tainted_love/replacer/replace_file.rb +32 -0
- data/lib/tainted_love/replacer/replace_kernel.rb +44 -0
- data/lib/tainted_love/replacer/replace_marshal.rb +19 -0
- data/lib/tainted_love/replacer/replace_object.rb +30 -0
- data/lib/tainted_love/replacer/replace_rails_user_input.rb +59 -0
- data/lib/tainted_love/replacer/replace_sprokets.rb +25 -0
- data/lib/tainted_love/replacer/replace_yaml.rb +28 -0
- data/lib/tainted_love/reporter/base.rb +47 -0
- data/lib/tainted_love/reporter/file_reporter.rb +28 -0
- data/lib/tainted_love/reporter/stdout_reporter.rb +30 -0
- data/lib/tainted_love/stack_trace.rb +46 -0
- data/lib/tainted_love/utils.rb +80 -0
- data/lib/tainted_love/validator/action_view_object_send.rb +15 -0
- data/lib/tainted_love/validator/base.rb +16 -0
- data/lib/tainted_love/validator/erb_eval.rb +13 -0
- data/lib/tainted_love/validator/redis_store_serialization.rb +13 -0
- data/lib/tainted_love/validator/sprokets_marshal.rb +15 -0
- data/lib/tainted_love/version.rb +5 -0
- data/lib/tainted_love/warning.rb +30 -0
- data/tainted_love.gemspec +31 -0
- metadata +315 -0
data/lib/tainted_love.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Dir[File.dirname(__FILE__) + '/tainted_love/**/*.rb'].each { |f| require f }
|
4
|
+
|
5
|
+
module TaintedLove
|
6
|
+
class << self
|
7
|
+
include TaintedLove::Utils
|
8
|
+
|
9
|
+
attr_reader :configuration
|
10
|
+
|
11
|
+
# Enables TaintedLove. Use a block to configure the TaintedLove::Configuration
|
12
|
+
#
|
13
|
+
# @yield [TaintedLove::Configuration]
|
14
|
+
# @returns [TaintedLove::Configuration]
|
15
|
+
def enable!
|
16
|
+
configuration = TaintedLove::Configuration.new
|
17
|
+
|
18
|
+
configuration.logger.info('TaintedLove is enabled')
|
19
|
+
configuration.replacers = TaintedLove::Replacer::Base.replacers
|
20
|
+
configuration.validators = TaintedLove::Validator::Base.validators
|
21
|
+
configuration.reporter = TaintedLove::Reporter::StdoutReporter.new
|
22
|
+
|
23
|
+
# Allows customization of which replacers/validators should be used
|
24
|
+
yield configuration if block_given?
|
25
|
+
|
26
|
+
@configuration = configuration
|
27
|
+
|
28
|
+
configuration.replacers.each do |replacer|
|
29
|
+
replacer = replacer.new
|
30
|
+
replacer.replace! if replacer.should_replace?
|
31
|
+
end
|
32
|
+
|
33
|
+
configuration
|
34
|
+
end
|
35
|
+
|
36
|
+
# Report tainted input
|
37
|
+
#
|
38
|
+
# @param replacer [Symbol] Replacer reporting the issue
|
39
|
+
# @param tainted_input [Object] Tainted object
|
40
|
+
# @param tags [Array<Symbol>] Tags to classify the warning
|
41
|
+
# @param message [String] Message about the warning
|
42
|
+
def report(replacer, tainted_input, tags = [], message = nil)
|
43
|
+
warning = TaintedLove::Warning.new
|
44
|
+
warning.tainted_input = tainted_input
|
45
|
+
warning.stack_trace = TaintedLove::StackTrace.new(Thread.current.backtrace(3))
|
46
|
+
warning.replacer = replacer
|
47
|
+
warning.tags = tags
|
48
|
+
warning.message = message
|
49
|
+
|
50
|
+
should_remove = @configuration.validators.any? do |validator|
|
51
|
+
validator.new.remove?(warning)
|
52
|
+
end
|
53
|
+
|
54
|
+
@configuration.reporter.add_warning(warning) unless should_remove
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module TaintedLove
|
6
|
+
class Configuration
|
7
|
+
attr_accessor :reporter, :logger, :validators, :replacers
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@reporter = TaintedLove::Reporter::StdoutReporter.new
|
11
|
+
@logger = Logger.new(STDERR)
|
12
|
+
@validators = []
|
13
|
+
@replacers = []
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
# Replacer will replace methods to report tainted input and taint values from user input coming
|
5
|
+
# from librairies.
|
6
|
+
module Replacer
|
7
|
+
class Base
|
8
|
+
# Determines if the replacer can run in the current context. This would usually check Ruby
|
9
|
+
# version or gem versions to see which classes and methods to replace.
|
10
|
+
def should_replace?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
# List of defined replacers
|
15
|
+
#
|
16
|
+
# @return [Array<Class>]
|
17
|
+
def self.replacers
|
18
|
+
TaintedLove::Replacer.constants.map do |const|
|
19
|
+
cls = TaintedLove::Replacer.const_get(const)
|
20
|
+
cls if cls.method_defined?(:replace!)
|
21
|
+
end.compact
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
module Replacer
|
5
|
+
class ReplaceActionController < Base
|
6
|
+
def should_replace?
|
7
|
+
Object.const_defined?('ActionController')
|
8
|
+
end
|
9
|
+
|
10
|
+
def replace!
|
11
|
+
TaintedLove.proxy_method('ActionController::Instrumentation', :send_file) do |_, *args|
|
12
|
+
TaintedLove.report(
|
13
|
+
:ReplaceActionController,
|
14
|
+
args.first,
|
15
|
+
[:lfi],
|
16
|
+
'Sendfile using tainted file name'
|
17
|
+
) if args.first.tainted?
|
18
|
+
end
|
19
|
+
|
20
|
+
TaintedLove.proxy_method('ActionController::Instrumentation', :render) do |_, *args|
|
21
|
+
unless args.empty?
|
22
|
+
f = args.first
|
23
|
+
|
24
|
+
if f.is_a?(Hash)
|
25
|
+
if f.key?(:inline) && f[:inline].tainted?
|
26
|
+
TaintedLove.report(
|
27
|
+
:ReplaceActionController,
|
28
|
+
f[:inline],
|
29
|
+
[:rce],
|
30
|
+
'render(inline:) using tainted string'
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
if f.key?(:file) && f[:file].tainted?
|
35
|
+
TaintedLove.report(
|
36
|
+
:ReplaceActionController,
|
37
|
+
f[:file],
|
38
|
+
[:lfi],
|
39
|
+
'render(file:) using tainted file name'
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if f.is_a?(String) && f.tainted?
|
45
|
+
TaintedLove.report(
|
46
|
+
:ReplaceActionController,
|
47
|
+
f,
|
48
|
+
[:lfi],
|
49
|
+
'render using tainted template name'
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
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
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
module Replacer
|
5
|
+
class ReplaceActionView < Base
|
6
|
+
def should_replace?
|
7
|
+
Object.const_defined?('ActionView')
|
8
|
+
end
|
9
|
+
|
10
|
+
def replace!
|
11
|
+
ActionView::OutputBuffer.class_eval do
|
12
|
+
def append=(value)
|
13
|
+
if value.tainted? && value.html_safe?
|
14
|
+
TaintedLove.report(
|
15
|
+
:ReplaceActionView,
|
16
|
+
value,
|
17
|
+
[:xss],
|
18
|
+
'Tainted string is html_safe'
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
self << value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Untaint the yield of a template
|
27
|
+
mod = Module.new do
|
28
|
+
def render(*args, &block)
|
29
|
+
super(*args) do |*sub_args, &sub_block|
|
30
|
+
block.call(*sub_args, &sub_block).untaint
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
ActionView::Template.prepend(mod) if Object.const_defined?('ActionView::Template')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
module Replacer
|
5
|
+
class ReplaceActiveRecord < Base
|
6
|
+
def should_replace?
|
7
|
+
Object.const_defined?('ActiveRecord')
|
8
|
+
end
|
9
|
+
|
10
|
+
def replace!
|
11
|
+
require 'active_record/relation'
|
12
|
+
|
13
|
+
TaintedLove.proxy_method('ActiveRecord::QueryMethods', :where) do |_, *args|
|
14
|
+
unless args.empty?
|
15
|
+
f = args.first
|
16
|
+
if f.is_a?(String) && f.tainted?
|
17
|
+
TaintedLove.report(:ReplaceActiveRecord, f, [:sqli], 'Model.where using tainted string')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
TaintedLove.proxy_method('ActiveRecord::QueryMethods', :select) 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#select using tainted string')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
mod = Module.new do
|
32
|
+
[:find_by_sql, :count_by_sql].each do |method|
|
33
|
+
define_method(method) do |*args|
|
34
|
+
if args.first.tainted?
|
35
|
+
TaintedLove.report(:ReplaceActiveRecord, args.first, [:sqli], "Model##{method} using tainted string")
|
36
|
+
end
|
37
|
+
|
38
|
+
super(*args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ActiveRecord::Base.extend(mod)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
module Replacer
|
5
|
+
class ReplaceDigest < Base
|
6
|
+
def should_replace?
|
7
|
+
Object.const_defined?('Digest')
|
8
|
+
end
|
9
|
+
|
10
|
+
def replace!
|
11
|
+
digests = [:MD5, :SHA256, :SHA512, :SHA2, :SHA384, :SHA1]
|
12
|
+
|
13
|
+
digests.each do |digest|
|
14
|
+
mod = Digest.const_get(digest)
|
15
|
+
mod.extend(create_module(mod)) if Object.const_defined?(mod.to_s)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_module(digest_class)
|
20
|
+
module_code = %{
|
21
|
+
Module.new do
|
22
|
+
def hexdigest(value)
|
23
|
+
digest = super
|
24
|
+
|
25
|
+
digest.instance_eval do
|
26
|
+
def tainted_love_origin
|
27
|
+
#{digest_class}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
digest
|
32
|
+
end
|
33
|
+
end
|
34
|
+
}
|
35
|
+
eval(module_code)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
module Replacer
|
5
|
+
class ReplaceFile < Base
|
6
|
+
def replace!
|
7
|
+
File.instance_eval do
|
8
|
+
alias :_tainted_love_original_read :read
|
9
|
+
alias :_tainted_love_original_write :write
|
10
|
+
|
11
|
+
def read(*args)
|
12
|
+
if args.first.tainted?
|
13
|
+
TaintedLove.report(:ReplaceFile, args.first, [:lfi], 'File read using tainted file name')
|
14
|
+
|
15
|
+
_tainted_love_original_read(*args)
|
16
|
+
else
|
17
|
+
_tainted_love_original_read(*args).untaint
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def write(*args)
|
22
|
+
if args.first.tainted?
|
23
|
+
TaintedLove.report(:ReplaceFile, args.first, [:lfi], 'File write using tainted file name')
|
24
|
+
end
|
25
|
+
|
26
|
+
_tainted_love_original_write(*args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
module Replacer
|
5
|
+
class ReplaceKernel < Base
|
6
|
+
def replace!
|
7
|
+
%i[eval system `].each do |method|
|
8
|
+
TaintedLove.proxy_method(Kernel, method) do |_, *args|
|
9
|
+
TaintedLove.report(
|
10
|
+
:ReplaceKernel,
|
11
|
+
args.first,
|
12
|
+
[:rce],
|
13
|
+
'Command execution using tainted input'
|
14
|
+
) if args.first&.tainted?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Kernel.class_eval do
|
19
|
+
alias_method :_tainted_love_original_open, :open
|
20
|
+
|
21
|
+
def open(*args, &block)
|
22
|
+
first = args.first
|
23
|
+
return_value = _tainted_love_original_open(*args, &block)
|
24
|
+
|
25
|
+
if first.tainted?
|
26
|
+
return_value.taint
|
27
|
+
|
28
|
+
TaintedLove.report(
|
29
|
+
:ReplaceKernel,
|
30
|
+
first,
|
31
|
+
[:rce],
|
32
|
+
'Kernel#open begins with "|" and uses tainted input'
|
33
|
+
) if first.is_a?(String) && first[0] == '|'
|
34
|
+
else
|
35
|
+
return_value.untaint
|
36
|
+
end
|
37
|
+
|
38
|
+
return_value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
module Replacer
|
5
|
+
class ReplaceMarshal < Base
|
6
|
+
def replace!
|
7
|
+
mod = Module.new do
|
8
|
+
def load(source, proc = nil)
|
9
|
+
TaintedLove.report(:ReplaceMarshal, source, [:rce], 'Marshal.load using tainted input') if source.tainted?
|
10
|
+
|
11
|
+
super(source, proc)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Marshal.singleton_class.prepend(mod)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
module Replacer
|
5
|
+
class ReplaceObject < Base
|
6
|
+
def replace!
|
7
|
+
mod = Module.new do
|
8
|
+
def send(*args, &block)
|
9
|
+
if args[0].tainted? && args[1].tainted?
|
10
|
+
TaintedLove.report(
|
11
|
+
:ReplaceObject,
|
12
|
+
args.first,
|
13
|
+
[:rce],
|
14
|
+
'User input in the first 2 arguments of Object#send'
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
super(*args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def tainted_love_tracking
|
22
|
+
@tainted_love_tracking ||= []
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Object.prepend(mod)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TaintedLove
|
4
|
+
module Replacer
|
5
|
+
# Ensures user input is tainted in Rails
|
6
|
+
class ReplaceRailsUserInput < Base
|
7
|
+
def should_replace?
|
8
|
+
Object.const_defined?('Rails')
|
9
|
+
end
|
10
|
+
|
11
|
+
def replace!
|
12
|
+
# taint headers
|
13
|
+
TaintedLove.proxy_method('ActionDispatch::Http::Headers', :[]) do |return_value, *_args|
|
14
|
+
return_value.taint
|
15
|
+
end
|
16
|
+
|
17
|
+
# taint the values loaded from the database
|
18
|
+
if Object.const_defined?('ActiveRecord::Base')
|
19
|
+
ActiveRecord::Base.after_find do
|
20
|
+
attributes.values.each do |value|
|
21
|
+
value.taint unless value.frozen?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
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
|
46
|
+
end
|
47
|
+
|
48
|
+
# taint params keys
|
49
|
+
if Object.const_defined?('ActionController::Parameters')
|
50
|
+
ActionController::Parameters.class_eval do
|
51
|
+
def keys
|
52
|
+
@parameters.keys.map { |key| key.dup.taint }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|