CVE-2020-5257: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: In Administrate (rubygem) before version 0.13.0, when sorting by attributes on a dashboard, the direction parameter was not validated before being interpolated into the SQL query. This could present a SQL injection if the attacker were able to modify the `direction` parameter and bypass ActiveRecord SQL protections. Whilst this does have a high-impact, to exploit this you need access to the Administrate dashboards, which we would expect to be behind authentication. This is patched in wersion 0.13.0.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 8.1
Published: 2020-03-13T21:15Z
Repository: https://github.com/thoughtbot/administrate
Language: Ruby
Files changed: lib/administrate/order.rb, spec/lib/administrate/order_spec.rb
Commit date: 2020-03-13 12:15:54 +0000
Commit message: Merge pull request from GHSA-2p5p-m353-833w

Previously, order parameters were passed directly through to the query.
This meant that passing in `foo` via a URL string would try and sort by
`foo`.
Diff: diff --git a/lib/administrate/order.rb b/lib/administrate/order.rb
index 25f9d19..575a8b9 100644
--- a/lib/administrate/order.rb
+++ b/lib/administrate/order.rb
@@ -1,10 +1,10 @@
 module Administrate
   class Order
     def initialize(attribute = nil, direction = nil)
       @attribute = attribute
-      @direction = direction || :asc
+      @direction = sanitize_direction(direction)
     end
 
     def apply(relation)
       return order_by_association(relation) unless
         reflect_association(relation).nil?
@@ -32,20 +32,24 @@ module Administrate
 
     private
 
     attr_reader :attribute
 
+    def sanitize_direction(direction)
+      %w[asc desc].include?(direction.to_s) ? direction.to_sym : :asc
+    end
+
     def reversed_direction_param_for(attr)
       if ordered_by?(attr)
         opposite_direction
       else
         :asc
       end
     end
 
     def opposite_direction
-      direction.to_sym == :asc ? :desc : :asc
+      direction == :asc ? :desc : :asc
     end
 
     def order_by_association(relation)
       return order_by_count(relation) if has_many_attribute?(relation)
 
diff --git a/spec/lib/administrate/order_spec.rb b/spec/lib/administrate/order_spec.rb
index 1522e23..7899926 100644
--- a/spec/lib/administrate/order_spec.rb
+++ b/spec/lib/administrate/order_spec.rb
@@ -49,10 +49,21 @@ describe Administrate::Order do
         ordered = order.apply(relation)
 
         expect(relation).to have_received(:reorder).with("table_name.name desc")
         expect(ordered).to eq(relation)
       end
+
+      it "sanitizes arbitary direction parameters" do
+        order = Administrate::Order.new(:name, :foo)
+        relation = relation_with_column(:name)
+        allow(relation).to receive(:reorder).and_return(relation)
+
+        ordered = order.apply(relation)
+
+        expect(relation).to have_received(:reorder).with("table_name.name asc")
+        expect(ordered).to eq(relation)
+      end
     end
 
     context "when relation has_many association" do
       it "orders the column by count" do
         order = Administrate::Order.new(:name)

Vulnerable code:       @direction = direction || :asc
      direction.to_sym == :asc ? :desc : :asc
Fixed code:       @direction = sanitize_direction(direction)
    def sanitize_direction(direction)
      %w[asc desc].include?(direction.to_s) ? direction.to_sym : :asc
    end

      direction == :asc ? :desc : :asc

      it "sanitizes arbitary direction parameters" do
        order = Administrate::Order.new(:name, :foo)
        relation = relation_with_column(:name)
        allow(relation).to receive(:reorder).and_return(relation)

        ordered = order.apply(relation)

        expect(relation).to have_received(:reorder).with("table_name.name asc")
        expect(ordered).to eq(relation)
      end