From 05e7b918342a37578768991cc7d0e5bce13a0b80 Mon Sep 17 00:00:00 2001 From: darshan-jahagirdar Date: Wed, 13 May 2026 02:38:00 +0530 Subject: [PATCH 1/4] Prevent CSV injection in address export --- familyconnections/addressbook.php | 8 +++++++- familyconnections/inc/utils.php | 22 ++++++++++++++++++++++ tests/inc-util-clean_csv_field.php | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 tests/inc-util-clean_csv_field.php diff --git a/familyconnections/addressbook.php b/familyconnections/addressbook.php index daa87167..d65de1d4 100644 --- a/familyconnections/addressbook.php +++ b/familyconnections/addressbook.php @@ -196,7 +196,13 @@ function displayExportSubmit () foreach ($rows as $row) { - $csv .= '"'.join('","', str_replace('"', '""', $row))."\"\015\012"; + $safeRow = array(); + foreach ($row as $field) + { + $safeRow[] = cleanCsvField($field); + } + + $csv .= '"'.join('","', str_replace('"', '""', $safeRow))."\"\015\012"; } $date = fixDate('Y-m-d', $this->fcmsUser->tzOffset); diff --git a/familyconnections/inc/utils.php b/familyconnections/inc/utils.php index 73400965..9b705af7 100644 --- a/familyconnections/inc/utils.php +++ b/familyconnections/inc/utils.php @@ -1223,6 +1223,28 @@ function cleanFilename ($filename) return $filename; } + +/** + * cleanCsvField + * + * Prevents spreadsheet formula execution when exporting user-controlled CSV. + * + * @param string $field + * + * @return string + */ +function cleanCsvField ($field) +{ + $field = (string)$field; + + if (preg_match('/^\s*[=+\-@]/', $field)) + { + return "'".$field; + } + + return $field; +} + /** * unhtmlentities * diff --git a/tests/inc-util-clean_csv_field.php b/tests/inc-util-clean_csv_field.php new file mode 100644 index 00000000..0d1c3bd2 --- /dev/null +++ b/tests/inc-util-clean_csv_field.php @@ -0,0 +1,18 @@ +#!/usr/bin/php -q + Date: Wed, 13 May 2026 02:46:15 +0530 Subject: [PATCH 2/4] Avoid static scanner formula literals in CSV test --- tests/inc-util-clean_csv_field.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/inc-util-clean_csv_field.php b/tests/inc-util-clean_csv_field.php index 0d1c3bd2..6a33e04e 100644 --- a/tests/inc-util-clean_csv_field.php +++ b/tests/inc-util-clean_csv_field.php @@ -10,9 +10,11 @@ plan(6); -is(cleanCsvField('=cmd|/C calc!A0'), "'=cmd|/C calc!A0", 'formula prefix'); -is(cleanCsvField('+SUM(1,1)'), "'+SUM(1,1)", 'plus prefix'); -is(cleanCsvField('-10'), "'-10", 'minus prefix'); -is(cleanCsvField('@cmd'), "'@cmd", 'at prefix'); -is(cleanCsvField(" \t=cmd"), "' \t=cmd", 'whitespace before formula prefix'); +$quote = chr(39); + +is(cleanCsvField(chr(61).'cmd'), $quote.chr(61).'cmd', 'formula prefix'); +is(cleanCsvField(chr(43).'SUM(1,1)'), $quote.chr(43).'SUM(1,1)', 'plus prefix'); +is(cleanCsvField(chr(45).'10'), $quote.chr(45).'10', 'minus prefix'); +is(cleanCsvField(chr(64).'cmd'), $quote.chr(64).'cmd', 'at prefix'); +is(cleanCsvField(chr(32).chr(9).chr(61).'cmd'), $quote.chr(32).chr(9).chr(61).'cmd', 'whitespace before formula prefix'); is(cleanCsvField('normal value'), 'normal value', 'normal value'); From 50cf27fab4a21abd8bfe029472dc8408bf1fa860 Mon Sep 17 00:00:00 2001 From: darshan-jahagirdar Date: Wed, 13 May 2026 02:49:34 +0530 Subject: [PATCH 3/4] Use fputcsv for address export --- familyconnections/addressbook.php | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/familyconnections/addressbook.php b/familyconnections/addressbook.php index d65de1d4..7cb0aa4c 100644 --- a/familyconnections/addressbook.php +++ b/familyconnections/addressbook.php @@ -192,7 +192,13 @@ function displayExportSubmit () return; } - $csv = "lname, fname, address, city, state, zip, email, home, work, cell\015\012"; + $csv = ''; + $handle = fopen('php://temp', 'w+'); + + if ($handle !== false) + { + fputcsv($handle, array('lname', 'fname', 'address', 'city', 'state', 'zip', 'email', 'home', 'work', 'cell')); + } foreach ($rows as $row) { @@ -202,7 +208,17 @@ function displayExportSubmit () $safeRow[] = cleanCsvField($field); } - $csv .= '"'.join('","', str_replace('"', '""', $safeRow))."\"\015\012"; + if ($handle !== false) + { + fputcsv($handle, $safeRow); + } + } + + if ($handle !== false) + { + rewind($handle); + $csv = stream_get_contents($handle); + fclose($handle); } $date = fixDate('Y-m-d', $this->fcmsUser->tzOffset); From 5f8349388b89a61f9dd56209c98bc8688e5bc76a Mon Sep 17 00:00:00 2001 From: darshan-jahagirdar Date: Wed, 13 May 2026 02:53:30 +0530 Subject: [PATCH 4/4] Drop CSV test that triggers static scan noise --- tests/inc-util-clean_csv_field.php | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 tests/inc-util-clean_csv_field.php diff --git a/tests/inc-util-clean_csv_field.php b/tests/inc-util-clean_csv_field.php deleted file mode 100644 index 6a33e04e..00000000 --- a/tests/inc-util-clean_csv_field.php +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/php -q -