From 20142ac771c48cfa48246b9d5be84d8971d97950 Mon Sep 17 00:00:00 2001 From: Tomas Cohen Arazi Date: Fri, 1 Aug 2025 12:03:17 -0300 Subject: [PATCH] Bug 40579: Fix CSV formula injection vulnerabilities This patch addresses CSV formula injection vulnerabilities by ensuring all Text::CSV instances use formula protection. Changes made: * Add formula => 'empty' to all Text::CSV constructors in: - Koha::Patrons::Import - Koha::BackgroundJob::ImportKBARTFile - Koha::ERM::EUsage::CounterFile The 'empty' formula mode causes potentially dangerous formulas (starting with =, +, -, @) to be treated as empty strings rather than executed, preventing CSV injection attacks while maintaining data integrity. Security impact: * Prevents execution of malicious formulas in CSV imports * Protects against data exfiltration via CSV formula injection * Maintains backward compatibility for legitimate CSV data To test: 1. Apply the previous patch (author test) 2. Run: $ ktd --shell k$ prove xt/author/Text_CSV_Various.t => FAIL: Should show 3 files without formula protection 3. Apply this patch 4. Repeat 2 => SUCCESS: All tests should now pass 5. Try importing CSV with formula content (=cmd|"/c calc",test,data) => SUCCESS: Formula content is rejected/emptied, not executed 6. Import normal CSV data => SUCCESS: Normal data imports correctly 7. Sign off :-D --- Koha/BackgroundJob/ImportKBARTFile.pm | 3 ++- Koha/ERM/EUsage/CounterFile.pm | 6 +++--- Koha/Patrons/Import.pm | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Koha/BackgroundJob/ImportKBARTFile.pm b/Koha/BackgroundJob/ImportKBARTFile.pm index eb68fe7a6ce..080e99b7a3c 100644 --- a/Koha/BackgroundJob/ImportKBARTFile.pm +++ b/Koha/BackgroundJob/ImportKBARTFile.pm @@ -235,7 +235,8 @@ sub read_file { sep_char => $delimiter, quote_char => $quote_char, binary => 1, - allow_loose_quotes => 1 + allow_loose_quotes => 1, + formula => 'empty' } ); my $headers_to_check = $csv->getline($fh); diff --git a/Koha/ERM/EUsage/CounterFile.pm b/Koha/ERM/EUsage/CounterFile.pm index 2d5d80f15cd..95c6180f2ae 100644 --- a/Koha/ERM/EUsage/CounterFile.pm +++ b/Koha/ERM/EUsage/CounterFile.pm @@ -304,7 +304,7 @@ sub validate { my ($self) = @_; open my $fh, "<", \$self->file_content or die; - my $csv = Text::CSV_XS->new( { binary => 1, always_quote => 1, eol => $/, decode_utf8 => 1 } ); + my $csv = Text::CSV_XS->new( { binary => 1, always_quote => 1, eol => $/, decode_utf8 => 1, formula => 'empty' } ); $csv->column_names(qw( header_key header_value )); my @header_rows = $csv->getline_hr_all( $fh, 0, 12 ); @@ -331,7 +331,7 @@ sub _set_report_type_from_file { my ($self) = @_; open my $fh, "<", \$self->file_content or die; - my $csv = Text::CSV_XS->new( { binary => 1, always_quote => 1, eol => $/, decode_utf8 => 1 } ); + my $csv = Text::CSV_XS->new( { binary => 1, always_quote => 1, eol => $/, decode_utf8 => 1, formula => 'empty' } ); $csv->column_names(qw( header_key header_value )); my @header_rows = $csv->getline_hr_all( $fh, 0, 12 ); @@ -354,7 +354,7 @@ sub _get_rows_from_COUNTER_file { my ($self) = @_; open my $fh, "<", \$self->file_content or die; - my $csv = Text::CSV_XS->new( { binary => 1, always_quote => 1, eol => $/, decode_utf8 => 1 } ); + my $csv = Text::CSV_XS->new( { binary => 1, always_quote => 1, eol => $/, decode_utf8 => 1, formula => 'empty' } ); my $header_columns = $csv->getline_all( $fh, 13, 1 ); $csv->column_names( @{$header_columns}[0] ); diff --git a/Koha/Patrons/Import.pm b/Koha/Patrons/Import.pm index f36495d76ac..3e422339490 100644 --- a/Koha/Patrons/Import.pm +++ b/Koha/Patrons/Import.pm @@ -65,7 +65,7 @@ has 'today_iso' => ( has 'text_csv' => ( is => 'rw', lazy => 1, - default => sub { Text::CSV->new( { binary => 1, } ); }, + default => sub { Text::CSV->new( { binary => 1, formula => 'empty' } ); }, ); sub import_patrons { -- 2.50.1