CVE-2023-4006: Improper Neutralization of Formula Elements in a CSV File (CWE-1236)
value: Improper Neutralization of Formula Elements in a CSV File in GitHub repository thorsten/phpmyfaq prior to 3.1.16.
CWE description: The product saves user-provided information into a Comma-Separated Value (CSV) file, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as a command when the file is opened by a spreadsheet product.
CVSS v3 base score: 9.8
Published: 2023-07-31T01:15Z
Repository: https://github.com/thorsten/phpmyfaq
Language: PHP
Files changed: phpmyfaq/admin/report.export.php, phpmyfaq/src/phpMyFAQ/Report.php, tests/phpMyFAQ/ReportTest.php
Commit date: 2023-07-01 10:10:47 +0200
Commit message: fix: added sanitizer for CSV

Diff: diff --git a/phpmyfaq/admin/report.export.php b/phpmyfaq/admin/report.export.php
index 7a56bbcf2ef..95b7602e5c6 100644
--- a/phpmyfaq/admin/report.export.php
+++ b/phpmyfaq/admin/report.export.php
@@ -114,11 +114,12 @@ if ($user->perm->hasPermission($user->getUserId(), 'reports')) {
         }
     }
 
     $content = '';
     foreach ($text as $row) {
-        $content .= implode(';', $row);
+        $csvRow = array_map(['phpMyFAQ\Report', 'sanitize'], $row);
+        $content .= implode(';', $csvRow);
         $content .= "\r\n";
     }
 
     $oHttpStreamer = new HttpStreamer('csv', $content);
     $oHttpStreamer->send(HttpStreamer::HTTP_CONTENT_DISPOSITION_ATTACHMENT);
diff --git a/phpmyfaq/src/phpMyFAQ/Report.php b/phpmyfaq/src/phpMyFAQ/Report.php
index 7b2df0823a0..f779fce91fe 100644
--- a/phpmyfaq/src/phpMyFAQ/Report.php
+++ b/phpmyfaq/src/phpMyFAQ/Report.php
@@ -26,11 +26,11 @@ namespace phpMyFAQ;
 class Report
 {
     /**
      * @var Configuration
      */
-    private $config;
+    private Configuration $config;
 
     /**
      * Constructor.
      *
      * @param Configuration $config
@@ -145,6 +145,20 @@ class Report
         }
 
         $toBeRemoved = ['=', '+', '-', 'HYPERLINK'];
         return str_replace($toBeRemoved, '', $outputString);
     }
+
+    /**
+     * Sanitizes input to avoid CSV injection.
+     * @param string|int $value
+     * @return string
+     */
+    public static function sanitize($value): string
+    {
+        if (preg_match('/[=\+\-\@\|]/', $value)) {
+            $value = '"' . str_replace('"', '""', $value) . '"';
+        }
+
+        return $value;
+    }
 }
diff --git a/tests/phpMyFAQ/ReportTest.php b/tests/phpMyFAQ/ReportTest.php
new file mode 100644
index 00000000000..313c327943d
--- /dev/null
+++ b/tests/phpMyFAQ/ReportTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace phpMyFAQ;
+
+use PHPUnit\Framework\TestCase;
+
+class ReportTest extends TestCase
+{
+
+    public function testSanitize(): void
+    {
+        $data = [
+            ['John Doe', 'john.doe@example.com', '12345'],
+            ['Jane Smith', 'jane.smith@example.com', '=SUM(A1:A10)'],
+        ];
+
+        $actual = [];
+
+        $expected = [
+            'John Doe,"john.doe@example.com",12345',
+            'Jane Smith,"jane.smith@example.com","=SUM(A1:A10)"'
+        ];
+
+        foreach ($data as $row) {
+            $csvRow = array_map(['phpMyFAQ\Report', 'sanitize'], $row);
+            $actual[] = implode(',', $csvRow);
+        }
+
+        $this->assertEquals($expected, $actual);
+    }
+}

Vulnerable code:         $content .= implode(';', $row);
    private $config;
Fixed code:         $csvRow = array_map(['phpMyFAQ\Report', 'sanitize'], $row);
        $content .= implode(';', $csvRow);
    private Configuration $config;

    /**
     * Sanitizes input to avoid CSV injection.
     * @param string|int $value
     * @return string
     */
    public static function sanitize($value): string
    {
        if (preg_match('/[=\+\-\@\|]/', $value)) {
            $value = '"' . str_replace('"', '""', $value) . '"';
        }

        return $value;
    }
<?php

namespace phpMyFAQ;

use PHPUnit\Framework\TestCase;

class ReportTest extends TestCase
{

    public function testSanitize(): void
    {
        $data = [
            ['John Doe', 'john.doe@example.com', '12345'],
            ['Jane Smith', 'jane.smith@example.com', '=SUM(A1:A10)'],
        ];

        $actual = [];

        $expected = [
            'John Doe,"john.doe@example.com",12345',
            'Jane Smith,"jane.smith@example.com","=SUM(A1:A10)"'
        ];

        foreach ($data as $row) {
            $csvRow = array_map(['phpMyFAQ\Report', 'sanitize'], $row);
            $actual[] = implode(',', $csvRow);
        }

        $this->assertEquals($expected, $actual);
    }
}