Cross Site Scripting (XSS) is a common security vulnerability that targets web applications by injecting malicious JavaScript or HTML into the application (or backend database). Attacker usually takes advantage of application’s poor input validation and submits malicious scripts as legit input (Ex. in a Description text field). The inputted scripts become dynamically included in application's web pages when they are rendered by victim user, allowing underlying malicious scripts to be automatically executed. This can cause several types of damage, such as: hijacking user's session, hacking user's account credentials, accessing and stealing sensitive data, and much more.
Since Oracle APEX applications are usually exposed to the Internet, they are potential targets for XSS attacks. This does not imply that internal APEX application (i.e. on intranet) are not subject to XSS attacks. However, they require either an internal attacker, or an attacker who hacks into the network and application/database first, which makes it less likely to happen. In any case, the XSS remediation tips provided in this post apply to both public-facing and internal APEX applications similarly.
Developing a web applications that fully address XSS vulnerabilities can be very tricky. It requires making sure that developers follow certain best practices during development cycle, and that applications regularly go through security reviews to uncover any XSS candidates before code gets deployed to production. It is a bit more challenging to address XSS vulnerabilities in already existing APEX applications especially large ones as you will likely need to apply the fix to hundreds or thousands of candidate items, and make sure that whatever you do does not break existing functionality.
My preferred strategy for tackling XSS is to look at the problem from both sides: 1) make sure that user cannot submit input that contains XSS scripts (a.k.a. Validate on Input), and 2) make sure that if for any reason XSS scripts make their way into the application, they won't be executed when rendered by the application (a.k.a. Escape on Output).
Validating on Input
This is where you make sure that user’s attempts to submit input that contains malicious XSS scripts are blocked and rejected by the application right away. This can be relaxed so that it seamlessly encodes HTML and JavaScript content and store user's input in a “safe format” without notifying user. However, I personally go that route only if user strictly ask for it. I think that making the application completely reject malicious XSS scripts is more secure, and it prevents accidental user’s data alteration and corruption.
Typically, APEX applications allow users to enter text input through the following text items:
o Text Fields
o Text Areas
o Rich Text Editor
APEX provides a native property called Restricted Characters that validates content of these text items upon page submission. The property offers the following options:
Whitelist options:
o Whitelist for a-Z, 0-9 and space
Throws standard error:
contains special characters. Only the characters a-Z, 0-9 and spaces are allowed.
Blacklist options:
o Blacklist HTML command characters (<>")
Throws standard error:
contains <, > or " which are invalid characters.
o Blacklist &<>"/;,*|=% and --
Throws standard error:
contains one of the following invalid characters &<>"/;,*|=% or --
o Blacklist &<>"/;,*|=% or -- and new line
Throws standard error:
contains one of the following invalid characters &<>"/;,*|=% or -- or new line.
While this property can be helpful, it might not work in all cases. For example, if you want to allow greater than > and less than < operators in certain text fields, or if you want to allow certain "safe" HTML tags for text formatting such as: <b> , <u> , <i>…etc. in such cases, this property won’t help, so you should consider developing a custom validation solution.
Once you create your custom validation solution, you then need to apply it to all applicable text items, preferably through a script to avoid having to do that manually especially if you are working on an already existing "large" application, not a small application or a brand new application.
Creating a custom validation solution
You can create a simple function that takes two parameters:
o Text item value (P_TEXT)
o Text item label (P_ITEM_LABEL). This can be optional as it will only be used to print targeted validation error messages that reflect item’s label
Then, write some PL/SQL to parse out P_TEXT, and validate against a set of whitelisted tags.
Why whitelisting, not blacklisting? Well, blacklisting requires you to identify all tags that can be malicious, which you might not exactly know right away. Also, as hackers are getting better (maybe I should say worse) every day, new tags or combination of tags might become malicious which requires you to update your blacklist frequently. If you do not update your blacklist immediately, your application will be exposed to XSS attacks. On the other hand, it should be much easier for you (and users) to identify a “short” list of tags that should be allowed in the application. This list rarely needs to be updated, and having to whitelist new tags adds another layer of security if you create an approval process to handle whitelist updates.
In order to make this flexible, let's create a whitelist table so you can add/remove tags through DMLs instead of code changes.
Create whitelist table:
CREATE TABLE TBL_XSS_WHITELIST AS
(TAG_ID NUMBER
,TAG VARCHAR2(500)
);
Insert few tags into the table, for example:
INSERT INTO TBL_XSS_WHITELIST values (1,'<i>');
INSERT INTO TBL_XSS_WHITELIST values (2,'</i>');
INSERT INTO TBL_XSS_WHITELIST values (3,'<b>');
INSERT INTO TBL_XSS_WHITELIST values (4,'</b>');
INSERT INTO TBL_XSS_WHITELIST values (5,'<u>');
INSERT INTO TBL_XSS_WHITELIST values (6,'</u>');
Now let's create the validation function:
CREATE OR REPLACE FUNCTION validate_text_xss (
p_text VARCHAR2,
p_label VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2
AS
l_string VARCHAR2 (4000);
l_lt_cnt NUMBER;
l_gt_cnt NUMBER;
l_start_idx NUMBER;
l_end_idx NUMBER;
l_is_safe NUMBER;
l_return VARCHAR2 (4000);
l_err_msg VARCHAR2 (4000) := 'contains prohibited JavaScript or HTML tags';
l_label VARCHAR2 (4000);
BEGIN
IF ( p_text IS NOT NULL AND INSTR (p_text, '<') != 0 AND INSTR (p_text, '>') != 0 ) THEN
--p_text is not null, and contains both < and > which might represent HTML or JS tags
--validate string(s) between < and > against a whitelist
-- get count of < sign in p_text
l_lt_cnt := REGEXP_COUNT (p_text, '<');
--scan through p_text and parse out each string between consecutive < and > signs
FOR i IN 1 .. l_lt_cnt
LOOP
l_start_idx :=
INSTR (p_text,
'<',
1,
i);
l_end_idx :=
INSTR (p_text,
'>',
l_start_idx,
1);
l_string := SUBSTR (p_text, l_start_idx, l_end_idx - l_start_idx + 1);
-- check if l_string contains both < and > signs, and has some text in between (which makes it a potential HTML/JS tag)
IF (REGEXP_COUNT (l_string, '<') = REGEXP_COUNT (l_string, '>') AND REPLACE (l_string, ' ') != '<>')
THEN
-- validate l_string against your whitelist
BEGIN
SELECT 1
INTO l_is_safe
FROM TBL_XSS_WHITELIST
WHERE REPLACE (LOWER (l_string), ' ') = REPLACE (LOWER (tag), ' ');
EXCEPTION
WHEN NO_DATA_FOUND
THEN
l_return :=
l_return || '"' || l_label || '" ' || l_err_msg;
EXIT;
END;
END IF;
END LOOP;
END IF;
RETURN l_return;
END validate_text_xss;
/
Now, in order to apply this function to all candidate text items, create a shared PL/SQL process that runs on every page. You can exclude unnecessary pages if needed.
In process source code:
DECLARE
l_return VARCHAR2 (4000);
BEGIN
/* loop through APEX Text items on current APEX page*/
FOR txt_item
IN (SELECT item_name, label
FROM APEX_APPLICATION_PAGE_ITEMS
WHERE application_id = :APP_ID -- current APEX application
AND page_id = :APP_PAGE_ID -- current APEX page
AND LOWER (display_as) LIKE '%text%' -- Text Field, TextArea, Rich Text Editor
AND NVL (condition_type_code, ' ') <> 'NEVER' -- active items
-- Add whatever filters suit your application's XSS requirements
)
LOOP
l_return := validate_text_xss (v(txt_item.item_name), txt_item.label);
IF l_return IS NOT NULL
THEN --an error message has been retrieved
APEX_ERROR.ADD_ERROR (
p_message => l_return,
p_display_location => apex_error.c_inline_in_notification);
END IF;
END LOOP;
END;
You might want to make this process conditional so it only gets executed when user tries to submit input. You can do that by adding a SQL condition based on page request. For example:
lower(:REQUEST) like '%submit%'
OR lower(:REQUEST) like '%apply_changes%'
OR lower(:REQUEST) like '%save%'
And that's it! Now your application will validate user's text input against your preferred XSS whitelist. Any text that contains unwhitelisted JavaScript or HTML tags will be rejected and user will be notified via a user-friendly error message. You may want to create an additional “exclusions” table where you identify APEX items (or even pages) that you want to exclude from XSS validation, and adjust this query to filter out these items or pages.
I will cover Escaping on Output in my next post to avoid being lengthy.
Note 1: If you have text items created through PL/SQL code (i.e. using APEX_ITEM API), the shared process described above won't address these items since they won't be stored in APEX metadata tables. In such case, you need to adjust the code that processes these items, and add calls to the XSS validation function to validate corresponding text before submitting to DB.
Note 2: All code in this post is only sample code to demonstrate my thought process. You will likely need to build on it to come up with a solutions that fits your application. I plan to create an APEX plug-in
when time allows to make this more usable and convenient for APEX developers.