From def7b81a114e5b7eb75aa45cd4bd39dac3bb8ed3 Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Sat, 11 Jul 2026 11:40:06 +0200 Subject: [PATCH] Fix IDOR: getQrcode/editQrcode/deleteQrcode had no ownership check Found while auditing the owner-scope SQL fix from the last commit: getQrcode() queried purely by id with no scope applied at all (not even the buggy old form), and editQrcode()/deleteQrcode() both call getQrcode() first but then run their own unscoped where('id', $id) for the actual update/delete. Net effect: any authenticated admin/user with edit or delete rights could view, edit, or delete *any other tenant's* qr code just by guessing/incrementing the id - in the static/dynamic edit forms, the bulk download/delete endpoint, and the single delete flow alike. Fixed by applying qr_apply_owner_scope() in getQrcode() (covers the edit-prefill and delete-lookup paths, and exits via failure() before reaching the actual write query if out of scope) and adding it directly to the update/delete queries in editQrcode()/deleteQrcode() too, for defense in depth rather than relying solely on the earlier check. Verified with a two-tenant scenario (separate admin accounts): before the fix admin B could view/edit-prefill/delete admin A's qr code, after the fix all three are correctly blocked (404 / "not found" / delete is silently a no-op) and admin A's code is untouched. Users.php and presets.php were checked too and already scope correctly via different, unaffected patterns - this was isolated to the two Qrcode classes. --- src/lib/Qrcode/Qrcode-intchil.php | 5 +++++ src/lib/Qrcode/Qrcode.php | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/lib/Qrcode/Qrcode-intchil.php b/src/lib/Qrcode/Qrcode-intchil.php index 9b0d62b..45285d0 100644 --- a/src/lib/Qrcode/Qrcode-intchil.php +++ b/src/lib/Qrcode/Qrcode-intchil.php @@ -207,9 +207,12 @@ class Qrcode { } public function getQrcode($id) { + require_once BASE_PATH . '/includes/security.php'; + $db = getDbInstance(); $db->where('id', $id); + qr_apply_owner_scope($db); $result = $db->getOne($this->table); if($result !== NULL) @@ -531,6 +534,7 @@ class Qrcode { if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]) || $data_to_db['filename'] == $input_data["old_filename"]){ $db->where('id', $input_data["id"]); + qr_apply_owner_scope($db); $stat = $db->update($this->table, $data_to_db); try{ @@ -563,6 +567,7 @@ class Qrcode { $qrcode = $this->getQrcode($id); $db->where('id', $id); + qr_apply_owner_scope($db); $status = $db->delete($this->table); if ($status) { diff --git a/src/lib/Qrcode/Qrcode.php b/src/lib/Qrcode/Qrcode.php index d49e447..6181e55 100644 --- a/src/lib/Qrcode/Qrcode.php +++ b/src/lib/Qrcode/Qrcode.php @@ -192,9 +192,12 @@ class Qrcode { } public function getQrcode($id) { + require_once BASE_PATH . '/includes/security.php'; + $db = getDbInstance(); $db->where('id', $id); + qr_apply_owner_scope($db); $result = $db->getOne($this->table); if($result !== NULL) @@ -376,6 +379,7 @@ class Qrcode { if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]) || $data_to_db['filename'] == $input_data["old_filename"]){ $db->where('id', $input_data["id"]); + qr_apply_owner_scope($db); $stat = $db->update($this->table, $data_to_db); try{ @@ -408,6 +412,7 @@ class Qrcode { $qrcode = $this->getQrcode($id); $db->where('id', $id); + qr_apply_owner_scope($db); $status = $db->delete($this->table); if ($status) {