opensourcepos / opensourcepos

Open Source Point of Sale is a web based point of sale application written in PHP using CodeIgniter framework. It uses MySQL as the data back end and has a Bootstrap 3 based user interface.

Home Page:http://www.opensourcepos.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Escaping output in CI4

objecttothis opened this issue · comments

Type of Feature

✨ New Feature

OpensourcePOS Version

opensourcepos 3.4.0-dev

Description

We've talked from time to time about escaping output in HTML and forms to prevent XSS attacks. The recommended best practice I am seeing in my research is using CodeIgniter's esc() function with it's context 2nd parameter to get context-specific escaping. The problem is that in HTML forms (and other contexts) this leaves text like Me & You displaying as Me & You or O'Malley displaying as O'Malley. AI recommends wrapping the esc() in html_entity_decode(), but in an HTML context this just undoes the work that esc() just did and the form is vulnerable to XSS where HTML is injected.

The best solution I can think of would be to use esc() but wrap it in a custom function that we place in security_helper.php which takes in a string and array of safe characters to decode. Something like:

function html_limited_decode(string $original, array $safe_characters)
{
    $search = esc($safe_characters);
    $replace = $safe_characters;
    return str_replace($search, $replace, $original);
}

// Usage
$value = custom_decode(esc($person_info->first_name), ['\'']);

This still mitigates many XSS attacks, but allows characters we want. For example O'Malley gets displayed properly, but <input type='text' name='username' value=' ' onmouseover='alert(document.cookie)' > gets converted to &lt;Input Type='Text' Name='Username' Value=' ' Onmouseover='Alert(Document.Cookie)' &gt; and doesn't run.

If we do this, it will be important to only include the characters which absolutely must display properly. So for example in $person_info->zip we should just call esc() against it since they shouldn't be including any HTML entities so there shouldn't be anything to decode. IMO, using this method along with proper validation of fields both gives us properly displaying data in our forms and is far more secure than not escaping outputs.

Filtering inputs should still be done to prevent XSS and SQL injection attacks on data coming into OSPOS. esc() can't do anything about that. Escaping outputs is about preventing what is already in the database from being interpreted by the browser and executed.

For example, if we do no escaping and only filter inputs a field submitted with Nice post! <img src='x' onerror='alert("XSS Attack!")'> may pass filtering unaltered since the img tag by itself may be harmless. Without escaping, this code may be executed. With escaping, the output that passed unscathed becomes Nice post! &lt;img src=&#039;x&#039; onerror=&#039;alert("XSS Attack!")&#039;&gt;

@jekkos what do you think about this idea?

Additional Information

No response

Verify you searched open requests in OpensourcePOS

  • I agree I have searched Open Requests

I'm pushing a proof of concept to the ci4-branch. Take a look at the form_basic_info.php and how it interacts with the Customer info view.

The idea is that we have a modified esc() function called esc_safe() which checks to see if the string is already encoded and only runs esc() against it if it's not already encoded. Then the result of that is wrapped in a modified version of html_entity_decode() called html_limited_decode() which takes a string and an array of safe characters. It then only decodes html entities for those characters. Take this example:

<div class="form-group form-group-sm">
	<?= form_label(lang('Common.first_name'), 'first_name', ['class' => 'required control-label col-xs-3']) ?>
	<div class='col-xs-8'>
		<?= form_input ([
			'name' => 'first_name',
			'id' => 'first_name',
			'class' => 'form-control input-sm',
			'value' => html_limited_decode(esc_safe($person_info->first_name), ['\''])
		]) ?>
	</div>
</div>

Since the only html entity character we want to not be encoded is the single quote, that's all that appears in safe characters.

CI is just using laminas/laminas-escaper and in the html context laminas-escaper is just calling htmlspecialchars() which has the optional bool $double_encode = true parameter. I submitted a PR to laminas laminas/laminas-escaper#54

It may take awhile to get it into CI4

in the meantime the esc_safe() function needs a little more work because currently it's escaping everything when just one character is able to be escaped instead of a true not double-encoding... I think until laminas and ci get my PR into the code, we may need to skip esc() all together and just call htmlspecialchars directly. This is only acceptable in the html context.

Nevermind. esc() does not need to be called inside any of the functions in the form helper https://codeigniter.com/user_guide/helpers/form_helper.html