CVE-2022-21831
Description
A code injection vulnerability exists in the Active Storage >= v5.2.0 that could allow an attacker to execute code via image_processing arguments.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Code injection vulnerability in Rails Active Storage via untrusted image_processing arguments allows RCE (versions >=5.2.0).
Vulnerability
Active Storage in Ruby on Rails versions >=5.2.0 contains a code injection vulnerability through the image_processing feature, specifically when using the mini_magick backend. The vulnerability arises when user-supplied input is passed as transformation method or argument to blob.variant() calls, allowing an attacker to inject arbitrary ImageMagick commands [4].
Exploitation
An attacker needs to supply untrusted input that reaches the transformation method or its arguments in a variant call, for example: <%= image_tag blob.variant(params[:t] => params[:v]) %>. This allows the attacker to control the transformation options, leading to arbitrary code execution [4].
Impact
Successful exploitation allows remote code execution within the context of the web application, potentially compromising the entire application and its data [4].
Mitigation
Fixed in versions 7.0.2.3, 6.1.4.7, 6.0.4.7, and 5.2.6.3. Workarounds include implementing a strict allow-list on accepted transformation methods and arguments, and applying a strict ImageMagick security policy. Patches are available for unsupported releases [4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
activestorageRubyGems | >= 5.2.0, < 5.2.6.3 | 5.2.6.3 |
activestorageRubyGems | >= 6.0.0, < 6.0.4.7 | 6.0.4.7 |
activestorageRubyGems | >= 6.1.0, < 6.1.4.7 | 6.1.4.7 |
activestorageRubyGems | >= 7.0.0, < 7.0.2.3 | 7.0.2.3 |
Affected products
4- Active Storage/Active Storagedescription
- ghsa-coords3 versionspkg:gem/activestoragepkg:rpm/opensuse/ruby3.2-rubygem-activestorage-7.0&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/rubygem-activestorage-7.0&distro=openSUSE%20Tumbleweed
>= 5.2.0, < 5.2.6.3+ 2 more
- (no CPE)range: >= 5.2.0, < 5.2.6.3
- (no CPE)range: < 7.0.4.3-1.1
- (no CPE)range: < 7.0.2.3-1.1
Patches
10a72f7d670e9Added image trasnformation validation via configurable allow-list
5 files changed · +463 −0
activestorage/lib/active_storage/engine.rb+17 −0 modified@@ -80,6 +80,20 @@ class Engine < Rails::Engine # :nodoc: application/pdf ) + default_unsupported_image_processing_arguments = %w( + -debug + -display + -distribute-cache + -help + -path + -print + -set + -verbose + -version + -write + -write-mask + ) + config.eager_load_namespaces << ActiveStorage initializer "active_storage.configs" do @@ -93,6 +107,9 @@ class Engine < Rails::Engine # :nodoc: ActiveStorage.draw_routes = app.config.active_storage.draw_routes != false ActiveStorage.resolve_model_to_route = app.config.active_storage.resolve_model_to_route || :rails_storage_redirect + ActiveStorage.supported_image_processing_methods = app.config.active_storage.supported_image_processing_methods || [] + ActiveStorage.unsupported_image_processing_arguments = app.config.active_storage.unsupported_image_processing_arguments || default_unsupported_image_processing_arguments + ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || [] ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || [] ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
activestorage/lib/active_storage.rb+3 −0 modified@@ -59,6 +59,9 @@ module ActiveStorage mattr_accessor :content_types_to_serve_as_binary, default: [] mattr_accessor :content_types_allowed_inline, default: [] + mattr_accessor :supported_image_processing_methods, default: [] + mattr_accessor :unsupported_image_processing_arguments + mattr_accessor :service_urls_expire_in, default: 5.minutes mattr_accessor :urls_expire_in
activestorage/lib/active_storage/transformers/image_processing_transformer.rb+352 −0 modified@@ -13,6 +13,300 @@ module ActiveStorage module Transformers class ImageProcessingTransformer < Transformer private + class UnsupportedImageProcessingMethod < StandardError; end + class UnsupportedImageProcessingArgument < StandardError; end + SUPPORTED_IMAGE_PROCESSING_METHODS = [ + "adaptive_blur", + "adaptive_resize", + "adaptive_sharpen", + "adjoin", + "affine", + "alpha", + "annotate", + "antialias", + "append", + "apply", + "attenuate", + "authenticate", + "auto_gamma", + "auto_level", + "auto_orient", + "auto_threshold", + "backdrop", + "background", + "bench", + "bias", + "bilateral_blur", + "black_point_compensation", + "black_threshold", + "blend", + "blue_primary", + "blue_shift", + "blur", + "border", + "bordercolor", + "borderwidth", + "brightness_contrast", + "cache", + "canny", + "caption", + "channel", + "channel_fx", + "charcoal", + "chop", + "clahe", + "clamp", + "clip", + "clip_path", + "clone", + "clut", + "coalesce", + "colorize", + "colormap", + "color_matrix", + "colors", + "colorspace", + "colourspace", + "color_threshold", + "combine", + "combine_options", + "comment", + "compare", + "complex", + "compose", + "composite", + "compress", + "connected_components", + "contrast", + "contrast_stretch", + "convert", + "convolve", + "copy", + "crop", + "cycle", + "deconstruct", + "define", + "delay", + "delete", + "density", + "depth", + "descend", + "deskew", + "despeckle", + "direction", + "displace", + "dispose", + "dissimilarity_threshold", + "dissolve", + "distort", + "dither", + "draw", + "duplicate", + "edge", + "emboss", + "encoding", + "endian", + "enhance", + "equalize", + "evaluate", + "evaluate_sequence", + "extent", + "extract", + "family", + "features", + "fft", + "fill", + "filter", + "flatten", + "flip", + "floodfill", + "flop", + "font", + "foreground", + "format", + "frame", + "function", + "fuzz", + "fx", + "gamma", + "gaussian_blur", + "geometry", + "gravity", + "grayscale", + "green_primary", + "hald_clut", + "highlight_color", + "hough_lines", + "iconGeometry", + "iconic", + "identify", + "ift", + "illuminant", + "immutable", + "implode", + "insert", + "intensity", + "intent", + "interlace", + "interline_spacing", + "interpolate", + "interpolative_resize", + "interword_spacing", + "kerning", + "kmeans", + "kuwahara", + "label", + "lat", + "layers", + "level", + "level_colors", + "limit", + "limits", + "linear_stretch", + "linewidth", + "liquid_rescale", + "list", + "loader", + "log", + "loop", + "lowlight_color", + "magnify", + "map", + "mattecolor", + "median", + "mean_shift", + "metric", + "mode", + "modulate", + "moments", + "monitor", + "monochrome", + "morph", + "morphology", + "mosaic", + "motion_blur", + "name", + "negate", + "noise", + "normalize", + "opaque", + "ordered_dither", + "orient", + "page", + "paint", + "pause", + "perceptible", + "ping", + "pointsize", + "polaroid", + "poly", + "posterize", + "precision", + "preview", + "process", + "quality", + "quantize", + "quiet", + "radial_blur", + "raise", + "random_threshold", + "range_threshold", + "red_primary", + "regard_warnings", + "region", + "remote", + "render", + "repage", + "resample", + "resize", + "resize_to_fill", + "resize_to_fit", + "resize_to_limit", + "resize_and_pad", + "respect_parentheses", + "reverse", + "roll", + "rotate", + "sample", + "sampling_factor", + "saver", + "scale", + "scene", + "screen", + "seed", + "segment", + "selective_blur", + "separate", + "sepia_tone", + "shade", + "shadow", + "shared_memory", + "sharpen", + "shave", + "shear", + "sigmoidal_contrast", + "silent", + "similarity_threshold", + "size", + "sketch", + "smush", + "snaps", + "solarize", + "sort_pixels", + "sparse_color", + "splice", + "spread", + "statistic", + "stegano", + "stereo", + "storage_type", + "stretch", + "strip", + "stroke", + "strokewidth", + "style", + "subimage_search", + "swap", + "swirl", + "synchronize", + "taint", + "text_font", + "threshold", + "thumbnail", + "tile_offset", + "tint", + "title", + "transform", + "transparent", + "transparent_color", + "transpose", + "transverse", + "treedepth", + "trim", + "type", + "undercolor", + "unique_colors", + "units", + "unsharp", + "update", + "valid_image", + "view", + "vignette", + "virtual_pixel", + "visual", + "watermark", + "wave", + "wavelet_denoise", + "weight", + "white_balance", + "white_point", + "white_threshold", + "window", + "window_group" + ].concat(ActiveStorage.supported_image_processing_methods) + + UNSUPPORTED_IMAGE_PROCESSING_ARGUMENTS = ActiveStorage.unsupported_image_processing_arguments + def process(file, format:) processor. source(file). @@ -28,6 +322,10 @@ def processor def operations transformations.each_with_object([]) do |(name, argument), list| + if ActiveStorage.variant_processor == :mini_magick + validate_transformation(name, argument) + end + if name.to_s == "combine_options" raise ArgumentError, <<~ERROR.squish Active Storage's ImageProcessing transformer doesn't support :combine_options, @@ -40,6 +338,60 @@ def operations end end end + + def validate_transformation(name, argument) + method_name = name.to_s.gsub("-","_") + + unless SUPPORTED_IMAGE_PROCESSING_METHODS.any? { |method| method_name == method } + raise UnsupportedImageProcessingMethod, <<~ERROR.squish + One or more of the provided transformation methods is not supported. + ERROR + end + + if argument.present? + if argument.is_a?(String) || argument.is_a?(Symbol) + validate_arg_string(argument) + elsif argument.is_a?(Array) + validate_arg_array(argument) + elsif argument.is_a?(Hash) + validate_arg_hash(argument) + end + end + end + + def validate_arg_string(argument) + if UNSUPPORTED_IMAGE_PROCESSING_ARGUMENTS.any? { |bad_arg| argument.to_s.downcase.include?(bad_arg) }; raise UnsupportedImageProcessingArgument end + end + + def validate_arg_array(argument) + argument.each do |arg| + if arg.is_a?(Integer) || arg.is_a?(Float) + next + elsif arg.is_a?(String) || arg.is_a?(Symbol) + validate_arg_string(arg) + elsif arg.is_a?(Array) + validate_arg_array(arg) + elsif arg.is_a?(Hash) + validate_arg_hash(arg) + end + end + end + + def validate_arg_hash(argument) + argument.each do |key, value| + validate_arg_string(key) + + if value.is_a?(Integer) || value.is_a?(Float) + next + elsif value.is_a?(String) || value.is_a?(Symbol) + validate_arg_string(value) + elsif value.is_a?(Array) + validate_arg_array(value) + elsif value.is_a?(Hash) + validate_arg_hash(value) + end + end + end end end end
activestorage/test/models/variant_test.rb+62 −0 modified@@ -211,6 +211,68 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase assert_equal :png, blob.send(:default_variant_format) end + test "variations with dangerous argument string raise UnsupportedImageProcessingArgument" do + process_variants_with :mini_magick do + blob = create_file_blob(filename: "racecar.jpg") + assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do + blob.variant(resize: "-PaTh /tmp/file.erb").processed + end + end + end + + test "variations with dangerous argument array raise UnsupportedImageProcessingArgument" do + process_variants_with :mini_magick do + blob = create_file_blob(filename: "racecar.jpg") + assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do + blob.variant(resize: [123, "-write", "/tmp/file.erb"]).processed + end + end + end + + test "variations with dangerous argument in a nested array raise UnsupportedImageProcessingArgument" do + process_variants_with :mini_magick do + blob = create_file_blob(filename: "racecar.jpg") + assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do + blob.variant(resize: [123, ["-write", "/tmp/file.erb"], "/tmp/file.erb"]).processed + end + + assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do + blob.variant(resize: [123, {"-write /tmp/file.erb": "something"}, "/tmp/file.erb"]).processed + end + end + end + + test "variations with dangerous argument hash raise UnsupportedImageProcessingArgument" do + process_variants_with :mini_magick do + blob = create_file_blob(filename: "racecar.jpg") + assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do + blob.variant(saver: {"-write": "/tmp/file.erb"}).processed + end + end + end + + test "variations with dangerous argument in a nested hash raise UnsupportedImageProcessingArgument" do + process_variants_with :mini_magick do + blob = create_file_blob(filename: "racecar.jpg") + assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do + blob.variant(saver: {"something": {"-write": "/tmp/file.erb"}}).processed + end + + assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do + blob.variant(saver: {"something": ["-write", "/tmp/file.erb"]}).processed + end + end + end + + test "variations with unsupported methods raise UnsupportedImageProcessingMethod" do + process_variants_with :mini_magick do + blob = create_file_blob(filename: "racecar.jpg") + assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingMethod) do + blob.variant(system: "touch /tmp/dangerous").processed + end + end + end + private def process_variants_with(processor) previous_processor, ActiveStorage.variant_processor = ActiveStorage.variant_processor, processor
railties/test/application/configuration_test.rb+29 −0 modified@@ -3098,6 +3098,35 @@ class MyLogger < ::Logger assert_equal :vips, ActiveStorage.variant_processor end + test "ActiveStorage.supported_image_processing_methods can be configured via config.active_storage.supported_image_processing_methods" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/add_image_processing_methods.rb", <<-RUBY + Rails.application.config.active_storage.supported_image_processing_methods = ["write", "set"] + RUBY + + app "development" + + assert ActiveStorage.supported_image_processing_methods.include?("write") + assert ActiveStorage.supported_image_processing_methods.include?("set") + end + + test "ActiveStorage.unsupported_image_processing_arguments can be configured via config.active_storage.unsupported_image_processing_arguments" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/add_image_processing_arguments.rb", <<-RUBY + Rails.application.config.active_storage.unsupported_image_processing_arguments = %w( + -write + -danger + ) + RUBY + + app "development" + + assert ActiveStorage.unsupported_image_processing_arguments.include?("-danger") + refute ActiveStorage.unsupported_image_processing_arguments.include?("-set") + end + test "hosts include .localhost in development" do app "development" assert_includes Rails.application.config.hosts, ".localhost"
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- github.com/advisories/GHSA-w749-p3v6-hccqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-21831ghsaADVISORY
- www.debian.org/security/2023/dsa-5372ghsavendor-advisoryWEB
- github.com/rails/rails/commit/0a72f7d670e9aa77a0bb8584cb1411ddabb7546eghsaWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/activestorage/CVE-2022-21831.ymlghsaWEB
- groups.google.com/g/rubyonrails-security/c/n-p-W1yxatIghsaWEB
- lists.debian.org/debian-lts-announce/2022/09/msg00002.htmlghsamailing-listWEB
- rubysec.com/advisories/CVE-2022-21831ghsaWEB
- security.netapp.com/advisory/ntap-20221118-0001ghsaWEB
- security.netapp.com/advisory/ntap-20221118-0001/mitre
News mentions
0No linked articles in our index yet.