From 588a8635ee78d5195ab6c02ec473c01fa5c86bd3 Mon Sep 17 00:00:00 2001 From: David Cook Date: Thu, 17 Oct 2024 05:35:56 +0000 Subject: [PATCH] Bug 37727: Prevent CSV Formula injection via DataTables This change prevents CSV Formula injection on DataTables exports by escaping formula with a single quote prefix as per OWASP recommendations. Test plan: 0. Apply patch 1. Go to http://localhost:8081/cgi-bin/koha/members/memberentry.pl ?op=edit_form&destination=circ&borrowernumber=51 2. Add the following in a "Circulation note" =SUM(1+1) 3. Go to http://localhost:8081/cgi-bin/koha/members/member.pl ?quicksearch=1&circsearch=1&searchmember=koha 4. Click "Export" and choose "Excel" and "CSV" 5. Open those downloaded files in Excel 6. Note that the =SUM(1+1) function is prefixed with a single quote, and is not automatically executed --- .../prog/en/includes/columns_settings.inc | 21 +++++++++++++++++-- koha-tmpl/intranet-tmpl/prog/js/datatables.js | 21 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc index 9bb0f982ba..b83cb81127 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc @@ -35,6 +35,23 @@ function KohaTable(id_selector, dt_parameters, table_settings, add_filters) { exportColumns = dt_parameters["exportColumns"]; } + const export_format_spreadsheet = { + body: function ( data, row, column, node ) { + var newnode = $(node); + + if ( newnode.find(".noExport").length > 0 ) { + newnode = newnode.clone(); + newnode.find(".noExport").remove(); + } + let trimmed_str = newnode.text().replace( /\n/g, ' ' ).trim(); + const unsafeCharacters = /^[=+\-@\t\r]/; + if ( unsafeCharacters.test(trimmed_str) ){ + trimmed_str = "'" + trimmed_str; + } + return trimmed_str; + } + } + var export_format = { body: function ( data, row, column, node ) { var newnode = $(node); @@ -69,7 +86,7 @@ function KohaTable(id_selector, dt_parameters, table_settings, add_filters) { exportOptions: { columns: exportColumns, rows: exportRows, - format: export_format + format: export_format_spreadsheet }, }, { @@ -100,7 +117,7 @@ function KohaTable(id_selector, dt_parameters, table_settings, add_filters) { exportOptions: { columns: exportColumns, rows: exportRows, - format: export_format + format: export_format_spreadsheet }, } ); diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js index 5f5d81a34f..b86bc6b24e 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js +++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js @@ -670,6 +670,23 @@ function _dt_buttons(params){ exportColumns = settings["exportColumns"]; } + const export_format_spreadsheet = { + body: function ( data, row, column, node ) { + var newnode = $(node); + + if ( newnode.find(".noExport").length > 0 ) { + newnode = newnode.clone(); + newnode.find(".noExport").remove(); + } + let trimmed_str = newnode.text().replace( /\n/g, ' ' ).trim(); + const unsafeCharacters = /^[=+\-@\t\r]/; + if ( unsafeCharacters.test(trimmed_str) ){ + trimmed_str = "'" + trimmed_str; + } + return trimmed_str; + } + } + var export_format = { body: function ( data, row, column, node ) { var newnode = $(node); @@ -688,14 +705,14 @@ function _dt_buttons(params){ extend: 'excelHtml5', exportOptions: { columns: exportColumns, - format: export_format + format: export_format_spreadsheet }, }, { extend: 'csvHtml5', exportOptions: { columns: exportColumns, - format: export_format + format: export_format_spreadsheet }, }, { -- 2.39.2