CVE-2022-24720: Other (NVD-CWE-Other)
value: image_processing is an image processing wrapper for libvips and ImageMagick/GraphicsMagick. Prior to version 1.12.2, using the `#apply` method from image_processing to apply a series of operations that are coming from unsanitized user input allows the attacker to execute shell commands. This method is called internally by Active Storage variants, so Active Storage is vulnerable as well. The vulnerability has been fixed in version 1.12.2 of image_processing. As a workaround, users who process based on user input should always sanitize the user input by allowing only a constrained set of operations.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 9.8
Published: 2022-03-01T23:15Z
Repository: https://github.com/janko/image_processing
Language: Ruby
Files changed: CHANGELOG.md, lib/image_processing/chainable.rb, test/pipeline_test.rb
Commit date: 2022-03-01 22:15:04 +0100
Commit message: Prevent remote shell execution in `#apply`

If the operations are coming from user input, this could allow the user
to execute arbitrary shell commands via `Kernel#system` and
`Kernel#spawn`:

  ImageProcessing::Vips.apply({ system: "echo something" })

We prevent this by using `#public_send` instead of `#send`, which goes
to method missing instead of calling private methods, which include
`Kernel#system` and `Kernel#spawn`.

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad8cb83..8f1807a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
+## HEAD
+
+* Prevent remote shell execution when using `#apply` with operations coming from user input (@janko)
+
 ## 1.12.1 (2020-11-06)
 
 * Fix format fallback for files ending with a dot on Ruby 2.7+ (@coding-chimp)
 
 ## 1.12.0 (2020-09-20)
diff --git a/lib/image_processing/chainable.rb b/lib/image_processing/chainable.rb
index 53040dc..06311de 100644
--- a/lib/image_processing/chainable.rb
+++ b/lib/image_processing/chainable.rb
@@ -32,17 +32,17 @@ module ImageProcessing
     #   # or
     #   .apply([[:resize_to_limit, [400, 400]], [:strip, true])
     def apply(operations)
       operations.inject(self) do |builder, (name, argument)|
         if argument == true || argument == nil
-          builder.send(name)
+          builder.public_send(name)
         elsif argument.is_a?(Array)
-          builder.send(name, *argument)
+          builder.public_send(name, *argument)
         elsif argument.is_a?(Hash)
-          builder.send(name, **argument)
+          builder.public_send(name, **argument)
         else
-          builder.send(name, argument)
+          builder.public_send(name, argument)
         end
       end
     end
 
     # Add an operation defined by the processor.
diff --git a/test/pipeline_test.rb b/test/pipeline_test.rb
index 9c21a45..d3d7162 100644
--- a/test/pipeline_test.rb
+++ b/test/pipeline_test.rb
@@ -256,6 +256,21 @@ describe "ImageProcessing::Pipeline" do
   it "raises a NoMethodError when predicate method is not defined" do
     assert_raises(NoMethodError) do
       ImageProcessing::Vips.valid?(@portrait)
     end
   end
+
+  it "doesn't allow making system calls" do
+    ImageProcessing::Vips.source(@portrait).apply(system: "touch foo.txt")
+    refute File.exist?("foo.txt")
+
+    assert_raises Vips::Error do
+      ImageProcessing::Vips.source(@portrait).spawn("touch foo.txt").call
+    end
+    refute File.exist?("foo.txt")
+
+    assert_raises MiniMagick::Error do
+      ImageProcessing::MiniMagick.source(@portrait).spawn("touch foo.txt").call
+    end
+    refute File.exist?("foo.txt")
+  end
 end

Vulnerable code:           builder.send(name)
          builder.send(name, *argument)
          builder.send(name, **argument)
          builder.send(name, argument)
Fixed code: ## HEAD

* Prevent remote shell execution when using `#apply` with operations coming from user input (@janko)

          builder.public_send(name)
          builder.public_send(name, *argument)
          builder.public_send(name, **argument)
          builder.public_send(name, argument)

  it "doesn't allow making system calls" do
    ImageProcessing::Vips.source(@portrait).apply(system: "touch foo.txt")
    refute File.exist?("foo.txt")

    assert_raises Vips::Error do
      ImageProcessing::Vips.source(@portrait).spawn("touch foo.txt").call
    end
    refute File.exist?("foo.txt")

    assert_raises MiniMagick::Error do
      ImageProcessing::MiniMagick.source(@portrait).spawn("touch foo.txt").call
    end
    refute File.exist?("foo.txt")
  end