Skip to content

Commit d947290

Browse files
committed
Security fix for csv injections.
1 parent d2f7532 commit d947290

File tree

3 files changed

+21
-12
lines changed

3 files changed

+21
-12
lines changed

Diff for: WEB-INF/lib/common.lib.php

+9
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,12 @@ function ttRandomString($length = 32) {
459459
}
460460
return $randomString;
461461
}
462+
463+
// ttNeutralizeForCsv neutralizes user input for export to CSV files
464+
// by removing =, +, -, and @ characters from the beginning of cell values.
465+
// This mitigates a risk of CSV injection, see https://owasp.org/www-community/attacks/CSV_Injection
466+
// Additionally, it replaces each quote character with a double quote.
467+
function ttNeutralizeForCsv($val) {
468+
$result = ltrim($val, '=+-@');
469+
return str_replace('"', '""', $result);
470+
}

Diff for: initialize.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
ini_set('display_errors', 'Off');
3838

3939
// require_once('init_auth.php');
40-
define("APP_VERSION", "1.19.23.5324");
40+
define("APP_VERSION", "1.19.23.5325");
4141
define("APP_DIR", dirname(__FILE__));
4242
define("LIBRARY_DIR", APP_DIR."/WEB-INF/lib");
4343
define("TEMPLATE_DIR", APP_DIR."/WEB-INF/templates");

Diff for: tofile.php

+11-11
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@
218218
foreach ($custom_fields->userFields as $userField) {
219219
$field_name = 'user_field_'.$userField['id'];
220220
$checkbox_control_name = 'show_'.$field_name;
221-
if ($bean->getAttribute($checkbox_control_name)) print ',"'.str_replace('"','""',$userField['label']).'"';
221+
if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($userField['label']).'"';
222222
}
223223
}
224224
if ($bean->getAttribute('chclient')) print ',"'.$i18n->get('label.client').'"';
@@ -229,7 +229,7 @@
229229
foreach ($custom_fields->timeFields as $timeField) {
230230
$field_name = 'time_field_'.$timeField['id'];
231231
$checkbox_control_name = 'show_'.$field_name;
232-
if ($bean->getAttribute($checkbox_control_name)) print ',"'.str_replace('"','""',$timeField['label']).'"';
232+
if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($timeField['label']).'"';
233233
}
234234
}
235235
if ($bean->getAttribute('chstart')) print ',"'.$i18n->get('label.start').'"';
@@ -248,24 +248,24 @@
248248
// Print items.
249249
foreach ($items as $item) {
250250
print '"'.$item['date'].'"';
251-
if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) print ',"'.str_replace('"','""',$item['user']).'"';
251+
if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) print ',"'.ttNeutralizeForCsv($item['user']).'"';
252252
// User custom fields.
253253
if ($custom_fields && $custom_fields->userFields) {
254254
foreach ($custom_fields->userFields as $userField) {
255255
$field_name = 'user_field_'.$userField['id'];
256256
$checkbox_control_name = 'show_'.$field_name;
257-
if ($bean->getAttribute($checkbox_control_name)) print ',"'.str_replace('"','""',$item[$field_name]).'"';
257+
if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($item[$field_name]).'"';
258258
}
259259
}
260-
if ($bean->getAttribute('chclient')) print ',"'.str_replace('"','""',$item['client']).'"';
261-
if ($bean->getAttribute('chproject')) print ',"'.str_replace('"','""',$item['project']).'"';
262-
if ($bean->getAttribute('chtask')) print ',"'.str_replace('"','""',$item['task']).'"';
260+
if ($bean->getAttribute('chclient')) print ',"'.ttNeutralizeForCsv($item['client']).'"';
261+
if ($bean->getAttribute('chproject')) print ',"'.ttNeutralizeForCsv($item['project']).'"';
262+
if ($bean->getAttribute('chtask')) print ',"'.ttNeutralizeForCsv($item['task']).'"';
263263
// Time custom fields.
264264
if ($custom_fields && $custom_fields->timeFields) {
265265
foreach ($custom_fields->timeFields as $timeField) {
266266
$field_name = 'time_field_'.$timeField['id'];
267267
$checkbox_control_name = 'show_'.$field_name;
268-
if ($bean->getAttribute($checkbox_control_name)) print ',"'.str_replace('"','""',$item[$field_name]).'"';
268+
if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($item[$field_name]).'"';
269269
}
270270
}
271271
if ($bean->getAttribute('chstart')) print ',"'.$item['start'].'"';
@@ -277,7 +277,7 @@
277277
print ',"'.$val.'"';
278278
}
279279
if ($bean->getAttribute('chunits')) print ',"'.$item['units'].'"';
280-
if ($bean->getAttribute('chnote')) print ',"'.str_replace('"','""',$item['note']).'"';
280+
if ($bean->getAttribute('chnote')) print ',"'.ttNeutralizeForCsv($item['note']).'"';
281281
if ($bean->getAttribute('chcost')) {
282282
if ($user->can('manage_invoices') || $user->isClient())
283283
print ',"'.$item['cost'].'"';
@@ -290,8 +290,8 @@
290290
$ip = $item['modified'] ? $item['modified_ip'].' '.$item['modified'] : $item['created_ip'].' '.$item['created'];
291291
print ',"'.$ip.'"';
292292
}
293-
if ($bean->getAttribute('chinvoice')) print ',"'.str_replace('"','""',$item['invoice']).'"';
294-
if ($bean->getAttribute('chtimesheet')) print ',"'.str_replace('"','""',$item['timesheet_name']).'"';
293+
if ($bean->getAttribute('chinvoice')) print ',"'.ttNeutralizeForCsv($item['invoice']).'"';
294+
if ($bean->getAttribute('chtimesheet')) print ',"'.ttNeutralizeForCsv($item['timesheet_name']).'"';
295295
print "\n";
296296
}
297297
}

0 commit comments

Comments
 (0)