From 863390a06466ef907fe144ca88cc28887dbb0f7d Mon Sep 17 00:00:00 2001 From: "Simon C. Kessler" Date: Fri, 10 Apr 2026 12:41:44 +0200 Subject: [PATCH] =?UTF-8?q?Sanity=20Checks=20f=C3=BCr=20Ordner=20und=20Dat?= =?UTF-8?q?einamen=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/packages/pck_net_storage.pkb | 136 +++++++++++++++++++------- 1 file changed, 100 insertions(+), 36 deletions(-) diff --git a/database/packages/pck_net_storage.pkb b/database/packages/pck_net_storage.pkb index 95417d7..58ae720 100644 --- a/database/packages/pck_net_storage.pkb +++ b/database/packages/pck_net_storage.pkb @@ -42,20 +42,40 @@ create or replace package body pck_net_storage as end if; end f_build_url; + -- Normalisiert einen Ordnerpfad: stellt sicher, dass er mit / endet. + -- null bleibt null (= Bucket-Root). + function f_normalize_prefix (i_prefix in varchar2) return varchar2 + is + begin + if i_prefix is null + then + return null; + end if; + + return rtrim(i_prefix, '/') || '/'; + end f_normalize_prefix; + procedure p_assert_allowed (i_object_key in varchar2) /*Kopf------------------------------------------------------------------------------------------------ - -- Beschreibung: Prüft den Objektschlüssel auf Path-Traversal-Angriffe und Tenant-Scope. - -- Wirft Application Error -20004 bei Path Traversal, -20005 bei Scope-Verletzung. + -- Beschreibung: Prüft den Objektschlüssel auf Gültigkeit, Path-Traversal-Angriffe und Tenant-Scope. + -- Wirft Application Error -20008 bei null-Key, -20004 bei Path Traversal, + -- -20005 bei Scope-Verletzung. ------------------------------------------------------------------------------------------------------ -- Parameter: i_object_key Zu prüfender Objektschlüssel ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-08 Prozedur erstellt + -- SCK 2026-04-10 Null-Prüfung und führender-Slash-Check ergänzt ------------------------------------------------------------------------------------------------Kopf*/ is l_tenant_prefix varchar2(256); begin - if instr(i_object_key, '..') > 0 + if i_object_key is null + then + raise_application_error(-20008, 'Object key darf nicht null sein'); + end if; + + if instr(i_object_key, '..') > 0 or substr(i_object_key, 1, 1) = '/' then raise_application_error(-20004, 'Path traversal attempt detected'); end if; @@ -159,20 +179,21 @@ create or replace package body pck_net_storage as -- SCK 2026-04-08 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is - l_url varchar2(4000); - l_response clob; - l_result t_net_storage_tab := t_net_storage_tab(); - l_next_start varchar2(1024); - l_count number := 0; - l_done boolean := false; - l_cur_start varchar2(1024) := i_start_with; - c_page_size constant number := 1000; + l_url varchar2(4000); + l_response clob; + l_result t_net_storage_tab := t_net_storage_tab(); + l_next_start varchar2(1024); + l_count number := 0; + l_done boolean := false; + l_cur_start varchar2(1024) := i_start_with; + l_parent_folder varchar2(1024) := f_normalize_prefix(i_parent_folder); + c_page_size constant number := 1000; begin while not l_done loop l_url := f_build_url() || '?limit=' || c_page_size - || (case when i_parent_folder is not null then '&prefix=' || utl_url.escape(i_parent_folder, false) else '' end) + || (case when l_parent_folder is not null then '&prefix=' || utl_url.escape(l_parent_folder, false) else '' end) || (case when i_include_subfolders = 'N' then '&delimiter=/' else '' end); if l_cur_start is not null @@ -202,7 +223,8 @@ create or replace package body pck_net_storage as rec.object_name ,rec.object_size ,to_date(substr(rec.last_modified, 1, 19), 'YYYY-MM-DD"T"HH24:MI:SS') - ,'N' + -- Explizit angelegte Ordner sind Zero-Byte-Objekte mit trailing / + ,(case when rec.object_name like '%/' then 'Y' else 'N' end) ,rec.etag ); l_count := l_count + 1; @@ -316,10 +338,14 @@ create or replace package body pck_net_storage as -- SCK 2026-04-08 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is + l_parent_folder varchar2(1024) := f_normalize_prefix(i_parent_folder); begin pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES'); - p_assert_allowed(i_parent_folder); - return f_list_objects_internal(i_parent_folder, i_include_subfolders, i_start_with, i_limit); + if l_parent_folder is not null + then + p_assert_allowed(l_parent_folder); + end if; + return f_list_objects_internal(l_parent_folder, i_include_subfolders, i_start_with, i_limit); end f_list_objects; function f_download_object (i_object_key in varchar2) return blob @@ -384,6 +410,11 @@ create or replace package body pck_net_storage as pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_object_key); + if substr(i_object_key, -1) = '/' + then + raise_application_error(-20012, 'Object Key darf nicht mit / enden — zum Anlegen von Ordnern p_create_folder verwenden'); + end if; + -- TEST --l_response := f_make_request( -- i_method => 'PUT' @@ -446,14 +477,14 @@ create or replace package body pck_net_storage as l_objects t_net_storage_tab; l_response clob; l_obj_path t_object_path; + l_prefix varchar2(1024) := f_normalize_prefix(i_prefix); begin pck_mitarbeiterrecht.p_hat_recht('ADMIN'); - p_assert_allowed(i_prefix); + p_assert_allowed(l_prefix); - -- TEST -- Alle Objekte im Ordner auflisten (rekursiv, alle Tiefen) --l_objects := f_list_objects_internal( - -- i_parent_folder => i_prefix + -- i_parent_folder => l_prefix -- ,i_include_subfolders => 'Y' -- ,i_start_with => null -- ,i_limit => 0 @@ -471,12 +502,12 @@ create or replace package body pck_net_storage as -- end if; --end loop; - l_obj_path := f_split_object_key(i_prefix); + l_obj_path := f_split_object_key(l_prefix); pck_log.p_info( i_module => c_log_module ,i_action => 'DELETE_FOLDER' ,i_message => 'Ordner "' || l_obj_path.filename || '" rekursiv gelöscht | Pfad: ' || l_obj_path.path - ,i_object_ref => i_prefix + ,i_object_ref => l_prefix ); end p_delete_folder; @@ -504,6 +535,16 @@ create or replace package body pck_net_storage as pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_object_key); + if i_new_name is null or length(trim(i_new_name)) = 0 + then + raise_application_error(-20013, 'Neuer Dateiname darf nicht leer sein'); + end if; + + if instr(i_new_name, '/') > 0 + then + raise_application_error(-20014, 'Dateiname darf keinen Schrägstrich enthalten — zum Verschieben p_move_object verwenden'); + end if; + -- Verzeichnispfad aus dem aktuellen Key extrahieren if instr(i_object_key, '/') > 0 then @@ -555,15 +596,24 @@ create or replace package body pck_net_storage as -- SCK 2026-04-08 Prozedur erstellt ------------------------------------------------------------------------------------------------Kopf*/ is - l_filename varchar2(1024); - l_new_key varchar2(1024); - l_body clob; - l_response clob; - l_obj_path t_object_path; + l_filename varchar2(1024); + l_new_key varchar2(1024); + l_body clob; + l_response clob; + l_obj_path t_object_path; + l_target_prefix varchar2(1024); begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_object_key); + if i_target_prefix is null + then + raise_application_error(-20015, 'Zielpräfix darf nicht null sein'); + end if; + + l_target_prefix := f_normalize_prefix(i_target_prefix); + p_assert_allowed(l_target_prefix); + -- Dateinamen aus dem aktuellen Key extrahieren if instr(i_object_key, '/') > 0 then @@ -572,7 +622,7 @@ create or replace package body pck_net_storage as l_filename := i_object_key; end if; - l_new_key := i_target_prefix || l_filename; + l_new_key := l_target_prefix || l_filename; p_assert_allowed(l_new_key); select json_object( @@ -594,7 +644,7 @@ create or replace package body pck_net_storage as pck_log.p_info( i_module => c_log_module ,i_action => 'MOVE' - ,i_message => 'Datei "' || l_obj_path.filename || '" verschoben | Von: ' || l_obj_path.path || ' | Nach: ' || i_target_prefix + ,i_message => 'Datei "' || l_obj_path.filename || '" verschoben | Von: ' || l_obj_path.path || ' | Nach: ' || l_target_prefix ,i_object_ref => i_object_key ); end p_move_object; @@ -617,21 +667,35 @@ create or replace package body pck_net_storage as l_folder_key varchar2(1024); l_response clob; l_obj_path t_object_path; + l_prefix varchar2(1024) := f_normalize_prefix(i_prefix); begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); - p_assert_allowed(i_prefix); + + if i_folder_name is null or length(trim(i_folder_name)) = 0 + then + raise_application_error(-20010, 'Ordnername darf nicht leer sein'); + end if; + + if instr(i_folder_name, '/') > 0 + then + raise_application_error(-20011, 'Ordnername darf keinen Schrägstrich enthalten'); + end if; + + if l_prefix is not null + then + p_assert_allowed(l_prefix); + end if; -- Ordner als leeres Objekt mit trailing Slash anlegen - l_folder_key := i_prefix || i_folder_name || '/'; + l_folder_key := l_prefix || i_folder_name || '/'; p_assert_allowed(l_folder_key); - -- TEST - --l_response := f_make_request( - -- i_method => 'PUT' - -- ,i_url => f_build_url(l_folder_key) - -- ,i_body_blob => empty_blob() - -- ,i_content_type => 'application/octet-stream' - --); + l_response := f_make_request( + i_method => 'PUT' + ,i_url => f_build_url(l_folder_key) + ,i_body_blob => empty_blob() + ,i_content_type => 'application/octet-stream' + ); l_obj_path := f_split_object_key(l_folder_key); pck_log.p_info(