Developing Secure Java Code - Best Practices for a Team

1 Developing Secure Java Code - Best Practices for a Team The following article shall introduce to us the basic practices to be followed to write sec...
Author: Angel Douglas
160 downloads 0 Views 254KB Size
1

Developing Secure Java Code - Best Practices for a Team The following article shall introduce to us the basic practices to be followed to write secure Java code. A development team designs an application to perform specific tasks based on functional requirements and test cases whereas an attacker will try to exploit any action that an application can be made to do, any action that is not specifically disallowed is allowed. It’s essential for development teams to grasp that client-side controls like user based inputs, fields and interface control, both inputs and validations, provide little or no security advantages. An attacker can use tools like client-side web proxies such as OWASP WebScarab, Burp proxy or network packet capturing tools such as WireShark to analyze application traffic and submit custom built requests, bypassing the interface completely. Finally, Java Applets, Flash, and other client side objects can be decompiled and examined for security loop-holes. Moreover, Java applications are gullible to attacks because they accept untrusted user input and interact with complex subsystems. Injection attacks such as cross-site scripting [XSS], XPath, and LDAP injection are possible when the components are susceptible, easy to attack, and are used in the application. An effective mitigation strategy is to whitelist input and encode or escape output before it is processed.

Working together as a Team ● ● ● ● ● ●



As you write code, follow the development rules for improving code functionality, security, performance, and maintainability. Verify as soon as each piece of code is completed or modified, use unit level reliability testing to verify that it's functional and secure. Verify as soon as each piece of code is completed or modified, use unit level functional testing to verify that it's implemented correctly and functions properly. Use regression testing to make sure that each piece of code continues to operate correctly as the functionality is added or code is modified. Maintain separate branches for all commits related to security and merge it into the master branch after a review by multiple team members. Have a document that documents the Java secure coding standards. It is also important to make sure that you always stick to these standards. Spend time in updating those standards. Have a Java security testing checklist to validate that the security fix works.

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

1

2



Have a list of certain libraries and common functions for fixing the most common type of vulnerabilities.

All the above steps can be automated to promote a quick implementation and allow your team to see the potential benefits without disturbing the development efforts or adding extra work to your already hectic schedule.

A basic approach that should be kept in mind to write security-critical Java code is as following:

Limit the lifetime of sensitive data Data such as login credentials in memory can be vulnerable to compromise. A fellow user who can execute code on the same system as an application may be able to access such data if the application, ●

That uses objects to return any sensitive data whose contents are not cleared or junkcollected right after use.

Noncompliant Code: The following code reads the username & password from the console and stores the password as a String object; the credentials remain exposed until the garbage collector recollects the value. String user = a.readLine("Enter your ID:"); String pass = a.readLine("Enter your pass:"); Compliant Code: The following code uses the Console.readPassword() method to obtain the password from the console. String user = a.readLine("Enter your ID:"); char[] pass = a.readPassword("Enter your pass:");

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

2

3

Use conservative file naming conventions File and path names containing particular characters or character sequences can cause problems when used in the construction of a file or path name: ● Leading dashes: Leading dashes could create problems when programs are called with the file names as a parameter because the first character or characters of the file name might be interpreted as a switch for the options. ● Control characters: like newlines, carriage returns, and escape. ●

Spaces: Spaces can cause problems with scripts and particularly when double quotes are not used to surround the file name.

Noncompliant code: File a = new File("A\uD8AB"); OutputStream out = new FileOutputStream(c);

The above code on an Ubuntu machine returns will generate the following filename output: A? Compliant code: File a = new File("name.ext"); OutputStream out = new FileOutputStream(a);

Using an expressive file name with only the subset of ASCII mentioned previously.

Prevent code injection Code injection involves injecting malicious code into an application, which will be executed within the context of the application. There are several ways in which Java code could be injected into an application with the use of scripting API, or dynamic JSP includes. Example: The following code allows a user to inject arbitrary Javascript into Java's script engine.

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

3

4

import javax.script.*; public class Ex1 { public static void main(String[] args) { try { ScriptEngineManager man1 = new ScriptEngineManager(); ScriptEngine engine = man1.getEngineByName("JavaScript"); System.out.println(args[0]); engine.eval("print('"+ args[0] + "')"); } catch(Exception e) { e.printStackTrace(); } } } In the above scenario, the attacker tries to inject code that creates a file on the file system. hallo'); var fImport = new JavaImporter(java.io.File); with(fImport) { var f = new File('new'); f.createNewFile(); }

Compliant Solution (Whitelisting):

The best defense to cope up against the code injection vulnerabilities is to prevent the inclusion of executable user input in code. Any user input that is used in dynamic code must be first sanitized, for instance, to ensure that it contains only valid, whitelisted characters. private static void evalScript(String firstName) throws ScriptException { // Allow only alphanumeric and underscore chars in firstName // (modify if firstName may also include special characters) if (!firstName.matches("[\\w]*")) { // String does not match whitelisted characters throw new IllegalArgumentException(); } ScriptEngineManager man1 = new ScriptEngineManager(); ScriptEngine engine = man1.getEngineByName("javascript"); engine.eval("print('"+ firstName + "')"); }

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

4

5

Prevent XPath Injection Extensible Markup Language (XML) can be used for data storage like a relational database. Data is frequently retrieved from such an XML document using XPaths. XPath injection can occur when data supplied to an XPath retrieval routine to retrieve data from an XML document is used without proper sanitization. This attack is similar to SQL injection or XML injection. An attacker can enter valid SQL or XML constructs in the data fields of the query in use.

XML Path Injection Example Consider the following XML schema: john d542353kij789f5 doe 2d187h6x29d098kn1 dena jb18dh12a36dh4z7 The passwords are hashed for illustrative purposes. Any untrusted code may attempt to try and extract the user details from this file with an XPath statement created dynamically from user input. //users/user[username/text()='&LOGIN&' and password/text()='&PASSWORD&' ] If an attacker knows that John is a valid user name, he or she can specify an input such as john' or '1'='1 This yields the following query string. //users/user[username/text()='john' or '1'='1' and password/text()='xxxx']

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

5

6

Because the '1'='1' is automatically true, the password is never validated. Consequently, the attacker is inappropriately authenticated as user Utah without knowing Utah's password.

Noncompliant code: private boolean doLogin(String userName, char[] password) { DocumentBuilder builder = domFactory.newDocumentBuilder(); Document doc = builder.parse("users.xml"); String pwd = hashPassword( password); XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); XPathExpression expr = xpath.compile("//users/user[username/text()='" + userName + "' and password/text()='" + pwd + "' ]"); Object result = expr.evaluate(doc, XPathConstants.NODESET); NodeList nodes = (NodeList) result; System.out.println( "Authenticated: " + node.getNodeValue()); }

In the above code, if the attack string is described as earlier is passed to evaluate() which returns the corresponding value in the XML file causing the doLogin() method to return true and to bypass any authorization.

Compliant Solution (XQuery): XPath injection can be prevented by adopting defenses similar to those used to prevent SQL injection: ● Treat all user input as untrusted, and perform proper sanitization. ● When sanitizing user input, verify the correctness of the data type, length, format, and content. ● In a client-server application, perform validation at both the client and the server sides. ● Extensively test applications that supply, propagate, or accept user input.

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

6

7

This compliant solution uses a specific query in a text file by reading the file in the required format and then inserting values for the username and password in a Map. The XQuery library constructs the XML query from these inputs. private boolean doLogin(String userName, String pwd) { DocumentBuilder builder = domFactory.newDocumentBuilder(); Document doc = builder.parse("users.xml"); XQuery xquery = new XQueryFactory().createXQuery(new File("login.xq")); Map queryVars = new HashMap(); queryVars.put("userName", userName); queryVars.put("password", pwd); NodeList nodes = xquery.execute(doc, null, queryVars).toNodes(); System.out.println(node.getNodeValue()); } Using this method, the data specified in the userName and password fields cannot be interpreted as executable content at runtime.

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

7

8

Prevent LDAP injection The Lightweight Directory Access Protocol (LDAP) allows an application to perform remote operations such as searching and modifying records in directories. LDAP injection results when an application fails to sanitize properly user input and inadequate validation thus allowing malicious users to access restricted information using the directory service. A whitelist can be used to restrict input to a list of valid characters.

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

8

9

Characters and Sequences to Exclude from Whitelists

Character

Name

' and "

Single and double quote

/ and \

Forward slash and backslash

\\

Double slashes*

space

Space character at beginning or end of string

#

Hash character at the beginning of the string

< and >

Angle brackets

, and ;

Comma and semicolon

+ and *

Addition and multiplication operators

( and )

Round braces

\u0000

Unicode NULL character

* This is a character sequence.

Prevent arbitrary file upload Java applications, including web applications, which accept file uploads must ensure that an attacker cannot upload or transfer malicious files. If a restricted file containing code is executed by the target system, an arbitrary file upload vulnerability could result in privilege escalation and the execution of arbitrary code. PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

9

10

To support file upload, a typical Java Server Pages (JSP) page consists of code such as the following:

Noncompliant Code Example: This noncompliant code example shows XML code from the upload action of a Apache struts application. The interceptor code is responsible for allowing file uploads. 10240 text/plain,image/JPEG,text/html The code for file upload appears in the UploadAction class: public String execute() { try { File fileToCreate = new File("filepath", "filename"); FileUtils.copyFile(uploadedFile, fileToCreate); return "SUCCESS"; } catch (Throwable e) { addActionError(e.getMessage()); return "ERROR"; }

The value of the parameter type maximumSize makes sure that a particular Action cannot accept a file larger than the size allowed. The allowedTypes parameter defines the type of files that are accepted. However, this approach fails to keep check that the uploaded file follows the security requirements because interceptor checks can be bypassed trivially. If an attacker were to use a proxy tool to change the content type in the raw HTTP request in transit, it would easily bypass the constraints, and the file would be uploaded. Same way, an attacker could upload a malicious file that has a .exe extension, for example.

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

10

11

Compliant example: The file upload should succeed only when the content type matches the actual content of the file. For example, a file with an image header must contain only an image and must not contain executable code. The following code tries to detect and extract metadata and structured text content from documents using existing parser libraries. The checkMetaData() method must be called before invoking code in execute() that is responsible for uploading the file. public String execute() { try { File fileToCreate = new File("filepath", "filename"); boolean PlainText = checkMetaData(uploadedFile, "text/plain"); boolean image = checkMetaData(uploadedFile, "image/JPEG"); boolean HtmlTxt = checkMetaData(uploadedFile, "text/html"); if (!textPlain || !img || !textHtml) { return "ERROR"; }

An Overview of practices that a team should follow. General Coding Practices: ● Use tried and tested code instead of making new unmanaged code for basic assignments. ● It is a good practice to imply the usage of hashes and checksums to verify the integrity of code, libraries, and executables. PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

11

12

● ● ● ● ● ● ●

Utilize locking mechanisms prevent simultaneous requests. Prevent shared variables access and resources from being accessed in an inappropriate manner or simultaneous access of any. Initialize all the variables and other data types, either during declaration or before the first usage. Try not to give any escalated privileges to an application, in cases where it is required provide access as late as you can, and revert the privileges as soon as possible. Avoid to pass any user inputs to any dynamically executing function. Limit users from producing new code or modifying any existing code. Execute mechanisms such as cryptographic signatures over an encrypted channel to transmit the code from server to client, if you implement features such as automatic updating.

Input Validation: ● All data validation should take place on the server. ● Data from all untrusted sources should be validated, with the classification of data sources as trusted and untrusted. A mechanism to check all inputs for the application centrally. ● Specify proper character sets, such as UTF-8, for all sources of input. ● Before validation, encode all data to a common character set. ● Failure to abide by any validation method should result in input to be rejected completely. ● Determine if the system supports UTF-8 extended character sets and if so, validate after UTF-8 decoding is completed. ● Validate all client side data before processing, including all parameters, URLs, and HTTP header content. Also, verify that header values in both requests and responses contain only ASCII characters. ● Validate data from redirects, check for expected data types, data range, data length ● Validate all input against a whitelist of allowed characters. ● The following should be checked for individually: o Null bytes (%00) o New line characters (%0d, %0a, \r, \n) o “dot-dot-slash" (../ or ..\) path alterations characters.

Output Encoding: ● All data encoding should take place on a the server. ● Utilize a standard, tested routine for each type of outbound encoding. PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

12

13



● ● ●

Contextually output encode all data returned to the client that originated outside the application's trust boundary. HTML entity encoding is one example but does not work in all cases. Encode all characters unless they are known to be safe for the intended interpreter Contextually sanitize all output of untrusted data to queries for SQL, XML, and LDAP. Sanitize all output of untrusted data to operating system commands.

Authentication and Password Management: ● All pages to require authentication except for those specifically designed to be accessed without authentication, intended to be public. ● All authentication controls must take place on the server. ● Use authentication services that have been tested whenever possible. ● All authentication controls should fail securely. ● All administrative and account management functions must be at least as secure as the primary authentication mechanism. ● Use only HTTP POST requests to transmit authentication credentials. ● Use a minimum password strength requirement by regulation. Minimum 8 characters, with an upper case, symbols, and numbers, the better is 16 characters or the use of multiple word passwords. ● Password input should never remain in cleartext on the user's screen. (registration, login forms) ● Notify users when a password reset occurs. ● Prevent same passwords to be used over a minimum time span for 90 days or never.

Session Management: ● The application should only recognize the server’s or the framework’s session management controls as valid. ● Session identifier creation must always be done on the server. ● Make sure that the algorithms are in place to maintain the randomness of the session identifiers. ● Logging out should terminate all sessions. ● The user can logout from all available pages after authorization. ● Session inactivity timeout measures based on business requirements, in any case, should not be more than a couple of hours. ● Disallow persistent logins. ● Terminate all previous sessions before a new successful login. ● Generate a new session identifier on any re-authentication. PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

13

14



Do not allow concurrent logins with the same user ID.

Access Control: ● Only use the server-side session objects to make authorization decisions. ● Use a single site-wide component to check access authorization. ● Access controls should fail securely. ● Restrict access to files or other resources only to authorized users. ● All protected URLs to be accessible by only authorized users. ● All protected functions to be only accessed authorized users. ● All direct object references to be accessible only authorized users. ● Restricted access to services by only authorized users. ● Restricted access to application data by only authorized users. ● Restrict access to user and data attributes and policy information used by access controls. ● Restricted access to security related configuration information to only specifically authorized users.

Cryptographic Practices: ● All cryptographic functions used to protect information from the application user must take place on the server. ● Protect master secrets from unauthorized access. ● Cryptographic modules should fail securely. ● All random numbers, random file names, random GUIDs, and random strings should be generated using the cryptographic module’s approved random number generator when these random values are intended to be un-guessable. ● Cryptographic modules used by the application should be compliant with FIPS 140-2 or an equivalent standard. (See http://csrc.nist.gov/groups/STM/cmvp/validation.html) ● Establish and maintain a policy and process for how cryptographic keys will be managed.

Error Handling and Logging: ● Do not reveal any sensitive information in error responses such as system details, or account information. ● Implement general error messages and use custom error pages.

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

14

15

● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

The application should handle errors generated by the application and not rely solely on the server configuration. Allocated memory should be freed with the correct process when the condition arises. All logging controls should take place on the server side. Both success and failure of security implementations should be logged. All important events should be logged. Log entries should not be executed as code in the log viewing process or interface. Logs to be accessed with certain specific authorized personnel only. Create a master plan for logging all operations. Do not store any sensitive information in logs, including unnecessary details about the system, session information or passwords. Log analysis should be conducted in a routine manner. Log all input validation failures, authentication attempts & failures, control failures. Any tampering events including attempts to connect with older passwords or invalid sessions tokens should be logged. Log all system exceptions All administrative changes should be logged. Log cryptographic module failures. Use a cryptographic hash function to validate log entry integrity.

References: 1. CERT Oracle Coding Standard for Java - https://www.cert.org/ 2. OWASP - https://www.owasp.org 3. Java Coding Guidelines: 75 Recommendations for Reliable and Secure Programs (By Fred Long, Dhruv Mohindra, Robert Seacord, Dean F. Sutherland, David Svoboda)

PRATEEK GIANCHANDANI HTTP://WWW.INFOSECINSTITUTE.COM/COURSES/SECURE_CODING_JAVA_JEE.HTML

15