CVE-2023-22728: Missing Authorization (CWE-862)
value: Silverstripe Framework is the Model-View-Controller framework that powers the Silverstripe content management system. Prior to version 4.12.15, the GridField print view incorrectly validates the permission of DataObjects potentially allowing a content author to view records they are not authorised to access. Users should upgrade to Silverstripe Framework 4.12.15 or above to address the issue.
CWE description: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
CVSS v3 base score: 4.3
Published: 2023-04-26T14:15Z
Repository: https://github.com/silverstripe/silverstripe-framework
Language: PHP
Files changed: src/Forms/GridField/GridFieldPrintButton.php, tests/php/Forms/GridField/GridFieldPrintButtonTest.php, tests/php/Forms/GridField/GridFieldPrintButtonTest/TestObject.php
Commit date: 2023-04-26 09:45:34 +1200
Commit message: [CVE-2023-22728] Check canView before printing from GridField

Diff: diff --git a/src/Forms/GridField/GridFieldPrintButton.php b/src/Forms/GridField/GridFieldPrintButton.php
index 1ec2be669f..1fc965808e 100644
--- a/src/Forms/GridField/GridFieldPrintButton.php
+++ b/src/Forms/GridField/GridFieldPrintButton.php
@@ -226,25 +226,27 @@ class GridFieldPrintButton extends AbstractGridFieldComponent implements GridFie
         /** @var GridFieldDataColumns $gridFieldColumnsComponent */
         $gridFieldColumnsComponent = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class);
 
         /** @var DataObject $item */
         foreach ($items->limit(null) as $item) {
-            $itemRow = new ArrayList();
+            if (!$item->hasMethod('canView') || $item->canView()) {
+                $itemRow = new ArrayList();
 
-            foreach ($printColumns as $field => $label) {
-                $value = $gridFieldColumnsComponent
-                    ? strip_tags($gridFieldColumnsComponent->getColumnContent($gridField, $item, $field))
-                    : $gridField->getDataFieldValue($item, $field);
+                foreach ($printColumns as $field => $label) {
+                    $value = $gridFieldColumnsComponent
+                        ? strip_tags($gridFieldColumnsComponent->getColumnContent($gridField, $item, $field))
+                        : $gridField->getDataFieldValue($item, $field);
+
+                    $itemRow->push(new ArrayData([
+                        "CellString" => $value,
+                    ]));
+                }
 
-                $itemRow->push(new ArrayData([
-                    "CellString" => $value,
+                $itemRows->push(new ArrayData([
+                    "ItemRow" => $itemRow
                 ]));
             }
-
-            $itemRows->push(new ArrayData([
-                "ItemRow" => $itemRow
-            ]));
             if ($item->hasMethod('destroy')) {
                 $item->destroy();
             }
         }
 
diff --git a/tests/php/Forms/GridField/GridFieldPrintButtonTest.php b/tests/php/Forms/GridField/GridFieldPrintButtonTest.php
index 59e73a494c..f78c305bab 100644
--- a/tests/php/Forms/GridField/GridFieldPrintButtonTest.php
+++ b/tests/php/Forms/GridField/GridFieldPrintButtonTest.php
@@ -30,10 +30,23 @@ class GridFieldPrintButtonTest extends SapphireTest
             $obj->write();
         }
     }
 
     public function testLimit()
+    {
+        $this->assertEquals(42, $this->getTestableRows()->count());
+    }
+
+    public function testCanViewIsRespected()
+    {
+        $orig = TestObject::$canView;
+        TestObject::$canView = false;
+        $this->assertEquals(0, $this->getTestableRows()->count());
+        TestObject::$canView = $orig;
+    }
+
+    private function getTestableRows()
     {
         $list = TestObject::get();
 
         $button = new GridFieldPrintButton();
         $button->setPrintColumns(['Name' => 'My Name']);
@@ -46,9 +59,8 @@ class GridFieldPrintButtonTest extends SapphireTest
         /** @skipUpgrade */
         new Form(Controller::curr(), 'Form', new FieldList($gridField), new FieldList());
 
         // Printed data should ignore pagination limit
         $printData = $button->generatePrintData($gridField);
-        $rows = $printData->ItemRows;
-        $this->assertEquals(42, $rows->count());
+        return $printData->ItemRows;
     }
 }
diff --git a/tests/php/Forms/GridField/GridFieldPrintButtonTest/TestObject.php b/tests/php/Forms/GridField/GridFieldPrintButtonTest/TestObject.php
index 2e8f80cc9d..dd5638d092 100644
--- a/tests/php/Forms/GridField/GridFieldPrintButtonTest/TestObject.php
+++ b/tests/php/Forms/GridField/GridFieldPrintButtonTest/TestObject.php
@@ -10,6 +10,13 @@ class TestObject extends DataObject implements TestOnly
     private static $table_name = 'GridFieldPrintButtonTest_Object';
 
     private static $db = [
         'Name' => 'Varchar'
     ];
+
+    public static bool $canView = true;
+
+    public function canView($member = null)
+    {
+        return static::$canView;
+    }
 }

Vulnerable code:             $itemRow = new ArrayList();
            foreach ($printColumns as $field => $label) {
                $value = $gridFieldColumnsComponent
                    ? strip_tags($gridFieldColumnsComponent->getColumnContent($gridField, $item, $field))
                    : $gridField->getDataFieldValue($item, $field);
                $itemRow->push(new ArrayData([
                    "CellString" => $value,

            $itemRows->push(new ArrayData([
                "ItemRow" => $itemRow
            ]));
        $rows = $printData->ItemRows;
        $this->assertEquals(42, $rows->count());
Fixed code:             if (!$item->hasMethod('canView') || $item->canView()) {
                $itemRow = new ArrayList();
                foreach ($printColumns as $field => $label) {
                    $value = $gridFieldColumnsComponent
                        ? strip_tags($gridFieldColumnsComponent->getColumnContent($gridField, $item, $field))
                        : $gridField->getDataFieldValue($item, $field);

                    $itemRow->push(new ArrayData([
                        "CellString" => $value,
                    ]));
                }
                $itemRows->push(new ArrayData([
                    "ItemRow" => $itemRow
    {
        $this->assertEquals(42, $this->getTestableRows()->count());
    }

    public function testCanViewIsRespected()
    {
        $orig = TestObject::$canView;
        TestObject::$canView = false;
        $this->assertEquals(0, $this->getTestableRows()->count());
        TestObject::$canView = $orig;
    }

    private function getTestableRows()
        return $printData->ItemRows;

    public static bool $canView = true;

    public function canView($member = null)
    {
        return static::$canView;
    }