mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-03-26 10:54:40 -04:00
* Fix stored XSS vulnerability in Attribute Definitions GHSA-rvfg-ww4r-rwqf: Stored XSS via Attribute Definition Name Security Impact: - Authenticated users with attribute management permission can inject XSS payloads - Payloads execute when viewing/editing attributes in admin panel - Can steal session cookies, perform CSRF attacks, or compromise admin operations Root Cause: 1. Input: Attributes.php postSaveDefinition() accepts definition_name without sanitization 2. Output: Views echo definition_name without proper escaping Fix Applied: - Input sanitization: Added FILTER_SANITIZE_FULL_SPECIAL_CHARS to definition_name and definition_unit - Output escaping: Added esc() wrapper when displaying definition_name in views - Defense-in-depth: htmlspecialchars on attribute values saved to database Files Changed: - app/Controllers/Attributes.php - Sanitize inputs on save - app/Views/attributes/form.php - Escape output on display - app/Views/attributes/item.php - Escape output on display * Remove input sanitization, keep output escaping only Use escaping on output (esc() in views) as the sole XSS prevention measure instead of sanitizing on input. This preserves the original data in the database while still protecting against XSS attacks. * Add validation for definition_fk foreign key in attribute definitions Validate definition_group input before saving: - Must be a positive integer (> 0) - Must exist in attribute_definitions table - Must be of type GROUP to ensure data integrity Also add translation for definition_invalid_group error message in all 45 language files (English placeholder for translations). * Refactor definition_fk validation into single conditional statement * Add esc() to attribute value outputs for XSS protection - Add esc() to TEXT input value in item.php - Add esc() to definition_unit in form.php These fields display user-provided content and need output escaping to prevent stored XSS attacks. * Refactor definition_group validation into separate method Extract validation logic for definition_fk into validateDefinitionGroup() private method to improve code readability and reduce method complexity. Returns: - null if input is empty (no group selected) - false if validation fails (invalid group) - integer ID if valid * Add translations for definition_invalid_group in all languages - Added proper translations for 28 languages (de, es, fr, it, nl, pl, pt-BR, ru, tr, uk, th, zh-Hans, zh-Hant, ro, sv, vi, id, el, he, fa, hu, da, sw-KE, sw-TZ, ar-LB, ar-EG) - Set empty string for 14 languages to fallback to English (cs, hr-HR, bg, bs, ckb, hy, km, lo, ml, nb, ta, tl, ur, az) --------- Co-authored-by: Ollama <ollama@steganos.dev>
168 lines
7.6 KiB
PHP
168 lines
7.6 KiB
PHP
<?php
|
|
/**
|
|
* @var array $definition_names
|
|
* @var array $definition_values
|
|
* @var int $item_id
|
|
* @var array $config
|
|
*/
|
|
?>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(lang('Attributes.definition_name'), 'definition_name_label', ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-8">
|
|
<?= form_dropdown([
|
|
'name' => 'definition_name',
|
|
'options' => $definition_names,
|
|
'selected' => -1,
|
|
'class' => 'form-control',
|
|
'id' => 'definition_name'
|
|
]) ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php foreach ($definition_values as $definition_id => $definition_value) { ?>
|
|
|
|
<div class="form-group form-group-sm">
|
|
<?= form_label(esc($definition_value['definition_name']), esc($definition_value['definition_name']), ['class' => 'control-label col-xs-3']) ?>
|
|
<div class="col-xs-8">
|
|
<div class="input-group">
|
|
<?php
|
|
echo form_hidden("attribute_ids[$definition_id]", strval($definition_value['attribute_id']));
|
|
$attribute_value = $definition_value['attribute_value'];
|
|
|
|
switch ($definition_value['definition_type']) {
|
|
case DATE:
|
|
$value = (empty($attribute_value) || empty($attribute_value->attribute_date)) ? NOW : strtotime($attribute_value->attribute_date);
|
|
echo form_input([
|
|
'name' => "attribute_links[$definition_id]",
|
|
'value' => to_date($value),
|
|
'class' => 'form-control input-sm datetime',
|
|
'data-definition-id' => $definition_id,
|
|
'readonly' => 'true'
|
|
]);
|
|
break;
|
|
case DROPDOWN:
|
|
$selected_value = $definition_value['selected_value'];
|
|
echo form_dropdown([
|
|
'name' => "attribute_links[$definition_id]",
|
|
'options' => $definition_value['values'],
|
|
'selected' => $selected_value,
|
|
'class' => 'form-control',
|
|
'data-definition-id' => $definition_id
|
|
]);
|
|
break;
|
|
case TEXT:
|
|
$value = (empty($attribute_value) || empty($attribute_value->attribute_value)) ? $definition_value['selected_value'] : $attribute_value->attribute_value;
|
|
echo form_input([
|
|
'name' => "attribute_links[$definition_id]",
|
|
'value' => esc($value),
|
|
'class' => 'form-control valid_chars',
|
|
'data-definition-id' => $definition_id
|
|
]);
|
|
break;
|
|
case DECIMAL:
|
|
$value = (empty($attribute_value) || empty($attribute_value->attribute_decimal)) ? $definition_value['selected_value'] : $attribute_value->attribute_decimal;
|
|
echo form_input([
|
|
'name' => "attribute_links[$definition_id]",
|
|
'value' => to_decimals((float)$value),
|
|
'class' => 'form-control valid_chars',
|
|
'data-definition-id' => $definition_id
|
|
]);
|
|
break;
|
|
case CHECKBOX:
|
|
$value = (empty($attribute_value) || empty($attribute_value->attribute_value)) ? $definition_value['selected_value'] : $attribute_value->attribute_value;
|
|
|
|
// Sends 0 if the box is unchecked instead of not sending anything.
|
|
echo form_input([
|
|
'type' => 'hidden',
|
|
'name' => "attribute_links[$definition_id]",
|
|
'id' => "attribute_links[$definition_id]",
|
|
'value' => 0,
|
|
'data-definition-id' => $definition_id
|
|
]);
|
|
echo form_checkbox([
|
|
'name' => "attribute_links[$definition_id]",
|
|
'id' => "attribute_links[$definition_id]",
|
|
'value' => 1,
|
|
'checked' => $value == 1,
|
|
'class' => 'checkbox-inline',
|
|
'data-definition-id' => $definition_id
|
|
]);
|
|
break;
|
|
}
|
|
?>
|
|
<span class="input-group-addon input-sm btn btn-default remove_attribute_btn">
|
|
<span class="glyphicon glyphicon-trash"></span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php } ?>
|
|
|
|
<script type="text/javascript">
|
|
(function() {
|
|
<?= view('partial/datepicker_locale', ['format' => dateformat_bootstrap($config['dateformat'])]) ?>
|
|
|
|
var enable_delete = function() {
|
|
$('.remove_attribute_btn').click(function() {
|
|
$(this).parents('.form-group').remove();
|
|
});
|
|
};
|
|
|
|
enable_delete();
|
|
|
|
$("input[name*='attribute_links']").change(function() {
|
|
var definition_id = $(this).data('definition-id');
|
|
$("input[name='attribute_ids[" + definition_id + "]']").val('');
|
|
}).autocomplete({
|
|
source: function(request, response) {
|
|
$.get('<?= 'attributes/suggestAttribute/' ?>' + this.element.data('definition-id') + '?term=' + request.term, function(data) {
|
|
return response(data);
|
|
}, 'json');
|
|
},
|
|
appendTo: '.modal-content',
|
|
select: function(event, ui) {
|
|
event.preventDefault();
|
|
$(this).val(ui.item.label);
|
|
},
|
|
delay: 10
|
|
});
|
|
|
|
var definition_values = function() {
|
|
var result = {};
|
|
$("[name*='attribute_links'").each(function() {
|
|
var definition_id = $(this).data('definition-id');
|
|
var element = $(this);
|
|
|
|
// For checkboxes, use the visible checkbox, not the hidden input
|
|
if (element.attr('type') === 'hidden' && element.siblings('input[type="checkbox"]').length > 0) {
|
|
// Skip hidden inputs that have a corresponding checkbox
|
|
return;
|
|
}
|
|
|
|
// For checkboxes, get the checked state
|
|
if (element.attr('type') === 'checkbox') {
|
|
result[definition_id] = element.prop('checked') ? '1' : '0';
|
|
} else {
|
|
result[definition_id] = element.val();
|
|
}
|
|
});
|
|
return result;
|
|
};
|
|
|
|
var refresh = function() {
|
|
var definition_id = $("#definition_name option:selected").val();
|
|
var attribute_values = definition_values();
|
|
attribute_values[definition_id] = '';
|
|
$('#attributes').load('<?= "items/attributes/$item_id" ?>', {
|
|
'definition_ids': JSON.stringify(attribute_values)
|
|
}, enable_delete);
|
|
};
|
|
|
|
$('#definition_name').change(function() {
|
|
refresh();
|
|
});
|
|
})();
|
|
</script>
|