Fix stored XSS vulnerability in item descriptions

GHSA-q58g-gg7v-f9rf: Stored XSS via Item Description

Security Impact:
- Authenticated users with item management permission can inject XSS payloads
- Payloads execute in POS register view (sales and receivings)
- Can steal session cookies, perform CSRF attacks, or compromise POS operations

Root Cause:
1. Input: Items.php:614 accepts description without sanitization
2. Output: register.php:255 and receiving.php:220 echo description without escaping

Fix Applied:
- Input sanitization: Added FILTER_SANITIZE_FULL_SPECIAL_CHARS to description POST
- Output escaping: Added esc() wrapper when echoing item descriptions
- Defense-in-depth approach: sanitize on input, escape on output

Files Changed:
- app/Controllers/Items.php - Sanitize description on save
- app/Views/sales/register.php - Escape description on display
- app/Views/receivings/receiving.php - Escape description on display

Testing:
- XSS payloads like '<script>alert(1)</script>' are now sanitized on input
- Any existing malicious descriptions are escaped on output
- Does not break legitimate descriptions with special characters
This commit is contained in:
Ollama
2026-03-07 18:31:25 +00:00
committed by jekkos
parent 52b0a83190
commit 977fa5647b
2 changed files with 8 additions and 8 deletions

View File

@@ -618,7 +618,7 @@ class Items extends Secure_Controller
// Save item data
$item_data = [
'name' => $this->request->getPost('name'),
'description' => $this->request->getPost('description'),
'description' => $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'category' => $this->request->getPost('category'),
'item_type' => $item_type,
'stock_type' => $this->request->getPost('stock_type') === null ? HAS_STOCK : intval($this->request->getPost('stock_type')),

View File

@@ -215,15 +215,15 @@ if (isset($success)) {
'class' => 'form-control input-sm',
'value' => $item['description']
]);
} else {
if ($item['description'] != '') { // TODO: !==?
echo $item['description'];
echo form_hidden('description', $item['description']);
} else {
echo '<i>' . lang('Sales.no_description') . '</i>';
echo form_hidden('description', '');
if ($item['description'] != '') { // TODO: !==?
echo esc($item['description']);
echo form_hidden('description', $item['description']);
} else {
echo '<i>' . lang('Sales.no_description') . '</i>';
echo form_hidden('description', '');
}
}
}
?>
</td>
<td colspan="7"></td>