APEX-AT-WORK no image

Sichere URLs mit einem Application Process erstellen

Von Tobias Arnhold 2.15.2011
Wenn Sie eine APEX Anwendung mit Session State Protection verwenden, dann muss jeder Request entsprechend ihrer Einstellungen mit einem sicheren Hash-Schlüssel versendet werden. Wie Sie dies während der Laufzeit mit einem Application Process erstellen können, beschreibt dieser Artikel:

Was ist Session State Protection und für was brauch ich diese Funktion in APEX Anwendungen?
SSP schützt vor dem manuellen ändern der Browser URL durch den Endbenutzer, so dass dieser nicht einfach eine falsche ID abfragen oder beliebige Page-/Applikations-Variablen während der Laufzeit ändern kann.
- SSP muss explizit über Shared Components > Session State Protection aktiviert werden.
- SSP kann für jede Seite und jedes Item individuell definiert werden.
- SSP macht die Entwicklung nicht einfacher aber wesentlich sicherer.
- SSP schützt nicht automatisch vor allen feindlichen Attacken gegen eine APEX Anwendung.

Alles wichtige rund um SSP finden Sie in der APEX Dokumentation:
http://download.oracle.com/docs/cd/E17556_01/doc/user.40/e15517/sec.htm#HTMDB12002

Ein paar nützliche Links zum Thema SSP und APEX Sicherheit allgemein:
http://www.oracle.com/global/de/community/tipps/securitytipps/index.html
http://www.talkapex.com/2009/05/apex-page-access-protection-and-session.html
http://dgielis.blogspot.com/2007/03/session-state-protection-and-url.html
http://apps2fusion.com/at/64-kr/400-preventing-url-tampering-using-apex-session-state-protection

Im Normalfall generiert APEX sichere URL's automatisch, sobald SSP aktiviert wurde. Leider ist dies in Verbindung mit manuell erstelltem Code nicht der Fall. Um eigene sichere URL's zu definieren, müssen Sie die URL mit der PL/SQL Funktion apex_util.prepare_url validieren und Sie erhalten die sichere URL als Rückgabewert. Dies funktioniert exzellent bei dynamisch erstelltem PL/SQL Code. Wenn Sie dies über eine Javascript Funktion verwenden möchten, dann brauchen Sie entweder eine Dynamic Action oder einen Application Process Aufruf, der ihnen die sichere URL zurück liefert. Ein Beispielfall könnte ein dynamischer Javascript Baum sein, dieser zur Seiten-Navigation verwendet wird. Die Baum-Daten würden via AJAX dynamisch nachgeladen werden. Bsp: ExtJS in APEX
(Dieses Beispiel zeigt nur ein mögliches Szenario, es enthält kein SSP und kein AJAX.)

Die Frage ist nun: Wie bauen Sie solch einen Prozess!?

Fügen Sie diesen Code ihrem Javascript Prozess hinzu:

/* Call Application Process um eine Secure URL zu erstellen */
v_no_sec_url = 'f?p=25500:8:3521997579041922::NO::P8_E_ID:131'
/* Statt der dargestellten URL wuerden Sie eine Javascript Variable verwenden, diese die URL enthaelt. */

/* Aufruf eines Application Prozess ueber Javascript: http://www.packtpub.com/article/ajax-implementation-apex */
var v_get_sec_url = new htmldb_Get(null, $v('pFlowId'),'APPLICATION_PROCESS=AP_SECURE_URL', $v('pFlowStepId'));

/* Uebergabe Parameter 1 und 2 definieren */
v_get_sec_url.addParam('x01',v_no_sec_url);
v_get_sec_url.addParam('x02','SESSION');

v_sec_url = v_get_sec_url.get(); /* Uebergebe APEX Wert an Javascript Variable */
/* alert(v_sec_url); */

window.location.href = v_sec_url /* Seite mit neuem HREF aktualisieren */

Nun definieren Sie den Application Process - AP_SECURE_URL:

DECLARE
v_part1 varchar2(100);
v_part2 varchar2(1000);
v_url varchar2(1200);
BEGIN
/* Teile URL */
v_part1 := substr(wwv_flow.g_x01,1,instr(wwv_flow.g_x01,'f?p')-1);
v_part2 := substr(wwv_flow.g_x01,instr(wwv_flow.g_x01,'f?p'));

/* Erstelle sichere URL */
/* Hilfe: http://apex.oracle.com/i/doc/AEAPI/apex_util074.htm */
v_url := APEX_UTIL.PREPARE_URL(p_url => v_part2,
p_checksum_type => wwv_flow.g_x02);

/* Return mit Teil 1 und Secure URL */
htp.p(v_part1 || v_url);
END;

Die erstellte URL ist aber nur die halbe Miete. Für jeden AJAX Call (Bsp: Klick auf Baum Subeinträge) muss sichergestellt werden, dass der Endbenutzer die übergebenen Parameter auch wirklich verwenden darf.
Deswegen müssen Sie ein oder mehrere Validierungs-Checks in die Prozedur integrieren:

DECLARE
v_part1 varchar2(100);
v_part2 varchar2(1000);
v_sec_url varchar2(1200);

/* Parameter fuer eigene URL Validierung */
v_url_validation number;
v_url_parameter varchar2(200);
v_url_page varchar2(10);
BEGIN
/* Teile URL */
v_part1 := substr(wwv_flow.g_x01,1,instr(wwv_flow.g_x01,'f?p')-1);
v_part2 := substr(wwv_flow.g_x01,instr(wwv_flow.g_x01,'f?p'));

/* Wichtig: Individuelle URL Validierung fuer uebergebene Parameter */
/* ----------------------------------------------------------------------- */
v_url_parameter := substr(v_part2,
instr(v_part2,':',1,7)+1
);
v_url_page := substr(v_part2,
instr(v_part2,':',1,1)+1,
instr(v_part2,':',1,2)-instr(v_part2,':',1,1)-1
);

if (v_url_parameter in (10,20,30,40,50)
and v_url_page = 10)
or (v_url_parameter in (60,70,80,90,100)
and v_url_page = 10
and :APP_USER = 'TARNHOLD')
then
v_url_validation := 1;
else
v_url_validation := 0;
end if;
/* ----------------------------------------------------------------------- */

if v_url_validation = 1 then
/* Wenn OK, dann erstelle sichere URL */
/* Hilfe: http://apex.oracle.com/i/doc/AEAPI/apex_util074.htm */
v_sec_url := APEX_UTIL.PREPARE_URL(p_url => v_part2,
p_checksum_type => wwv_flow.g_x02);

/* Return mit Teil 1 und Secure URL */
htp.p(v_part1 || v_sec_url);
else
/* Bei Validierungsfehlern (falsche Parameter) auf Anmeldeseite verlinken */
htp.p(v_part1 || 'f?p=' || :APP_ID || ':101');
end if;
EXCEPTION
WHEN OTHERS THEN
/* Bei unbekannten Fehler auf Anmeldeseite verlinken */
htp.p(v_part1 || 'f?p=' || :APP_ID || ':101');
END;

Solch eine Prozedur muss alle nicht erlaubten Fremdeingaben abfangen, um die Sicherheit in ihrer Anwendung zu gewährleisten.
Deswegen zeige ich ihnen eine noch sicherere Beispiel-Prozedur, damit eine mögliche Risikominimierung für feindliche Attacken und der Aufwand dessen deutlich wird, der für eine solche Prozedur notwendig ist:

DECLARE
v_part1 varchar2(100);
v_part2 varchar2(1000);
v_sec_url varchar2(1200);

/* Parameter fuer eigene URL Validierung */
v_url_validation number;
v_url_app varchar2(100);
v_url_page varchar2(100);
v_url_request varchar2(100);
v_url_debug varchar2(3);
v_url_clearcache varchar2(100);
v_url_param_name varchar2(200);
v_url_param_val varchar2(200);
-- v_url_printer varchar2(200);
BEGIN
/* Teile URL */
v_part1 := substr(wwv_flow.g_x01,1,instr(wwv_flow.g_x01,'f?p')-1);
v_part2 := substr(wwv_flow.g_x01,instr(wwv_flow.g_x01,'f?p'));

/* Wichtig: Individuelle URL Validierung fuer uebergebene Parameter */
/* ----------------------------------------------------------------------- */
v_url_app := substr(v_part2,
instr(v_part2,'=',1,1)+1,
instr(v_part2,':',1,1)-instr(v_part2,'=',1,1)-1
);
v_url_page := substr(v_part2,
instr(v_part2,':',1,1)+1,
instr(v_part2,':',1,2)-instr(v_part2,':',1,1)-1
);
v_url_request := substr(v_part2,
instr(v_part2,':',1,3)+1,
instr(v_part2,':',1,4)-instr(v_part2,':',1,3)-1
);
v_url_debug := substr(v_part2,
instr(v_part2,':',1,4)+1,
instr(v_part2,':',1,5)-instr(v_part2,':',1,4)-1
);
v_url_clearcache := substr(v_part2,
instr(v_part2,':',1,5)+1,
instr(v_part2,':',1,6)-instr(v_part2,':',1,5)-1
);
v_url_param_name := substr(v_part2,
instr(v_part2,':',1,6)+1,
instr(v_part2,':',1,7)-instr(v_part2,':',1,6)-1
);
v_url_param_val := substr(v_part2,
instr(v_part2,':',1,7)+1
);
/* TEST
htp.p( 'v_url_app: ' || v_url_app || ' ,v_url_page: ' || v_url_page || ' ,v_url_request: ' || v_url_request
|| ' ,v_url_debug: ' || v_url_debug || ' ,v_url_clearcache: ' || v_url_clearcache
|| ' ,v_url_param_name: ' || v_url_param_name || ' ,v_url_param_val: ' || v_url_param_val
);
*/

if v_url_app IN (:APP_ID,:APP_ALIAS)
AND v_url_request is null
AND v_url_debug = 'NO'
AND (
v_url_page IN ('1','2','3','4','5')
and v_url_param_name is null
and v_url_param_val is null
OR v_url_page = '10'
and v_url_param_name IN ('P10_MFD_ID')
and v_url_param_val IN (1,14,15,16,17,19) /* Koennte auch ein Sub Select sein*/
and length(v_url_param_val)-length(replace(v_url_param_val,',','')) = 0
OR v_url_page = '20'
and v_url_clearcache = '20'
and v_url_param_name IN ('P20_KLT_ID')
and v_url_param_val IN (100,200,300,400,500,600) /* Koennte auch ein Sub Select sein*/
and length(v_url_param_val)-length(replace(v_url_param_val,',','')) = 0
OR v_url_page = '30'
and v_url_clearcache = '30'
and v_url_param_name IN ('P30_KLS_ID,P30_KLS_PAR1')
and substr(v_url_param_val,1,instr(v_url_param_val,',')-1) IN (1000,2000,3000,4000,5000,6000)
and length(v_url_param_val)-length(replace(v_url_param_val,',','')) = 1
)
then
v_url_validation := 1;
else
v_url_validation := 0;
end if;
/* ----------------------------------------------------------------------- */

if v_url_validation = 1 then
/* Wenn OK, dann erstelle sichere URL */
/* Hilfe: http://apex.oracle.com/i/doc/AEAPI/apex_util074.htm */
/* Der Parameter: p_checksum_type sollte fest über die Prozedur gesteuert werden */
v_sec_url := APEX_UTIL.PREPARE_URL(p_url => v_part2,
p_checksum_type => 'SESSION');

/* Return mit Teil 1 und Secure URL */
htp.p(v_part1 || v_sec_url);
else
/* Bei Validierungsfehlern (falsche Parameter) auf Anmeldeseite verlinken */
htp.p(v_part1 || v_part2);
-- htp.p(v_part1 || 'f?p=' || :APP_ID || ':101');
end if;
EXCEPTION
WHEN OTHERS THEN
/* Bei unbekannten Fehler auf Anmeldeseite verlinken */
htp.p(v_part1 || 'f?p=' || :APP_ID || ':101');
END;

Was schließt man daraus: Schönheit kann ganz schön teuer (aufwendig) werden. :) Wer hätte das gedacht.

Fazit: Die Verwendung von SSP mit Standard APEX Funktionalität bietet ohne großes Wissen die meiste Sicherheit. Bei der Verwendung von eigenen Erweiterungen, muss auf die oben beschriebenen Sicherheitsaspekte Wert gelegt werden. Deswegen ist es auch von Vorteil solche Erweiterungen in Plug-Ins auszulagern. Mehr Infos dazu erhalten Sie in meinem letzten APEX Community Beitrag: HowTo: APEX 4.0 Dynamic Action Plugins

Wenn Sie Unterstützung bei APEX Security Themen benötigen, dann können Sie sich natürlich immer an mich (Tobias Arnhold - tobias-arnhold@hotmail.de) wenden. Ein wahrer Kenner auf dem Gebiet "APEX Security" ist Denes Kubicek, dieser auch spezielle Schulungen zum Thema APEX anbietet.