Security Fix in recurring invoices in cron

This commit is contained in:
johnnyq
2026-07-27 18:07:15 -04:00
parent a844d7b428
commit 97490bef3b
3 changed files with 19 additions and 5 deletions

View File

@@ -13,7 +13,7 @@ if (isset($_POST['add_invoice_recurring'])) {
enforceUserPermission('module_sales', 2);
$invoice_id = intval($_POST['invoice_id']);
$recurring_invoice_frequency = ($_POST['frequency'] === 'year') ? 'year' : 'month';
$recurring_invoice_frequency = validateRecurringFrequency($_POST['frequency']);
$sql = mysqli_query($mysqli,"SELECT * FROM invoices WHERE invoice_id = $invoice_id");
$row = mysqli_fetch_assoc($sql);
@@ -77,7 +77,7 @@ if (isset($_POST['add_recurring_invoice'])) {
enforceUserPermission('module_sales', 2);
$client_id = intval($_POST['client_id']);
$frequency = escapeSql($_POST['frequency']);
$frequency = validateRecurringFrequency($_POST['frequency']);
$start_date = escapeSql($_POST['start_date']);
$category = intval($_POST['category']);
$scope = escapeSql($_POST['scope']);
@@ -116,7 +116,7 @@ if (isset($_POST['edit_recurring_invoice'])) {
enforceUserPermission('module_sales', 2);
$recurring_invoice_id = intval($_POST['recurring_invoice_id']);
$frequency = escapeSql($_POST['frequency']);
$frequency = validateRecurringFrequency($_POST['frequency']);
$next_date = escapeSql($_POST['next_date']);
$category = intval($_POST['category']);
$scope = escapeSql($_POST['scope']);
@@ -394,7 +394,7 @@ if (isset($_GET['force_recurring'])) {
$row = mysqli_fetch_assoc($sql_recurring_invoices);
$recurring_invoice_id = intval($row['recurring_invoice_id']);
$recurring_invoice_scope = escapeSql($row['recurring_invoice_scope']);
$recurring_invoice_frequency = ($_POST['frequency'] === 'year') ? 'year' : 'month';
$recurring_invoice_frequency = validateRecurringFrequency($row['recurring_invoice_frequency']);
$recurring_invoice_status = escapeSql($row['recurring_invoice_status']);
$recurring_invoice_last_sent = escapeSql($row['recurring_invoice_last_sent']);
$recurring_invoice_next_date = escapeSql($row['recurring_invoice_next_date']);

View File

@@ -615,7 +615,7 @@ $sql_recurring_invoices = mysqli_query($mysqli, "SELECT * FROM recurring_invoice
while ($row = mysqli_fetch_assoc($sql_recurring_invoices)) {
$recurring_invoice_id = intval($row['recurring_invoice_id']);
$recurring_invoice_scope = escapeSql($row['recurring_invoice_scope']);
$recurring_invoice_frequency = escapeSql($row['recurring_invoice_frequency']);
$recurring_invoice_frequency = validateRecurringFrequency($row['recurring_invoice_frequency']);
$recurring_invoice_status = escapeSql($row['recurring_invoice_status']);
$recurring_invoice_last_sent = escapeSql($row['recurring_invoice_last_sent']);
$recurring_invoice_next_date = escapeSql($row['recurring_invoice_next_date']);

View File

@@ -349,6 +349,20 @@ function validateDate($date) {
return date('Y-m-d'); // Fallback
}
/*
* Validates a recurring invoice frequency against the only two values the UI
* offers. This value is spliced into SQL *unquoted* (INTERVAL 1 <frequency>),
* where escaping does nothing - a whitelist is the only safe handling. Apply it
* at every write path AND at the read side before use, so a row planted before
* this fix can't fire.
*/
function validateRecurringFrequency($frequency) {
if (in_array($frequency, ['month', 'year'], true)) {
return $frequency;
}
return 'month'; // Fallback
}
/*
* Formats bytes into human readable file sizes
*/