Skip to content

Commit

Permalink
security: CSV Formula Injection
Browse files Browse the repository at this point in the history
This addresses a security issue discovered by Aishwarya Iyer where a User
can change their Full Name to a windows formula and when an Agent exports a
list of Users containing said User and opens the export file, the formula
will be executed on their computer (if it's windows of course). This adds a
new validator called `is_formula()` to all text fields disallowing the use
of the following characters `= + - @` at the beginning of text. This should
mitigate CSV Formula injections for any text field that allows user-input in
the system. To further prevent CSV Formula injections this adds an escape
mechanism to the Exporter that will escape any content matching the formula
regex with a single quote (as mentioned in many posts about this subject).
  • Loading branch information
JediKev committed Jul 11, 2019
1 parent bbfff1a commit 9981848
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 3 deletions.
8 changes: 7 additions & 1 deletion include/class.export.php
Expand Up @@ -356,7 +356,13 @@ function dump() {
fputs($this->output, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($this->output, $this->getHeaders(), $delimiter);
while ($row=$this->next())
fputcsv($this->output, $row, $delimiter);
fputcsv($this->output, array_map(
function($v){
if (preg_match('/^[=\-+@].*/', $v))
return "'".$v;
return $v;
}, $row),
$delimiter);

fclose($this->output);
}
Expand Down
26 changes: 25 additions & 1 deletion include/class.forms.php
Expand Up @@ -1300,7 +1300,8 @@ function validateEntry($value) {
parent::validateEntry($value);
$config = $this->getConfiguration();
$validators = array(
'' => null,
'' => array(array('Validator', 'is_formula'),
__('Content cannot start with the following characters: = - + @')),
'email' => array(array('Validator', 'is_valid_email'),
__('Enter a valid email address')),
'phone' => array(array('Validator', 'is_phone'),
Expand Down Expand Up @@ -1379,6 +1380,29 @@ function getConfigurationOptions() {
);
}

function validateEntry($value) {
parent::validateEntry($value);
if (!$value)
return;
$config = $this->getConfiguration();
$validators = array(
'' => array(array('Validator', 'is_formula'),
__('Content cannot start with the following characters: = - + @')),
);
// Support configuration forms, as well as GUI-based form fields
if (!($valid = $this->get('validator')) && isset($config['validator']))
$valid = $config['validator'];
if (!isset($validators[$valid]))
return;
$func = $validators[$valid];
$error = $func[1];
if ($config['validator-error'])
$error = $this->getLocal('validator-error', $config['validator-error']);
if (is_array($func) && is_callable($func[0]))
if (!call_user_func($func[0], $value))
$this->_errors[] = $error;
}

function hasSpecialSearch() {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion include/class.user.php
Expand Up @@ -248,7 +248,7 @@ static function fromForm($form, $create=true) {
//Validate the form
$valid = true;
$filter = function($f) use ($thisstaff) {
return !isset($thisstaff) || $f->isRequiredForStaff();
return !isset($thisstaff) || $f->isRequiredForStaff() || $f->isVisibleToStaff();
};
if (!$form->isValid($filter))
$valid = false;
Expand Down
5 changes: 5 additions & 0 deletions include/class.validator.php
Expand Up @@ -204,6 +204,11 @@ static function is_username($username, &$error='') {
return $error == '';
}

static function is_formula($text, &$error='') {
if (!preg_match('/^[^=\+@-].*$/', $text))
$error = __('Content cannot start with the following characters: = - + @');
return $error == '';
}

/*
* check_ip
Expand Down

0 comments on commit 9981848

Please sign in to comment.