Security - Java coding guidelines: 75 recommendations for reliable and secure programs (2014)

Java coding guidelines: 75 recommendations for reliable and secure programs (2014)

Chapter 1. Security

The Java programming language and runtime system were designed with security in mind. For example, pointer manipulation is implicit and hidden from the programmer, and any attempt to reference a null pointer results in an exception being thrown. Similarly, an exception results from any attempt to access an array or a string outside of its bounds. Java is a strongly typed language, and all implicit type conversions are well defined and platform independent, as are the arithmetic types and conversions. The Java Virtual Machine (JVM) has a built-in bytecode verifier to ensure that the bytecode being run conforms to the Java Language Specification: Java SE 7 Edition (JLS) so that all the checks defined in the language are in place and cannot be circumvented.

The Java class loader mechanism identifies classes as they are loaded into the JVM, and can distinguish between trusted system classes and other classes that may not be trusted. Classes from external sources can be given privileges by digitally signing them; these digital signatures can also be examined by the class loader, and contribute to the class’s identification. Java also provides an extensible fine-grained security mechanism that enables the programmer to control access to resources such as system information, files, sockets, and any other security-sensitive resources that the programmer wishes to use. This security mechanism can require that a runtime security manager be in place to enforce a security policy. A security manager and its security policy are usually specified by command-line arguments, but they may be installed programmatically, provided that such an action is not already disallowed by an existing security policy. Privileges to access resources may be extended to nonsystem Java classes by relying on the identification provided by the class loader mechanism.

Enterprise Java applications are susceptible to attacks because they accept untrusted input and interact with complex subsystems. Injection attacks (such as cross-site scripting [XSS], XPath, and LDAP injection) are possible when the components susceptible to these attacks are used in the application. An effective mitigation strategy is to whitelist input, and encode or escape output before it is processed for rendering.

This chapter contains guidelines that are concerned specifically with ensuring the security of Java-based applications. Guidelines dealing with the following security nuances are articulated.

1. Dealing with sensitive data

2. Avoiding common injection attacks

3. Language features that can be misused to compromise security

4. Details of Java’s fine-grained security mechanism

1. Limit the lifetime of sensitive data

Sensitive data in memory can be vulnerable to compromise. An adversary who can execute code on the same system as an application may be able to access such data if the application

Image Uses objects to store sensitive data whose contents are not cleared or garbage-collected after use

Image Has memory pages that can be swapped out to disk as required by the operating system (for example, to perform memory management tasks or to support hibernation)

Image Holds sensitive data in a buffer (such as BufferedReader) that retains copies of the data in the OS cache or in memory

Image Bases its control flow on reflection that allows countermeasures to circumvent the limiting of the lifetime of sensitive variables

Image Reveals sensitive data in debugging messages, log files, environment variables, or through thread and core dumps

Sensitive data leaks become more likely if the memory containing the data is not cleared after using the data. To limit the risk of exposure, programs must minimize the lifetime of sensitive data.

Complete mitigation (that is, foolproof protection of data in memory) requires support from the underlying operating system and Java Virtual Machine. For example, if swapping sensitive data out to disk is an issue, a secure operating system that disables swapping and hibernation is required.

Noncompliant Code Example

This noncompliant code example reads user name and password information from the console and stores the password as a String object. The credentials remain exposed until the garbage collector reclaims the memory associated with this String.


class Password {
public static void main (String args[]) throws IOException {
Console c = System.console();
if (c == null) {
System.err.println("No console.");
System.exit(1);
}

String username = c.readLine("Enter your user name: ");
String password = c.readLine("Enter your password: ");

if (!verify(username, password)) {
throw new SecurityException("Invalid Credentials");
}

// ...
}

// Dummy verify method, always returns true
private static final boolean verify(String username,
String password) {
return true;
}
}


Compliant Solution

This compliant solution uses the Console.readPassword() method to obtain the password from the console.


class Password {
public static void main (String args[]) throws IOException {
Console c = System.console();

if (c == null) {
System.err.println("No console.");
System.exit(1);
}

String username = c.readLine("Enter your user name: ");
char[] password = c.readPassword("Enter your password: ");
if (!verify(username, password)) {
throw new SecurityException("Invalid Credentials");
}

// Clear the password
Arrays.fill(password, ' ');
}

// Dummy verify method, always returns true
private static final boolean verify(String username,
char[] password) {
return true;
}
}


The Console.readPassword() method allows the password to be returned as a sequence of characters rather than as a String object. Consequently, the programmer can clear the password from the array immediately after use. This method also disables echoing of the password to the console.

Noncompliant Code Example

This noncompliant code example uses a BufferedReader to wrap an InputStream-Reader object so that sensitive data can be read from a file:


void readData() throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream("file")));
// Read from the file
String data = br.readLine();
}


The BufferedReader.readLine() method returns the sensitive data as a String object, which can persist long after the data is no longer needed. The BufferedReader.read(char[], int, int) method can read and populate a char array. However, it requires the programmer to manually clear the sensitive data in the array after use. Alternatively, even if the BufferedReader were to wrap a FileReader object, it would suffer from the same pitfalls.

Compliant Solution

This compliant solution uses a directly allocated NIO (new I/O) buffer to read sensitive data from the file. The data can be cleared immediately after use and is not cached or buffered in multiple locations. It exists only in the system memory.


void readData(){
ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
try (FileChannel rdr =
(new FileInputStream("file")).getChannel()) {
while (rdr.read(buffer) > 0) {
// Do something with the buffer
buffer.clear();
}
} catch (Throwable e) {
// Handle error
}
}


Note that manual clearing of the buffer data is mandatory because direct buffers are not garbage collected.

Applicability

Failure to limit the lifetime of sensitive data can lead to information leaks.

Bibliography

[API 2013]

Class ByteBuffer

[Oracle 2013b]

“Reading ASCII Passwords from an InputStream Example” from the Java Cryptography Architecture [JCA] Reference Guide

[Tutorials 2013]

I/O from the Command Line

2. Do not store unencrypted sensitive information on the client side

When building an application that uses a client–server model, storing sensitive information, such as user credentials, on the client side may result in its unauthorized disclosure if the client is vulnerable to attack.

For web applications, the most common mitigation to this problem is to provide the client with a cookie and store the sensitive information on the server. Cookies are created by a web server, and are stored for a period of time on the client. When the client reconnects to the server, it provides the cookie, which identifies the client to the server, and the server then provides the sensitive information.

Cookies do not protect sensitive information against cross-site scripting (XSS) attacks. An attacker who is able to obtain a cookie either through an XSS attack, or directly by attacking the client, can obtain the sensitive information from the server using the cookie. This risk is timeboxed if the server invalidates the session after a limited time has elapsed, such as 15 minutes.

A cookie is typically a short string. If it contains sensitive information, that information should be encrypted. Sensitive information includes user names, passwords, credit card numbers, social security numbers, and any other personally identifiable information about the user. For more details about managing passwords, see Guideline 13, “Store passwords using a hash function.” For more information about securing the memory that holds sensitive information, see Guideline 1, “Limit the lifetime of sensitive data.”

Noncompliant Code Example

In this noncompliant code example, the login servlet stores the user name and password in the cookie to identify the user for subsequent requests:


protected void doPost(HttpServletRequest request,
HttpServletResponse response) {

// Validate input (omitted)

String username = request.getParameter("username");
char[] password =
request.getParameter("password").toCharArray();
boolean rememberMe =
Boolean.valueOf(request.getParameter("rememberme"));

LoginService loginService = new LoginServiceImpl();

if (rememberMe) {
if (request.getCookies()[0] != null &&
request.getCookies()[0].getValue() != null) {
String[] value =
request.getCookies()[0].getValue().split(";");

if (!loginService.isUserValid(value[0],
value[1].toCharArray())) {
// Set error and return
} else {
// Forward to welcome page
}
} else {
boolean validated =
loginService.isUserValid(username, password);
if (validated) {
Cookie loginCookie = new Cookie("rememberme", username +
";" + new String(password));
response.addCookie(loginCookie);
// ... forward to welcome page
} else {
// Set error and return
}
}
} else {
// No remember-me functionality selected
// Proceed with regular authentication;
// if it fails set error and return
}

Arrays.fill(password, ' ');
}


However, the attempt to implement the remember-me functionality is insecure because an attacker with access to the client machine can obtain this information directly on the client. This code also violates Guideline 13, “Store passwords using a hash function.”

Compliant Solution (Session)

This compliant solution implements the remember-me functionality by storing the user name and a secure random string in the cookie. It also maintains state in the session using HttpSession:


protected void doPost(HttpServletRequest request,
HttpServletResponse response) {

// Validate input (omitted)

String username = request.getParameter("username");
char[] password =
request.getParameter("password").toCharArray();
boolean rememberMe =
Boolean.valueOf(request.getParameter("rememberme"));
LoginService loginService = new LoginServiceImpl();
boolean validated = false;
if (rememberMe) {
if (request.getCookies()[0] != null &&
request.getCookies()[0].getValue() != null) {

String[] value =
request.getCookies()[0].getValue().split(";");

if (value.length != 2) {
// Set error and return
}

if (!loginService.mappingExists(value[0], value[1])) {
// (username, random) pair is checked
// Set error and return
}
} else {
validated = loginService.isUserValid(username, password);

if (!validated) {
// Set error and return
}
}

String newRandom = loginService.getRandomString();
// Reset the random every time
loginService.mapUserForRememberMe(username, newRandom);
HttpSession session = request.getSession();
session.invalidate();
session = request.getSession(true);
// Set session timeout to 15 minutes
session.setMaxInactiveInterval(60 * 15);
// Store user attribute and a random attribute
// in session scope
session.setAttribute("user", loginService.getUsername());
Cookie loginCookie =
new Cookie("rememberme", username + ";" + newRandom);
response.addCookie(loginCookie);
//... forward to welcome page
} else { // No remember-me functionality selected
//... authenticate using isUserValid(),
// and if failed, set error
}
Arrays.fill(password, ' ');
}


The server maintains a mapping between user names and secure random strings. When a user selects “Remember me,” the doPost() method checks whether the supplied cookie contains a valid user name and random string pair. If the mapping contains a matching pair, the server authenticates the user and forwards him or her to the welcome page. If not, the server returns an error to the client. If the user selects “Remember me” but the client fails to supply a valid cookie, the server requires the user to authenticate using his or her credentials. If the authentication is successful, the server issues a new cookie with remember-me characteristics.

This solution avoids session-fixation attacks by invalidating the current session and creating a new session. It also reduces the window during which an attacker could perform a session-hijacking attack by setting the session timeout to 15 minutes between client accesses.

Applicability

Storing unencrypted sensitive information on the client makes this information available to anyone who can attack the client.

Bibliography

[Oracle 2011c]

Package javax.servlet.http

[OWASP 2009]

Session Fixation in Java

[OWASP 2011]

Cross-Site Scripting (XSS)

[W3C 2003]

The World Wide Web Security FAQ

3. Provide sensitive mutable classes with unmodifiable wrappers

Immutability of fields prevents inadvertent modification as well as malicious tampering so that defensive copying while accepting input or returning values is unnecessary. However, some sensitive classes cannot be immutable. Fortunately, read-only access to mutable classes can be granted to untrusted code using unmodifiable wrappers. For example, the Collection classes include a set of wrappers that allow clients to observe an unmodifiable view of a Collection object.

Noncompliant Code Example

This noncompliant code example consists of class Mutable, which allows the internal array object to be modified:


class Mutable {
private int[] array = new int[10];

public int[] getArray() {
return array;
}
public void setArray(int[] i) {
array = i;
}
}

//...
private Mutable mutable = new Mutable();
public Mutable getMutable() {return mutable;}


An untrusted invoker may call the mutator method setArray(), and violate the object’s immutability property. Invoking the getter method getArray() also allows modification of the private internal state of the class. This class also violates The CERT® Oracle® Secure Coding Standard for Java [Long 2012], “OBJ05-J. Defensively copy private mutable class members before returning their references.”

Noncompliant Code Example

This noncompliant code example extends the Mutable class with a MutableProtector subclass:


class MutableProtector extends Mutable {
@Override
public int[] getArray() {
return super.getArray().clone();
}
}
// ...
private Mutable mutable = new MutableProtector();
// May be safely invoked by untrusted caller having read ability
public Mutable getMutable() {return mutable;}


In this class, invoking the getter method getArray() does not allow modification of the private internal state of the class, in accordance with “OBJ05-J. Defensively copy private mutable class members before returning their references” [Long 2012]. However, an untrusted invoker may call the method setArray() and modify the Mutable object.

Compliant Solution

In general, sensitive classes can be transformed into safe-view objects by providing appropriate wrappers for all methods defined by the core interface, including the mutator methods. The wrappers for the mutator methods must throw an UnsupportedOperationException so that clients cannot perform operations that affect the immutability property of the object.

This compliant solution adds a setArray() method that overrides the Mutable.setArray() method and prevents mutation of the Mutable object:


class MutableProtector extends Mutable {
@Override
public int[] getArray() {
return super.getArray().clone();
}

@Override
public void setArray(int[] i) {
throw new UnsupportedOperationException();
}
}

// ...
private Mutable mutable = new MutableProtector();
// May be safely invoked by untrusted caller having read ability
public Mutable getMutable() {return mutable; }


The MutableProtector wrapper class overrides the getArray() method and clones the array. Although the calling code gets a copy of the mutable object’s array, the original array remains unchanged and inaccessible. The overriding setArray() method throws an exception if the caller attempts to use this method on the returned object. This object can be passed to untrusted code when read access to the data is permissible.

Applicability

Failure to provide an unmodifiable, safe view of a sensitive mutable object to untrusted code can lead to malicious tampering and corruption of the object.

Bibliography

[Long 2012]

OBJ05-J. Defensively copy private mutable class members before returning their references

[Tutorials 2013]

Unmodifiable Wrappers

4. Ensure that security-sensitive methods are called with validated arguments

Application code that calls security-sensitive methods must validate the arguments being passed to the methods. In particular, null values may be interpreted as benign by certain security-sensitive methods but may override default settings. Although security-sensitive methods should be coded defensively, the client code must validate arguments that the method might otherwise accept as valid. Failure to do so can result in privilege escalation and execution of arbitrary code.

Noncompliant Code Example

This noncompliant code example shows the two-argument doPrivileged() method that takes an access control context as the second argument. This code restores privileges from a previously saved context.


AccessController.doPrivileged(
new PrivilegedAction<Void>() {
public Void run() {
// ...
}
}, accessControlContext);


When passed a null access control context, the two-argument doPrivileged() method fails to reduce the current privileges to those of the previously saved context. Consequently, this code can grant excess privileges when the accessControlContext argument is null. Programmers who intend to call AccessController.doPrivileged() with a null access control context should explicitly pass the null constant or use the one-argument version of AccessController.doPrivileged().

Compliant Solution

This compliant solution prevents granting of excess privileges by ensuring that accessControlContext is non-null:


if (accessControlContext == null) {
throw new SecurityException("Missing AccessControlContext");
}
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
public Void run() {
// ...
}
}, accessControlContext);


Applicability

Security-sensitive methods must be thoroughly understood and their parameters validated to prevent corner cases with unexpected argument values (such as null arguments). If unexpected argument values are passed to security-sensitive methods, arbitrary code execution becomes possible, and privilege escalation becomes likely.

Bibliography

[API 2013]

AccessController.doPrivileged(), System.setSecurityManager()

5. Prevent arbitrary file upload

Java applications, including web applications, that 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, it can compromise application-layer defenses. For example, an application that permits HTML files to be uploaded could allow malicious code to be executed—an attacker can submit a valid HTML file with a cross-site scripting (XSS) payload that will execute in the absence of an output-escaping routine. For this reason, many applications restrict the type of files that can be uploaded.

It may also be possible to upload files with dangerous extensions such as .exe and .sh that could cause arbitrary code execution on server-side applications. An application that restricts only the Content-Type field in the HTTP header could be vulnerable to such an attack.

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


<s:form action="doUpload" method="POST"
enctype="multipart/form-data">
<s:file name="uploadFile" label="Choose File" size="40" />
<s:submit value="Upload" name="submit" />
</s:form>


Many Java enterprise frameworks provide configuration settings intended to be used as a defense against arbitrary file upload. Unfortunately, most of them fail to provide adequate protection. Mitigation of this vulnerability involves checking file size, content type, and file contents, among other metadata attributes.

Noncompliant Code Example

This noncompliant code example shows XML code from the upload action of a Struts 2 application. The interceptor code is responsible for allowing file uploads.


<action name="doUpload" class="com.example.UploadAction">
<interceptor-ref name="fileUpload">
<param name="maximumSize"> 10240 </param>
<param name="allowedTypes">
text/plain,image/JPEG,text/html
</param>
</interceptor-ref>
</action>


The code for file upload appears in the UploadAction class:


public class UploadAction extends ActionSupport {
private File uploadedFile;
// setter and getter for uploadedFile

public String execute() {
try {
// File path and file name are hardcoded for illustration
File fileToCreate = new File("filepath", "filename");
// Copy temporary file content to this file
FileUtils.copyFile(uploadedFile, fileToCreate);
return "SUCCESS";
} catch (Throwable e) {
addActionError(e.getMessage());
return "ERROR";
}
}
}


The value of the parameter type maximumSize ensures that a particular Action cannot receive a very large file. The allowedTypes parameter defines the type of files that are accepted. However, this approach fails to ensure that the uploaded file conforms to the security requirements because interceptor checks can be trivially bypassed. If an attacker were to use a proxy tool to change the content type in the raw HTTP request in transit, the framework would fail to prevent the file’s upload. Consequently, an attacker could upload a malicious file that has a .exe extension, for example.

Compliant Solution

The file upload must 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. This compliant solution uses the Apache Tika library [Apache 2013] 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 class UploadAction extends ActionSupport {
private File uploadedFile;
// setter and getter for uploadedFile

public String execute() {
try {
// File path and file name are hardcoded for illustration
File fileToCreate = new File("filepath", "filename");

boolean textPlain = checkMetaData(uploadedFile,
"text/plain");
boolean img = checkMetaData(uploadedFile, "image/JPEG");
boolean textHtml = checkMetaData(uploadedFile,
"text/html");

if (!textPlain || !img || !textHtml) {
return "ERROR";
}

// Copy temporary file content to this file
FileUtils.copyFile(uploadedFile, fileToCreate);
return "SUCCESS";
} catch (Throwable e) {
addActionError(e.getMessage());
return "ERROR";
}
}

public static boolean checkMetaData(
File f, String getContentType) {
try (InputStream is = new FileInputStream(f)) {
ContentHandler contenthandler = new BodyContentHandler();
Metadata metadata = new Metadata();
metadata.set(Metadata.RESOURCE_NAME_KEY, f.getName());
Parser parser = new AutoDetectParser();
try {
parser.parse(is, contenthandler,
metadata, new ParseContext());
} catch (SAXException | TikaException e) {
// Handle error
return false;
}

if (metadata.get(Metadata.CONTENT_TYPE).equalsIgnoreCase(
getContentType)) {
return true;
} else {
return false;
}
} catch (IOException e) {
// Handle error
return false;
}
}
}


The AutoDetectParser selects the best available parser on the basis of the content type of the file to be parsed.

Applicability

An arbitrary file upload vulnerability could result in privilege escalation and the execution of arbitrary code.

Bibliography

[Apache 2013]

Apache Tika: A Content Analysis Toolkit

6. Properly encode or escape output

Proper input sanitization can prevent insertion of malicious data into a subsystem such as a database. However, different subsystems require different types of sanitization. Fortunately, it is usually obvious which subsystems will eventually receive which inputs, and consequently what type of sanitization is required.

Several subsystems exist for the purpose of outputting data. An HTML renderer is one common subsystem for displaying output. Data sent to an output subsystem may appear to originate from a trusted source. However, it is dangerous to assume that output sanitization is unnecessary, because such data may indirectly originate from an untrusted source and may include malicious content. Failure to properly sanitize data passed to an output subsystem can allow several types of attacks. For example, HTML renderers are prone to HTML injection and cross-site scripting (XSS) attacks [OWASP 2011]. Output sanitization to prevent such attacks is as vital as input sanitization.

As with input validation, data should be normalized before sanitizing it for malicious characters. Properly encode all output characters other than those known to be safe to avoid vulnerabilities caused by data that bypasses validation. See The CERT® Oracle® Secure Coding Standard for Java [Long 2012], “IDS01-J. Normalize strings before validating them,” for more information.

Noncompliant Code Example

This noncompliant code example uses the model–view–controller (MVC) concept of the Java EE–based Spring Framework to display data to the user without encoding or escaping it. Because the data is sent to a web browser, the code is subject to both HTML injection and XSS attacks.


@RequestMapping("/getnotifications.htm")
public ModelAndView getNotifications(
HttpServletRequest request, HttpServletResponse response) {
ModelAndView mv = new ModelAndView();
try {
UserInfo userDetails = getUserInfo();
List<Map<String,Object>> list =
new ArrayList<Map<String, Object>>();
List<Notification> notificationList =
NotificationService.getNotificationsForUserId(
userDetails.getPersonId());

for (Notification notification: notificationList) {
Map<String,Object> map = new HashMap<String, Object>();
map.put("id", notification.getId());
map.put("message", notification.getMessage());
list.add(map);
}

mv.addObject("Notifications", list);
} catch (Throwable t) {
// Log to file and handle
}

return mv;
}


Compliant Solution

This compliant solution defines a ValidateOutput class that normalizes the output to a known character set, performs output sanitization using a whitelist, and encodes any unspecified data values to enforce a double-checking mechanism. Note that the required whitelisting patterns will vary according to the specific needs of different fields [OWASP 2013].


public class ValidateOutput {
// Allows only alphanumeric characters and spaces
private static final Pattern pattern =
Pattern.compile("^[a-zA-Z0-9\\s]{0,20}$");

// Validates and encodes the input field based on a whitelist
public String validate(String name, String input)
throws ValidationException {
String canonical = normalize(input);

if (!pattern.matcher(canonical).matches()) {
throw new ValidationException("Improper format in " +
name + " field");
}

// Performs output encoding for nonvalid characters
canonical = HTMLEntityEncode(canonical);
return canonical;
}

// Normalizes to known instances
private String normalize(String input) {
String canonical =
java.text.Normalizer.normalize(input,
Normalizer.Form.NFKC);
return canonical;
}

// Encodes nonvalid data
private static String HTMLEntityEncode(String input) {
StringBuffer sb = new StringBuffer();

for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
if (Character.isLetterOrDigit(ch) ||
Character.isWhitespace(ch)) {
sb.append(ch);
} else {
sb.append("&#" + (int)ch + ";");
}
}
return sb.toString();
}
}

// ...

@RequestMapping("/getnotifications.htm")
public ModelAndView getNotifications(HttpServletRequest request,
HttpServletResponse response) {
ValidateOutput vo = new ValidateOutput();

ModelAndView mv = new ModelAndView();
try {
UserInfo userDetails = getUserInfo();
List<Map<String,Object>> list =
new ArrayList<Map<String,Object>>();
List<Notification> notificationList =
NotificationService.getNotificationsForUserId(
serDetails.getPersonId());

for (Notification notification: notificationList) {
Map<String,Object> map = new HashMap<String,Object>();
map.put("id", vo.validate("id" ,notification.getId()));
map.put("message",
vo.validate("message", notification.getMessage()));
list.add(map);
}

mv.addObject("Notifications", list);
}
catch (Throwable t) {
// Log to file and handle
}

return mv;
}


Output encoding and escaping is mandatory when accepting dangerous characters such as double quotes and angle braces. Even when input is whitelisted to disallow such characters, output escaping is recommended because it provides a second level of defense. Note that the exact escape sequence can vary depending on where the output is embedded. For example, untrusted output may occur in an HTML value attribute, CSS, URL, or script; the output encoding routine will be different in each case. It is also impossible to securely use untrusted data in some contexts. Consult the OWASP XSS (Cross-Site Scripting) Prevention Cheat Sheet for more information on preventing XSS attacks (www.owasp.org/index.php/XSS_Prevention_Cheat_Sheet).

Applicability

Failure to encode or escape output before it is displayed or passed across a trust boundary can result in the execution of arbitrary code.

Related Vulnerabilities

The Apache GERONIMO-1474 vulnerability, reported in January 2006, allowed attackers to submit URLs containing JavaScript. The Web Access Log Viewer failed to sanitize the data it forwarded to the administrator console, thereby enabling a classic XSS attack.

Bibliography

[Long 2012]

IDS01-J. Normalize strings before validating them

[OWASP 2011]

Cross-Site Scripting (XSS)

[OWASP 2013]

How to Add Validation Logic to HttpServletRequest XSS (Cross-Site Scripting) Prevention Cheat Sheet

7. Prevent code injection

Code injection can occur when untrusted input is injected into dynamically constructed code. One obvious source of potential vulnerabilities is the use of JavaScript from Java code. The javax.script package consists of interfaces and classes that define Java scripting engines and a framework for the use of those interfaces and classes in Java code. Misuse of the javax.script API permits an attacker to execute arbitrary code on the target system.

This guideline is a specific instance of The CERT® Oracle® Secure Coding Standard for Java [Long 2012], “IDS00-J. Sanitize untrusted data passed across a trust boundary.”

Noncompliant Code Example

This noncompliant code example incorporates untrusted user input into a JavaScript statement that is responsible for printing the input:


private static void evalScript(String firstName)
throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
engine.eval("print('"+ firstName + "')");
}


An attacker can enter a specially crafted argument in an attempt to inject malicious JavaScript. This example shows a malicious string that contains JavaScript code that can create or overwrite an existing file on a vulnerable system.


dummy\');
var bw = new JavaImporter(java.io.BufferedWriter);
var fw = new JavaImporter(java.io.FileWriter);
with(fw) with(bw) {
bwr = new BufferedWriter(new FileWriter(\"config.cfg\"));
bwr.write(\"some text\"); bwr.close();
}
// ;


The script in this example prints “dummy” and then writes “some text” to a configuration file called config.cfg. An actual exploit can execute arbitrary code.

Compliant Solution (Whitelisting)

The best defense against code injection vulnerabilities is to prevent the inclusion of executable user input in code. User input used in dynamic code must be sanitized, for example, to ensure that it contains only valid, whitelisted characters. Santization is best performed immediately after the data has been input, using methods from the data abstraction used to store and process the data. Refer to “IDS00-J. Sanitize untrusted data passed across a trust boundary” [Long 2012] for more details. If special characters must be permitted in the name, they must be normalized before comparison with their equivalent forms for the purpose of input validation. This compliant solution uses whitelisting to prevent unsanitized input from being interpreted by the scripting engine.


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 manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
engine.eval("print('"+ firstName + "')");
}


Compliant Solution (Secure Sandbox)

An alternative approach is to create a secure sandbox using a security manager (see Guideline 20, “Create a secure sandbox using a security manager”). The application should prevent the script from executing arbitrary commands, such as querying the local file system. The two-argument form of doPrivileged() can be used to lower privileges when the application must operate with higher privileges, but the scripting engine must not. The RestrictedAccessControlContext reduces the permissions granted in the default policy file to those of the newly created protection domain. The effective permissions are the intersection of the permissions of the newly created protection domain and the systemwide security policy. Refer to Guideline 16, “Avoid granting excess privileges,” for more details on the two-argument form of doPrivileged().

This compliant solution illustrates the use of an AccessControlContext in the two-argument form of doPrivileged().


class ACC {
private static class RestrictedAccessControlContext {
private static final AccessControlContext INSTANCE;

static {
INSTANCE =
new AccessControlContext(
new ProtectionDomain[] {
new ProtectionDomain(null, null) // No permissions
});
}
}

private static void evalScript(final String firstName)
throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
final ScriptEngine engine =
manager.getEngineByName("javascript");
// Restrict permission using the two-argument
// form of doPrivileged()
try {
AccessController.doPrivileged(
new PrivilegedExceptionAction<Object>() {

public Object run() throws ScriptException {
engine.eval("print('" + firstName + "')");
return null;
}
},
// From nested class
RestrictedAccessControlContext.INSTANCE);

} catch (PrivilegedActionException pae) {
// Handle error
}
}
}


This approach can be combined with whitelisting for additional security.

Applicability

Failure to prevent code injection can result in the execution of arbitrary code.

Bibliography

[API 2013]

Package javax.script

[Long 2012]

IDS00-J. Sanitize untrusted data passed across a trust boundary

[OWASP 2013]

Code Injection in Java

8. Prevent XPath injection

Extensible Markup Language (XML) can be used for data storage in a manner similar to 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 (see The CERT® Oracle® Secure Coding Standard for Java [Long 2012], “IDS00-J. Sanitize untrusted data passed across a trust boundary”). An attacker can enter valid SQL or XML constructs in the data fields of the query in use. In typical attacks, the conditional field of the query resolves to a tautology or otherwise gives the attacker access to privileged information.

This guideline is a specific example of the broadly scoped Guideline 7, “Prevent code injection.”

XML Path Injection Example

Consider the following XML schema.


<users>
<user>
<username>Utah</username>
<password>e90205372a3b89e2</password>
</user>
<user>
<username>Bohdi</username>
<password>6c16b22029df4ec6</password>
</user>
<user>
<username>Busey</username>
<password>ad39b3c2a4dabc98</password>
</user>
</users>


The passwords are hashed in compliance with Guideline 13, “Store passwords using a hash function.” MD5 hashes are shown here for illustrative purposes; in practice, you should use a safer algorithm such as SHA-256.

Untrusted code may attempt to retrieve user details from this file with an XPath statement constructed dynamically from user input.

//users/user[username/text()='&LOGIN&' and
password/text()='&PASSWORD&' ]

If an attacker knows that Utah is a valid user name, he or she can specify an input such as

Utah' or '1'='1

This yields the following query string.

//users/user[username/text()='Utah' or '1'='1'
and password/text()='xxxx']

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 Example

This noncompliant code example reads a user name and password from the user and uses them to construct the query string. The password is passed as a char array and then hashed. This example is vulnerable to the attack described earlier. If the attack string described earlier is passed toevaluate(), the method call returns the corresponding node in the XML file, causing the doLogin() method to return true and bypass any authorization.


private boolean doLogin(String userName, char[] password)
throws ParserConfigurationException, SAXException,
IOException, XPathExpressionException {

DocumentBuilderFactory domFactory =
DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true);
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;

// Print first names to the console
for (int i = 0; i < nodes.getLength(); i++) {
Node node =
nodes.item(i).getChildNodes().item(1).
getChildNodes().item(0);
System.out.println(
"Authenticated: " + node.getNodeValue()
);
}

return (nodes.getLength() >= 1);
}


Compliant Solution (XQuery)

XPath injection can be prevented by adopting defenses similar to those used to prevent SQL injection.

Image Treat all user input as untrusted, and perform appropriate sanitization.

Image When sanitizing user input, verify the correctness of the data type, length, format, and content. For example, use a regular expression that checks for XML tags and special characters in user input. This practice corresponds to input sanitization. See Guideline 7, “Prevent code injection,” for additional details.

Image In a client–server application, perform validation at both the client and the server sides.

Image Extensively test applications that supply, propagate, or accept user input.

An effective technique for preventing the related issue of SQL injection is parameterization. Parameterization ensures that user-specified data is passed to an API as a parameter such that the data is never interpreted as executable content. Unfortunately, Java SE currently lacks an analogous interface for XPath queries. However, an XPath analog to SQL parameterization can be emulated by using an interface such as XQuery that supports specifying a query statement in a separate file supplied at runtime.

Input File: login.xq


declare variable $userName as xs:string external;
declare variable $password as xs:string external;
//users/user[@userName=$userName and @password=$password]


This compliant solution uses a query specified in a text file by reading the file in the required format and then inserting values for the user name and password in a Map. The XQuery library constructs the XML query from these inputs.


private boolean doLogin(String userName, String pwd)
throws ParserConfigurationException, SAXException,
IOException, XPathExpressionException {

DocumentBuilderFactory domFactory =
DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true);
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();

// Print first names to the console
for (int i = 0; i < nodes.getLength(); i++) {
Node node =
nodes.item(i).getChildNodes().item(1).
getChildNodes().item(0);
System.out.println(node.getNodeValue());
}

return (nodes.getLength() >= 1);
}


Using this method, the data specified in the userName and password fields cannot be interpreted as executable content at runtime.

Applicability

Failure to validate user input may result in information disclosure and execution of unprivileged code.

According to OWASP [OWASP 2013],

[Prevention of XPath injection] requires the following characters to be removed (that is, prohibited) or properly escaped.

Image < > / ' = " to prevent straight parameter injection.

Image XPath queries should not contain any meta characters (such as ' = * ? // or similar).

Image XSLT expansions should not contain any user input, or if they do, [you should] comprehensively test the existence of the file, and ensure that the files are within the bounds set by the Java 2 Security Policy.

Bibliography

[Fortify 2013]

“Input Validation and Representation: XML Injection”

[Long 2012]

IDS00-J. Sanitize untrusted data passed across a trust boundary

[OWASP 2013]

Testing for XPath Injection

[Sen 2007]

Avoid the Dangers of XPath Injection

[Oracle 2011b]

Ensure Data Security

9. Prevent LDAP injection

The Lightweight Directory Access Protocol (LDAP) allows an application to remotely perform operations such as searching and modifying records in directories. LDAP injection results from inadequate input sanitization and validation, and allows malicious users to glean restricted information using the directory service.

A whitelist can be used to restrict input to a list of valid characters. Characters and character sequences that must be excluded from whitelists—including Java Naming and Directory Interface (JNDI) metacharacters and LDAP special characters—are listed in Table 1–1.

Image

Table 1–1. Characters and sequences to exclude from whitelists

LDAP Injection Example

Consider an LDAP Data Interchange Format (LDIF) file that contains records in the following format:


dn: dc=example,dc=com
objectclass: dcobject
objectClass: organization
o: Some Name
dc: example

dn: ou=People,dc=example,dc=com
ou: People
objectClass: dcobject
objectClass: organizationalUnit
dc: example

dn: cn=Manager,ou=People,dc=example,dc=com
cn: Manager
sn: John Watson
# Several objectClass definitions here (omitted)
userPassword: secret1
mail: john@holmesassociates.com

dn: cn=Senior Manager,ou=People,dc=example,dc=com
cn: Senior Manager
sn: Sherlock Holmes
# Several objectClass definitions here (omitted)
userPassword: secret2
mail: sherlock@holmesassociates.com


A search for a valid user name and password often takes the form

(&(sn=<USERSN>)(userPassword=<USERPASSWORD>))

However, an attacker could bypass authentication by using S* for the USERSN field and * for the USERPASSWORD field. Such input would yield every record whose USERSN field began with S.

An authentication routine that permitted LDAP injection would allow unauthorized users to log in. Likewise, a search routine would allow an attacker to discover part or all of the data in the directory.

Noncompliant Code Example

This noncompliant code example allows a caller of the method searchRecord() to search for a record in the directory using the LDAP protocol. The string filter is used to filter the result set for those entries that match a user name and password supplied by the caller.


// String userSN = "S*"; // Invalid
// String userPassword = "*"; // Invalid
public class LDAPInjection {
private void searchRecord(String userSN, String userPassword)
throws NamingException {
Hashtable<String, String> env =
new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
try {
DirContext dctx = new InitialDirContext(env);

SearchControls sc = new SearchControls();
String[] attributeFilter = {"cn", "mail"};
sc.setReturningAttributes(attributeFilter);
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
String base = "dc=example,dc=com";

// The following resolves to (&(sn=S*)(userPassword=*))
String filter = "(&(sn=" + userSN + ")(userPassword=" +
userPassword + "))";
NamingEnumeration<?> results =
dctx.search(base, filter, sc);
while (results.hasMore()) {
SearchResult sr = (SearchResult) results.next();
Attributes attrs = (Attributes) sr.getAttributes();
Attribute attr = (Attribute) attrs.get("cn");
System.out.println(attr);
attr = (Attribute) attrs.get("mail");
System.out.println(attr);
}
dctx.close();
} catch (NamingException e) {
// Forward to handler
}
}
}


When a malicious user enters specially crafted input, as outlined previously, this elementary authentication scheme fails to confine the output of the search query to the information for which the user has access privileges.

Compliant Solution

This compliant solution uses a whitelist to sanitize user input so that the filter string contains only valid characters. In this code, userSN may contain only letters and spaces, whereas a password may contain only alphanumeric characters.


// String userSN = "Sherlock Holmes"; // Valid
// String userPassword = "secret2"; // Valid

// ... beginning of LDAPInjection.searchRecord() ...
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
String base = "dc=example,dc=com";

if (!userSN.matches("[\\w\\s]*") ||
!userPassword.matches("[\\w]*")) {
throw new IllegalArgumentException("Invalid input");
}

String filter = "(&(sn = " + userSN + ")(userPassword=" +
userPassword + "))";

// ... remainder of LDAPInjection.searchRecord() ...


When a database field such as a password must include special characters, it is critical to ensure that the authentic data is stored in sanitized form in the database and also that any user input is normalized before the validation or comparison takes place. Using characters that have special meanings in JNDI and LDAP in the absence of a comprehensive normalization and whitelisting-based routine is discouraged. Special characters must be transformed to sanitized, safe values before they are added to the whitelist expression against which input will be validated. Likewise, normalization of user input should occur before the validation step.

Applicability

Failure to sanitize untrusted input can result in information disclosure and privilege escalation.

Bibliography

[OWASP 2013]

Preventing LDAP Injection in Java

10. Do not use the clone() method to copy untrusted method parameters

Making defensive copies of mutable method parameters mitigates against a variety of security vulnerabilities; see The CERT® Oracle® Secure Coding Standard for Java [Long 2012], “OBJ06-J. Defensively copy mutable inputs and mutable internal components,” for additional information. However, inappropriate use of the clone() method can allow an attacker to exploit vulnerabilities by providing arguments that appear normal but subsequently return unexpected values. Such objects may consequently bypass validation and security checks. When such a class might be passed as an argument to a method, treat the argument as untrusted, and do not use the clone() method provided by the class. Also, do not use the clone() method of nonfinal classes to make defensive copies.

This guideline is a specific instance of Guideline 15, “Do not rely on methods that can be overridden by untrusted code.”

Noncompliant Code Example

This noncompliant code example defines a validateValue() method that validates a time value:


private Boolean validateValue(long time) {
// Perform validation
return true; // If the time is valid
}

private void storeDateInDB(java.util.Date date)
throws SQLException {
final java.util.Date copy = (java.util.Date)date.clone();
if (validateValue(copy.getTime())) {
Connection con =
DriverManager.getConnection(
"jdbc:microsoft:sqlserver://<HOST>:1433",
"<UID>", "<PWD>"
);
PreparedStatement pstmt =
con.prepareStatement("UPDATE ACCESSDB SET TIME = ?");
pstmt.setLong(1, copy.getTime());
// ...
}
}


The storeDateInDB() method accepts an untrusted date argument and attempts to make a defensive copy using its clone() method. This allows an attacker to take control of the program by creating a malicious date class that extends Date. If the attacker’s code runs with the same privileges as store-DateInDB(), the attacker merely embeds malicious code inside their clone() method:


class MaliciousDate extends java.util.Date {
@Override
public MaliciousDate clone() {
// malicious code goes here
}
}


If, however, the attacker can only provide a malicious date with lessened privileges, the attacker can bypass validation but still confound the remainder of the program. Consider this example:


public class MaliciousDate extends java.util.Date {
private static int count = 0;

@Override
public long getTime() {
java.util.Date d = new java.util.Date();
return (count++ == 1) ? d.getTime() : d.getTime() - 1000;
}
}


This malicious date will appear to be a benign date object the first time that getTime() is invoked. This allows it to bypass validation in the storeDateInDB() method. However, the time that is actually stored in the database will be incorrect.

Compliant Solution

This compliant solution avoids using the clone() method. Instead, it creates a new java.util.Date object that is subsequently used for access control checks and insertion into the database.


private void storeDateInDB(java.util.Date date)
throws SQLException {
final java.util.Date copy = new java.util.Date(date.getTime());
if (validateValue(copy.getTime())) {
Connection con =
DriverManager.getConnection(
"jdbc:microsoft:sqlserver://<HOST>:1433",
"<UID>", "<PWD>"
);
PreparedStatement pstmt =
con.prepareStatement("UPDATE ACCESSDB SET TIME = ?");
pstmt.setLong(1, copy.getTime());
// ...
}
}


Noncompliant Code Example (CVE-2012-0507)

This noncompliant code example shows a constructor of the Java core class AtomicReferenceArray present in the Java 1.7.0 update 2:


public AtomicReferenceArray(E[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}


This code was subsequently invoked by the Flashback exploit that infected 600,000 Macintosh computers in April 2012.1

1. “Exploiting Java Vulnerability CVE-2012-0507 Using Metasploit” is shared by user BreakTheSec on Slideshare.net (July 14, 2012); see www.slideshare.net/BreakTheSec/exploiting-java-vulnerability.

Compliant Solution (CVE-2012-0507)

In Java 1.7.0 update 3, the constructor was modified to use the Arrays.copyOf() method instead of the clone() method as follows:


public AtomicReferenceArray(E[] array) {
// Visibility guaranteed by final field guarantees
this.array = Arrays.copyOf(
array, array.length, Object[].class);
}


Applicability

Using the clone() method to copy untrusted arguments affords attackers the opportunity to execute arbitrary code.

Bibliography

[Long 2012]

OBJ06-J. Defensively copy mutable inputs and mutable internal components

[Sterbenz 2006]

Secure Coding Antipatterns: Avoiding Vulnerabilities

11. Do not use Object.equals() to compare cryptographic keys

The method java.lang.Object.equals(), by default, is unable to compare composite objects such as cryptographic keys. Most Key classes fail to provide an equals() implementation that overrides Object.equals(). In such cases, the components of the composite object must be compared individually to ensure correctness.

Noncompliant Code Example

This noncompliant code example compares two keys using the equals() method. The keys may compare as unequal even when they represent the same value.


private static boolean keysEqual(Key key1, Key key2) {
if (key1.equals(key2)) {
return true;
}
return false;
}


Compliant Solution

This compliant solution uses the equals() method as a first test, and then compares the encoded version of the keys to facilitate provider-independent behavior. It checks whether an RSAPrivateKey and an RSAPrivateCrtKey represent equivalent private keys [Oracle 2011b].


private static boolean keysEqual(Key key1, Key key2) {
if (key1.equals(key2)) {
return true;
}

if (Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
return true;
}

// More code for different types of keys here
// For example, the following code can check whether
// an RSAPrivateKey and an RSAPrivateCrtKey are equal
if ((key1 instanceof RSAPrivateKey) &&
(key2 instanceof RSAPrivateKey)) {

if ((((RSAKey) key1).getModulus().equals(
((RSAKey) key2).getModulus())) &&
(((RSAPrivateKey) key1).getPrivateExponent().equals(
((RSAPrivateKey) key2).getPrivateExponent()))) {
return true;
}
}
return false;
}


Automated Detection

Using Object.equals() to compare cryptographic keys may yield unexpected results.

Bibliography

[API 2013]

java.lang.Object.equals(), Object.equals()

[Oracle 2011b]

Determining If Two Keys Are Equal (JCA Reference Guide)

12. Do not use insecure or weak cryptographic algorithms

Security-intensive applications must avoid use of insecure or weak cryptographic primitives. The computational capacity of modern computers permits circumvention of such cryptography via brute-force attacks. For example, the Data Encryption Standard (DES) encryption algorithm is considered highly insecure; messages encrypted using DES have been decrypted by brute force within a single day by machines such as the Electronic Frontier Foundation’s (EFF) Deep Crack.

Noncompliant Code Example

This noncompliant code example encrypts a String input using a weak cryptographic algorithm (DES):


SecretKey key = KeyGenerator.getInstance("DES").generateKey();
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);

// Encode bytes as UTF8; strToBeEncrypted contains
// the input string that is to be encrypted
byte[] encoded = strToBeEncrypted.getBytes("UTF8");

// Perform encryption
byte[] encrypted = cipher.doFinal(encoded);


Compliant Solution

This compliant solution uses the more secure Advanced Encryption Standard (AES) algorithm to perform the encryption.


Cipher cipher = Cipher.getInstance("AES");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may be unavailable

SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();

SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);

// Encode bytes as UTF8; strToBeEncrypted contains the
// input string that is to be encrypted
byte[] encoded = strToBeEncrypted.getBytes("UTF8");

// Perform encryption
byte[] encrypted = cipher.doFinal(encoded);


Applicability

Use of mathematically and computationally insecure cryptographic algorithms can result in the disclosure of sensitive information.

Weak cryptographic algorithms can be disabled in Java SE 7; see the Java PKI Programmer’s Guide, Appendix D, “Disabling Cryptographic Algorithms” [Oracle 2011a].

Weak cryptographic algorithms may be used in scenarios that specifically call for a breakable cipher. For example, the ROT13 cipher is commonly used on bulletin boards and web sites when the purpose of encryption is to protect people from information, rather than to protect information from people.

Bibliography

[Oracle 2011a]

Appendix D, “Disabling Cryptographic Algorithms”

[Oracle 2013b]

Java Cryptography Architecture (JCA) Reference Guide

13. Store passwords using a hash function

Programs that store passwords as cleartext (unencrypted text data) risk exposure of those passwords in a variety of ways. Although programs generally receive passwords from users as cleartext, they should ensure that the passwords are not stored as cleartext.

An acceptable technique for limiting the exposure of passwords is the use of hash functions, which allow programs to indirectly compare an input password to the original password string without storing a cleartext or decryptable version of the password. This approach minimizes the exposure of the password without presenting any practical disadvantages.

Cryptographic Hash Functions

The value produced by a hash function is the hash value or message digest. Hash functions are computationally feasible functions whose inverses are computationally infeasible. In practice, a password can be encoded to a hash value, but decoding remains infeasible. The equality of the passwords can be tested through the equality of their hash values.

A good practice is to always append a salt to the password being hashed. A salt is a unique (often sequential) or randomly generated piece of data that is stored along with the hash value. The use of a salt helps prevent brute-force attacks against the hash value, provided that the salt is long enough to generate sufficient entropy (shorter salt values cannot significantly slow down a brute-force attack). Each password should have its own salt associated with it. If a single salt were used for more than one password, two users would be able to see whether their passwords are the same.

The choice of hash function and salt length presents a trade-off between security and performance. Increasing the effort required for effective brute-force attacks by choosing a stronger hash function can also increase the time required to validate a password. Increasing the length of the salt makes brute-force attacks more difficult but requires additional storage space.

Java’s MessageDigest class provides implementations of various cryptographic hash functions. Avoid defective functions such as the Message-Digest Algorithm (MD5). Hash functions such as Secure Hash Algorithm (SHA)-1 and SHA-2 are maintained by the National Security Agency, and are currently considered safe. In practice, many applications use SHA-256 because this hash function has reasonable performance while still being considered secure.

Noncompliant Code Example

This noncompliant code example encrypts and decrypts the password stored in password.bin using a symmetric key algorithm.


public final class Password {
private void setPassword(byte[] pass) throws Exception {
// Arbitrary encryption scheme
bytes[] encrypted = encrypt(pass);
clearArray(pass);
// Encrypted password to password.bin
saveBytes(encrypted,"password.bin");
clearArray(encrypted);
}

boolean checkPassword(byte[] pass) throws Exception {
// Load the encrypted password
byte[] encrypted = loadBytes("password.bin");
byte[] decrypted = decrypt(encrypted);
boolean arraysEqual = Arrays.equal(decrypted, pass);
clearArray(decrypted);
clearArray(pass);
return arraysEqual;
}

private void clearArray(byte[] a) {
for (int i = 0; i < a.length; i++) {
a[i] = 0;
}
}
}


An attacker could potentially decrypt this file to discover the password, particularly when the attacker has knowledge of the key and encryption scheme used by the program. Passwords should be protected even from system administrators and privileged users. Consequently, using encryption is only partly effective in mitigating password disclosure threats.

Noncompliant Code Example

This noncompliant code example uses the SHA-256 hash function through the MessageDigest class to compare hash values instead of cleartext strings, but it uses a String to store the password:


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public final class Password {
private void setPassword(String pass) throws Exception {
byte[] salt = generateSalt(12);
MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
// Encode the string and salt
byte[] hashVal = msgDigest.digest((pass+salt).getBytes());
saveBytes(salt, "salt.bin");
// Save the hash value to password.bin
saveBytes(hashVal,"password.bin");
}

boolean checkPassword(String pass) throws Exception {
byte[] salt = loadBytes("salt.bin");
MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
// Encode the string and salt
byte[] hashVal1 = msgDigest.digest((pass+salt).getBytes());
// Load the hash value stored in password.bin
byte[] hashVal2 = loadBytes("password.bin");
return Arrays.equals(hashVal1, hashVal2);
}

private byte[] generateSalt(int n) {
// Generate a random byte array of length n
}
}


Even when an attacker knows that the program stores passwords using SHA-256 and a 12-byte salt, he or she will be unable to retrieve the actual password from password.bin and salt.bin.

Although this approach solves the decryption problem from the previous noncompliant code example, this program may inadvertently store the passwords as cleartext in memory. Java String objects are immutable, and can be copied and internally stored by the Java Virtual Machine. Consequently, Java lacks a mechanism to securely erase a password once it has been stored in a String. See Guideline 1, “Limit the lifetime of sensitive data,” for more information.

Compliant Solution

This compliant solution addresses the problems from the previous noncompliant code example by using a byte array to store the password:


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public final class Password {

private void setPassword(byte[] pass) throws Exception {
byte[] salt = generateSalt(12);
byte[] input = appendArrays(pass, salt);
MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
// Encode the string and salt
byte[] hashVal = msgDigest.digest(input);
clearArray(pass);
clearArray(input);
saveBytes(salt, "salt.bin");
// Save the hash value to password.bin
saveBytes(hashVal,"password.bin");
clearArray(salt);
clearArray(hashVal);
}

boolean checkPassword(byte[] pass) throws Exception {
byte[] salt = loadBytes("salt.bin");
byte[] input = appendArrays(pass, salt);
MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
// Encode the string and salt
byte[] hashVal1 = msgDigest.digest(input);
clearArray(pass);
clearArray(input);
// Load the hash value stored in password.bin
byte[] hashVal2 = loadBytes("password.bin");
boolean arraysEqual = Arrays.equals(hashVal1, hashVal2);
clearArray(hashVal1);
clearArray(hashVal2);
return arraysEqual;
}

private byte[] generateSalt(int n) {
// Generate a random byte array of length n
}

private byte[] appendArrays(byte[] a, byte[] b) {
// Return a new array of a[] appended to b[]
}

private void clearArray(byte[] a) {
for (int i = 0; i < a.length; i++) {
a[i] = 0;
}
}
}


In both the setPassword() and checkPassword() methods, the cleartext representation of the password is erased immediately after it is converted into a hash value. Consequently, attackers must work harder to retrieve the cleartext password after the erasure. Providing guaranteed erasure is extremely challenging, is likely to be platform specific, and may even be impossible because of copying garbage collectors, dynamic paging, and other platform features that operate below the level of the Java language.

Applicability

Passwords stored without a secure hash can be exposed to malicious users. Violations of this guideline generally have a clear exploit associated with them.

Applications such as password managers may need to retrieve the original password to enter it into a third-party application. This is permitted even though it violates this guideline. The password manager is accessed by a single user, and always has the user’s permission to store his or her passwords and to display those passwords on command. Consequently, the limiting factor to safety and security is the user’s competence rather than the program’s operation.

Bibliography

[API 2013]

Class MessageDigest

Class String

[Hirondelle 2013]

Passwords Never Clear in Text

[OWASP 2012]

“Why Add Salt?”

[Paar 2010]

Chapter 11, “Hash Functions”

14. Ensure that SecureRandom is properly seeded

Random number generation depends on a source of entropy such as signals, devices, or hardware inputs. Secure random number generation is also addressed by The CERT® Oracle® Secure Coding Standard for Java [Long 2012], “MSC02-J. Generate strong random numbers.”

The java.security.SecureRandom class is widely used for generating cryptographically strong random numbers. According to the java.security file present in the Java Runtime Environment’s lib/security folder [API 2013]:

Select the source of seed data for SecureRandom. By default an attempt is made to use the entropy gathering device specified by the securerandom.source property. If an exception occurs when accessing the URL then the traditional system/thread activity algorithm is used.

On Solaris and Linux systems, if file:/dev/urandom is specified and it exists, a special SecureRandom implementation is activated by default. This “NativePRNG” reads random bytes directly from /dev/urandom. On Windows systems, the URLs file:/dev/random andfile:/dev/urandom enables use of the Microsoft CryptoAPI seed functionality.

An adversary should not be able to determine the original seed given several samples of random numbers. If this restriction is violated, all future random numbers may be successfully predicted by the adversary.

Noncompliant Code Example

This noncompliant code example constructs a secure random number generator that is seeded with the specified seed bytes.


SecureRandom random = new SecureRandom(
String.valueOf(new Date().getTime()).getBytes()
);


This constructor searches a registry of security providers and returns the first provider that supports secure random number generation. If no such provider exists, an implementation-specific default is selected. Furthermore, the default system-provided seed is overridden by a seed provided by the programmer. Using the current system time as the seed is predictable, and can result in the generation of random numbers with insufficient entropy.

Compliant Solution

Prefer the no-argument constructor of SecureRandom that uses the system-specified seed value to generate a 128-byte-long random number.


byte[] randomBytes = new byte[128];
SecureRandom random = new SecureRandom();
random.nextBytes(randomBytes);


It is also good practice to specify the exact random number generator and provider for better portability.

Applicability

Insufficiently secure random numbers enable attackers to gain specific information about the context in which they are used.

Insecure random numbers are useful in some contexts that do not require security. These are addressed in the exceptions to “MSC02-J. Generate strong random numbers” [Long 2012].

Bibliography

[API 2013]

SecureRandom

[Sethi 2009]

Proper Use of Java’s SecureRandom

[Long 2012]

MSC02-J. Generate strong random numbers

15. Do not rely on methods that can be overridden by untrusted code

Untrusted code can misuse APIs provided by trusted code to override methods such as Object.equals(), Object.hashCode(), and Thread.run(). These methods are valuable targets because they are commonly used behind the scenes and may interact with components in a way that is not easily discernible.

By providing overridden implementations, an attacker can use untrusted code to glean sensitive information, run arbitrary code, or launch a denial of service attack.

See Guideline 10, “Do not use the clone() method to copy untrusted method parameters,” for more specific details regarding overriding the Object.clone() method.

Noncompliant Code Example (hashCode)

This noncompliant code example shows a LicenseManager class that maintains a licenseMap. The map stores a LicenseType and license value pair.


public class LicenseManager {
Map<LicenseType, String> licenseMap =
new HashMap<LicenseType, String>();

public LicenseManager() {
LicenseType type = new LicenseType();
type.setType("demo-license-key");
licenseMap.put(type, "ABC-DEF-PQR-XYZ");
}
public Object getLicenseKey(LicenseType licenseType) {
return licenseMap.get(licenseType);
}
public void setLicenseKey(LicenseType licenseType,
String licenseKey) {
licenseMap.put(licenseType, licenseKey);
}
}

class LicenseType {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public int hashCode() {
int res = 17;
res = res * 31 + type == null ? 0 : type.hashCode();
return res;
}
@Override
public boolean equals(Object arg) {
if (arg == null || !(arg instanceof LicenseType)) {
return false;
}
if (type.equals(((LicenseType) arg).getType())) {
return true;
}
return false;
}
}


The constructor for LicenseManager initializes licenseMap with a demo license key that must remain secret. The license key is hard-coded for illustrative purposes; it should ideally be read from an external configuration file that stores an encrypted version of the key. The LicenseType class provides overridden implementations of equals() and hashCode() methods.

This implementation is vulnerable to an attacker who extends the LicenseType class and overrides the equals() and hashCode() methods:


public class CraftedLicenseType extends LicenseType {
private static int guessedHashCode = 0;
@Override
public int hashCode() {
// Returns a new hashCode to test every time get() is called
guessedHashCode++;
return guessedHashCode;
}
@Override
public boolean equals(Object arg) {
// Always returns true
return true;
}
}


The following is the malicious client program.


public class DemoClient {
public static void main(String[] args) {
LicenseManager licenseManager = new LicenseManager();
for (int i = 0; i <= Integer.MAX_VALUE; i++) {
Object guessed =
licenseManager.getLicenseKey(new CraftedLicenseType());
if (guessed != null) {
// prints ABC-DEF-PQR-XYZ
System.out.println(guessed);
}
}
}
}


The client program runs through the sequence of all possible hash codes using CraftedLicenseType until it successfully matches the hash code of the demo license key object stored in the LicenseManager class. Consequently, the attacker can discover the sensitive data present within thelicenseMap in only a few minutes. The attack operates by discovering at least one hash collision with respect to the key of the map.

Compliant Solution (IdentityHashMap)

This compliant solution uses an IdentityHashMap rather than a HashMap to store the license information:


public class LicenseManager {
Map<LicenseType, String> licenseMap =
new IdentityHashMap<LicenseType, String>();
// ...
}


According to the Java API class IdentityHashMap documentation [API 2006],

This class implements the Map interface with a hash table, using reference-equality in place of object-equality when comparing keys (and values). In other words, in an IdentityHashMap, two keys k1 and k2 are considered equal if and only if (k1==k2). (In normal Map implementations (likeHashMap) two keys k1 and k2 are considered equal if and only if (k1==null ? k2==null : k1.equals(k2)).)

Consequently, the overridden methods cannot expose internal class details. The client program can continue to add license keys, and can even retrieve the added key–value pairs, as demonstrated by the following client code.


public class DemoClient {
public static void main(String[] args) {
LicenseManager licenseManager = new LicenseManager();
LicenseType type = new LicenseType();
type.setType("custom-license-key");
licenseManager.setLicenseKey(type, "CUS-TOM-LIC-KEY");
Object licenseKeyValue = licenseManager.getLicenseKey(type);
// Prints CUS-TOM-LIC-KEY
System.out.println(licenseKeyValue);
}
}


Compliant Solution (final Class)

This compliant solution declares the LicenseType class final so that its methods cannot be overridden:


final class LicenseType {
// ...
}


Noncompliant Code Example

This noncompliant code example consists of a Widget class and a LayoutManager class containing a set of widgets:


public class Widget {
private int noOfComponents;

public Widget(int noOfComponents) {
this.noOfComponents = noOfComponents;
}
public int getNoOfComponents() {
return noOfComponents;
}
public final void setNoOfComponents(int noOfComponents) {
this.noOfComponents = noOfComponents;
}
public boolean equals(Object o) {
if (o == null || !(o instanceof Widget)) {
return false;
}
Widget widget = (Widget) o;
return this.noOfComponents == widget.getNoOfComponents();
}
@Override
public int hashCode() {
int res = 31;
res = res * 17 + noOfComponents;
return res;
}
}
public class LayoutManager {
private Set<Widget> layouts = new HashSet<Widget>();
public void addWidget(Widget widget) {
if (!layouts.contains(widget)) {
layouts.add(widget);
}
}
public int getLayoutSize() {
return layouts.size();
}
}


An attacker can extend the Widget class as a Navigator widget, and override the hashCode() method:


public class Navigator extends Widget {
public Navigator(int noOfComponents) {
super(noOfComponents);
}
@Override
public int hashCode() {
int res = 31;
res = res * 17;
return res;
}
}


The client code follows:


Widget nav = new Navigator(1);
Widget widget = new Widget(1);
LayoutManager manager = new LayoutManager();
manager.addWidget(nav);
manager.addWidget(widget);
System.out.println(manager.getLayoutSize()); // Prints 2


The set layouts is expected to contain just one item because the number of components for both the navigator and the widget being added is 1. However, the getLayoutSize() method returns 2.

The reason for this discrepancy is that the hashCode() method of Widget is used only once when the widget is added to the set. When the navigator is added, the hashCode() method provided by the Navigator class is used. Consequently, the set contains two different object instances.

Compliant Solution (final class)

This compliant solution declares the Widget class final so that its methods cannot be overridden:


public final class Widget {
// ...
}


Noncompliant Code Example (run())

In this noncompliant code example, class Worker and its subclass SubWorker each contain a startThread() method intended to start a thread.


public class Worker implements Runnable {
Worker() { }
public void startThread(String name) {
new Thread(this, name).start();
}
@Override
public void run() {
System.out.println("Parent");
}
}

public class SubWorker extends Worker {
@Override
public void startThread(String name) {
super.startThread(name);
new Thread(this, name).start();
}
@Override
public void run() {
System.out.println("Child");
}
}


If a client runs the following code:


Worker w = new SubWorker();
w.startThread("thread");


the client may expect Parent and Child to be printed. However, Child is printed twice because the overridden method run() is invoked both times that a new thread is started.

Compliant Solution

This compliant solution modifies the SubWorker class and removes the call to super.startThread():


public class SubWorker extends Worker {
@Override
public void startThread(String name) {
new Thread(this, name).start();
}
// ...
}


The client code is also modified to start the parent and child threads separately. This program produces the expected output:


Worker w1 = new Worker();
w1.startThread("parent-thread");
Worker w2 = new SubWorker();
w2.startThread("child-thread");


Bibliography

[API 2013]

Class IdentityHashMap

[Hawtin 2006]

[drlvm][kernel_classes] ThreadLocal vulnerability

16. Avoid granting excess privileges

A Java security policy grants permissions to code to allow access to specific system resources. A code source (an object of type CodeSource), to which a permission is granted, consists of the code location (URL) and a reference to the certificate(s) containing the public key(s) corresponding to the private key(s) used to digitally sign the code. Reference to the certificate(s) is pertinent only if the code was digitally signed. A protection domain encompasses a CodeSource and the permissions granted to code from that CodeSource, as determined by the security policy currently in effect. Consequently, classes signed by the same key and originating from the same URL are placed in the same protection domain. A class belongs to one and only one protection domain. Classes that have the same permissions but are from different code sources belong to different domains.

Each Java class runs in its appropriate domain, as determined by its code source. For any code running under a security manager to perform a secured action such as reading or writing a file, the code must be granted permission to perform that particular action. Privileged code can access privileged resources on behalf of an unprivileged caller by using the AccessController.doPrivileged() method. This is necessary, for example, when a system utility needs to open a font file on behalf of the user to display a document, but the application lacks permission to do so. To perform this action, the system utility uses its full privileges for obtaining the fonts, ignoring the privileges of the caller. Privileged code runs with all the privileges of the protection domain associated with the code source. These privileges often exceed those required to perform the privileged operation. Ideally, code should be granted only the minimum set of privileges required to complete its operation.

Guideline 19, “Define custom security permissions for fine-grained security,” describes another approach to eliminating excess privileges.

Noncompliant Code Example

This noncompliant code example shows a library method that allows callers to perform a privileged operation (reading a file) using the wrapper method performActionOnFile():


private FileInputStream openFile() {
final FileInputStream f[] = { null };

AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
f[0] = new FileInputStream("file");
} catch(FileNotFoundException fnf) {
// Forward to handler
}
return null;
}
});
return f[0];
}

// Wrapper method
public void performActionOnFile() {
try (FileInputStream f = openFile()){
// Perform operation
} catch (Throwable t) {
// Handle exception
}
}


In this example, the trusted code grants privileges beyond those required to read a file, even though read access to the file was the only permission needed by the doPrivileged() block. Consequently, this code violates the principle of least privilege by providing the code block with superfluous privileges.

Compliant Solution

The two-argument form of doPrivileged() accepts an AccessControlContext object from the caller and restricts the privileges of the contained code to the intersection of the privileges of the protection domain and those of the context passed as the second argument. Consequently, a caller that wishes to grant only permission to read the file can provide a context that has only file-reading permissions.

An AccessControlContext that grants the appropriate file-reading permissions can be created as an inner class:


private FileInputStream openFile(AccessControlContext context) {
if (context == null) {
throw new SecurityException("Missing AccessControlContext");
}

final FileInputStream f[] = { null };
AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
try {
f[0] = new FileInputStream("file");
} catch (FileNotFoundException fnf) {
// Forward to handler
}
return null;
}
},
// Restrict the privileges by passing the context argument
context);
return f[0];
}

private static class FileAccessControlContext {
public static final AccessControlContext INSTANCE;
static {
Permission perm = new java.io.FilePermission("file", "read");
PermissionCollection perms = perm.newPermissionCollection();
perms.add(perm);
INSTANCE = new AccessControlContext(new ProtectionDomain[] {
new ProtectionDomain(null, perms)});
}
}

// Wrapper method
public void performActionOnFile() {
try (final FileInputStream f =
// Grant only open-for-reading privileges
openFile(FileAccessControlContext.INSTANCE)){
// Perform action
} catch (Throwable t) {
// Handle exception
}
}


Callers that lack permission to create an appropriate AccessControlContext can request one using AccessController.getContext() to create the instance.

Applicability

Failure to follow the principle of least privilege can result in untrusted, unprivileged code performing unintended privileged operations. However, carefully restricting privileges adds complexity. This added complexity and the associated reduction of maintainability must be traded off against any security improvement.

Bibliography

[API 2013]

Class AccessController

[Oracle 2013a]

API for Privileged Blocks

17. Minimize privileged code

Programs must comply with the principle of least privilege not only by providing privileged blocks with the minimum permissions required for correct operation (see Guideline 16, “Avoid granting excess privileges”), but also by ensuring that privileged code contains only those operations that require increased privileges. Superfluous code contained within a privileged block must operate with the privileges of that block, increasing the attack surface.

Noncompliant Code Example

This noncompliant code example contains a changePassword() method that attempts to open a password file within a doPrivileged block and performs operations using that file. The doPrivileged block also contains a superfluous System.loadLibrary() call that loads the authentication library.


public void changePassword(String currentPassword,
String newPassword) {
final FileInputStream f[] = { null };

AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
String passwordFile = System.getProperty("user.dir") +
File.separator + "PasswordFileName";
f[0] = new FileInputStream(passwordFile);
// Check whether oldPassword matches the one in the file
// If not, throw an exception
System.loadLibrary("authentication");
} catch (FileNotFoundException cnf) {
// Forward to handler
}
return null;
}
}); // End of doPrivileged()
}


This example violates the principle of least privilege because an unprivileged caller could also cause the authentication library to be loaded. An unprivileged caller cannot invoke the System.loadLibrary() method directly, because this could expose native methods to the unprivileged code [SCG 2010]. Furthermore, the System.loadLibrary() method checks only the privileges of its immediate caller, so it should be used only with great care. For more information, see Guideline 18, “Do not expose methods that use reduced-security checks to untrusted code.”

Compliant Solution

This compliant solution moves the call to System.loadLibrary() outside the doPrivileged() block. Doing so allows unprivileged code to perform preliminary password-reset checks using the file, but prevents it from loading the authentication library.


public void changePassword(String currentPassword,
String newPassword) {
final FileInputStream f[] = { null };

AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
String passwordFile = System.getProperty("user.dir") +
File.separator + "PasswordFileName";
f[0] = new FileInputStream(passwordFile);
// Check whether oldPassword matches the one in the file
// If not, throw an exception
} catch (FileNotFoundException cnf) {
// Forward to handler
}
return null;
}
}); // End of doPrivileged()

System.loadLibrary("authentication");
}


The loadLibrary() invocation could also occur before preliminary password reset checks are performed; in this example, it is deferred for performance reasons.

Applicability

Minimizing privileged code reduces the attack surface of an application and simplifies the task of auditing privileged code.

Bibliography

[API 2013]

Class AccessController

18. Do not expose methods that use reduced-security checks to untrusted code

Most methods lack security manager checks because they do not provide access to sensitive parts of the system, such as the file system. Most methods that do provide security manager checks verify that every class and method in the call stack is authorized before they proceed. This security model allows restricted programs, such as Java applets, to have full access to the core Java library. It also prevents a sensitive method from acting on behalf of a malicious method that hides behind trusted methods in the call stack.

However, certain methods use a reduced-security check that checks only that the calling method is authorized rather than checking every method in the call stack. Any code that invokes these methods must guarantee that they cannot be invoked on behalf of untrusted code. These methods are listed in Table 1–2.

Image

Table 1–2. Methods that check the calling method only

Because the java.lang.reflect.Field.setAccessible() and getAccessible() methods are used to instruct the Java Virtual Machine (JVM) to override the language access checks, they perform standard (and more restrictive) security manager checks, and consequently lack the vulnerability described by this guideline. Nevertheless, these methods should also be used with extreme caution. The remaining set* and get* field reflection methods perform only the language access checks and are consequently vulnerable.

Class Loaders

Class loaders allow a Java application to be dynamically extended at runtime by loading additional classes. For each class that is loaded, the JVM tracks the class loader that was used to load the class. When a loaded class first refers to another class, the virtual machine requests that the referenced class be loaded by the same class loader that was used to load the referencing class. Java’s class loader architecture controls interaction between code loaded from different sources by allowing the use of different class loaders. This separation of class loaders is fundamental to the separation of code: it prevents malicious code from gaining access to and subverting trusted code.

Several methods which are charged with loading classes delegate their work to the class loader of the class of the method that called them. The security checks associated with loading classes are performed by the class loaders. Consequently, any method that invokes one of these class loading methods must guarantee that these methods cannot act on behalf of untrusted code. These methods are listed in Table 1–3.

Image

Table 1–3. Methods that use the calling method’s class loader

With the exception of the loadLibrary() and load() methods, the tabulated methods do not perform any security manager checks; they delegate security checks to the appropriate class loader.

In practice, the trusted code’s class loader frequently allows these methods to be invoked, whereas the untrusted code’s class loader may lack these privileges. However, when the untrusted code’s class loader delegates to the trusted code’s class loader, the untrusted code gains visibility to the trusted code. In the absence of such a delegation relationship, the class loaders would ensure namespace separation; consequently, the untrusted code would be unable to observe members or to invoke methods belonging to the trusted code.

The class loader delegation model is fundamental to many Java implementations and frameworks. Avoid exposing the methods listed in Tables 1–2 and 1–3 to untrusted code. Consider, for example, an attack scenario where untrusted code is attempting to load a privileged class. If its class loader lacks permission to load the requested privileged class on its own, but the class loader is permitted to delegate the class loading to a trusted class’s class loader, privilege escalation can occur. Furthermore, if the trusted code accepts tainted inputs, the trusted code’s class loader could be persuaded to load privileged, malicious classes on behalf of the untrusted code.

Classes that have the same defining class loader will exist in the same name-space, but they can have different privileges depending on the security policy. Security vulnerabilities can arise when privileged code coexists with unprivileged code (or less privileged code) that was loaded by the same class loader. In this case, the less privileged code can freely access members of the privileged code according to the privileged code’s declared accessibility. When the privileged code uses any of the tabulated APIs, it bypasses security manager checks (with the exception ofloadLibrary() and load()).

This guideline is similar to The CERT® Oracle® Secure Coding Standard for Java [Long 2012], “SEC03-J. Do not load trusted classes after allowing untrusted code to load arbitrary classes.” Many examples also violate “SEC00-J. Do not allow privileged blocks to leak sensitive information across a trust boundary.”

Noncompliant Code Example

In this noncompliant code example, a call to System.loadLibrary() is embedded in a doPrivileged block.


public void load(String libName) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
System.loadLibrary(libName);
return null;
}
});
}


This code is insecure because it could be used to load a library on behalf of untrusted code. In essence, the untrusted code’s class loader may be able to use this code to load a library even though it lacks sufficient permissions to do so directly. After loading the library, the untrusted code can call native methods from the library, if those methods are accessible, because the doPrivileged block prevents any security manager checks from being applied to callers further up the execution stack.

Nonnative library code can also be susceptible to related security flaws. Suppose there exists a library that contains a vulnerability that is not directly exposed, perhaps because it lies in an unused method. Loading this library may not directly expose a vulnerability. However, an attacker could then load an additional library that exploits the first library’s vulnerability. Moreover, nonnative libraries often use doPrivileged blocks, making them attractive targets.

Compliant Solution

This compliant solution hard codes the name of the library to prevent the possibility of tainted values. It also reduces the accessibility of the load() method from public to private. Consequently, untrusted callers are prohibited from loading the awt library.


private void load() {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
System.loadLibrary("awt");
return null;
}
});
}


Noncompliant Code Example

This noncompliant code example returns an instance of java.sql.Connection from trusted code to untrusted code.


public Connection getConnection(String url, String username,
String password) {
// ...
return DriverManager.getConnection(url, username, password);
}


Untrusted code that lacks the permissions required to create a SQL connection can bypass these restrictions by using the acquired instance directly. The getConnection() method is unsafe because it uses the url argument to indicate a class to be loaded; this class serves as the database driver.

Compliant Solution

This compliant solution prevents malicious users from supplying their own URL to the database connection, thereby limiting their ability to load untrusted drivers.


private String url = // Hardwired value

public Connection getConnection(String username,
String password) {
// ...
return DriverManager.getConnection(this.url,
username, password);
}


Noncompliant Code Example (CERT Vulnerability 636312)

CERT Vulnerability Note VU#636312 describes a vulnerability in Java 1.7.0 update 6 that was widely exploited in August 2012. The exploit actually used two vulnerabilities; the other one is described in The CERT® Oracle® Secure Coding Standard for Java [Long 2012], “SEC05-J. Do not use reflection to increase accessibility of classes, methods, or fields.”

The exploit runs as a Java applet. The applet class loader ensures that an applet cannot directly invoke methods of classes present in the com.sun.* package. A normal security manager check ensures that specific actions are allowed or denied depending on the privileges of all of the caller methods on the call stack (the privileges are associated with the code source that encompasses the class).

The first goal of the exploit code was to access the private sun.awt.SunToolkit class. However, invoking class.forName() directly on the name of this class would cause a SecurityException to be thrown. Consequently, the exploit code used the following method to access any class, bypassing the security manager:


private Class GetClass(String paramString)
throws Throwable {
Object arrayOfObject[] = new Object[1];
arrayOfObject[0] = paramString;
Expression localExpression =
new Expression(Class.class, "forName", arrayOfObject);
localExpression.execute();
return (Class)localExpression.getValue();
}


The java.beans.Expression.execute() method delegates its work to the following method:


private Object invokeInternal() throws Exception {
Object target = getTarget();
String methodName = getMethodName();

if (target == null || methodName == null) {
throw new NullPointerException(
(target == null ? "target" : "methodName") +
" should not be null");
}

Object[] arguments = getArguments();
if (arguments == null) {
arguments = emptyArray;
}
// Class.forName() won't load classes outside
// of core from a class inside core, so it
// is handled as a special case.
if (target == Class.class && methodName.equals("forName")) {
return ClassFinder.resolveClass((String)arguments[0],
this.loader);
}

// ...


The com.sun.beans.finder.ClassFinder.resolveClass() method delegates its work to its findClass() method:


public static Class<?> findClass(String name)
throws ClassNotFoundException {
try {
ClassLoader loader =
Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
if (loader != null) {
return Class.forName(name, false, loader);
}
} catch (ClassNotFoundException exception) {
// Use current class loader instead
} catch (SecurityException exception) {
// Use current class loader instead
}
return Class.forName(name);
}


Although this method is called in the context of an applet, it uses Class.forName() to obtain the requested class. Class.forName() delegates the search to the calling method’s class loader. In this case, the calling class (com.sun.beans.finder.ClassFinder) is part of core Java, so the trusted class loader is used in place of the more restrictive applet class loader, and the trusted class loader loads the class, unaware that it is acting on behalf of malicious code.

Compliant Solution (CVE-2012-4681)

Oracle mitigated this vulnerability in Java 1.7.0 update 7 by patching the com.sun.beans.finder.ClassFinder.findClass() method. The checkPackageAccess() method checks the entire call stack to ensure that Class.forName(), in this instance only, fetches classes only on behalf of trusted methods.


public static Class<?> findClass(String name)
throws ClassNotFoundException {
checkPackageAccess(name);
try {
ClassLoader loader =
Thread.currentThread().getContextClassLoader();
if (loader == null) {
// Can be null in IE (see 6204697)
loader = ClassLoader.getSystemClassLoader();
}
if (loader != null) {
return Class.forName(name, false, loader);
}

} catch (ClassNotFoundException exception) {
// Use current class loader instead
} catch (SecurityException exception) {
// Use current class loader instead
}
return Class.forName(name);
}


Noncompliant Code Example (CVE-2013-0422)

Java 1.7.0 update 10 was widely exploited in January 2013 because of several vulnerabilities. One such vulnerability in the com.sun.jmx.mbeanserver.MBeanInstantiator class granted unprivileged code the ability to access any class regardless of the current security policy or accessibility rules. The MBeanInstantiator.findClass() method could be invoked with any string and would attempt to return the Class object named after the string. This method delegated its work to the MBeanInstantiator.loadClass() method, whose source code is shown here:


/**
* Load a class with the specified loader, or with this object
* class loader if the specified loader is null.
**/
static Class<?> loadClass(String className, ClassLoader loader)
throws ReflectionException {
Class<?> theClass;
if (className == null) {
throw new RuntimeOperationsException(
new IllegalArgumentException(
"The class name cannot be null"),
"Exception occurred during object instantiation");
} try {
if (loader == null) {
loader = MBeanInstantiator.class.getClassLoader();
}
if (loader != null) {
theClass = Class.forName(className, false, loader);
} else {
theClass = Class.forName(className);
}
} catch (ClassNotFoundException e) {
throw new ReflectionException(
e, "The MBean class could not be loaded");
}
return theClass;
}


This method delegates the task of dynamically loading the specified class to the Class.forName() method, which delegates the work to its calling method’s class loader. Because the calling method is MBeanInstantiator.loadClass(), the core class loader is used, which provides no security checks.

Compliant Solution (CVE-2013-0422)

Oracle mitigated this vulnerability in Java 1.7.0 update 11 by adding an access check to the MBeanInstantiator.loadClass() method. This access check ensures that the caller is permitted to access the class being sought:


// ...
if (className == null) {
throw new RuntimeOperationsException(
new IllegalArgumentException(
"The class name cannot be null"),
"Exception occurred during object instantiation");
}
ReflectUtil.checkPackageAccess(className);
try {
if (loader == null)
// ...


Applicability

Allowing untrusted code to invoke methods with reduced-security checks can result in privilege escalation. Likewise, allowing untrusted code to perform actions using the immediate caller’s class loader may allow the untrusted code to execute with the same privileges as the immediate caller.

Methods that avoid using the immediate caller’s class loader instance fall outside the scope of this guideline. For example, the three-argument java.lang.Class.forName() method requires an explicit argument that specifies the class loader instance to use.

public static Class forName(String name, boolean initialize,
ClassLoader loader) throws ClassNotFoundException

Do not use the immediate caller’s class loader as the third argument when instances must be returned to untrusted code.

Bibliography

[API 2013]

Class ClassLoader

[Chan 1998]

java.lang.reflect AccessibleObject

[Guillardoy 2012]

Java 0Day Analysis (CVE-2012-4681)

[Long 2012]

SEC00-J. Do not allow privileged blocks to leak sensitive information across a trust boundary

SEC03-J. Do not load trusted classes after allowing untrusted code to load arbitrary classes

SEC05-J. Do not use reflection to increase accessibility of classes, methods, or fields

[Manion 2013]

“Anatomy of Java Exploits”

[Oracle 2013d]

Oracle Security Alert for CVE-2013-0422

19. Define custom security permissions for fine-grained security

The default SecurityManager checks whether the caller of a particular method has sufficient permissions to proceed with an action. An action is defined in Java’s security architecture as a level of access, and requires certain permissions before it can be performed. For example, the actions forjava.io.FilePermission are read, write, execute, and delete [API 2013]. The “Permission Descriptions and Risks” guide [Oracle 2011d] enumerates the default permissions and the risks associated with granting these permissions to Java code.

Sometimes, stronger restrictions than those provided by the default security manager are necessary. Failure to provide custom permissions when no corresponding default permissions exist can lead to privilege escalation vulnerabilities that enable untrusted callers to execute restricted operations or actions.

This guideline addresses the problem of excess privileges. See Guideline 16, “Avoid granting excess privileges,” for another approach to solving this problem.

Noncompliant Code Example

This noncompliant code example contains a privileged block that is used to perform two sensitive operations: loading a library and setting the default exception handler.


class LoadLibrary {
private void loadLibrary() {
AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
// Privileged code
System.loadLibrary("myLib.so");
// Perform some sensitive operation like
// setting the default exception handler
MyExceptionReporter.setExceptionReporter(reporter);
return null;
}
});
}
}


When used, the default security manager forbids the loading of the library unless the RuntimePermission loadLibrary.myLib is granted in the policy file. However, the security manager does not automatically guard a caller from performing the second sensitive operation of setting the default exception handler because the permission for this operation is nondefault and, consequently, unavailable. This security weakness can be exploited, for example, by programming and installing an exception handler that reveals information that a legitimate handler would filter out.

Compliant Solution

This compliant solution defines a custom permission ExceptionReporterPermission with the target exc.reporter to prohibit illegitimate callers from setting the default exception handler. This can be achieved by subclassing BasicPermission, which allows binary-style permissions (either allow or disallow). The compliant solution then uses a security manager to check whether the caller has the requisite permission to set the handler. The code throws a SecurityException if the check fails. The custom permission class ExceptionReporterPermission is also defined with the two required constructors.


class LoadLibrary {
private void loadLibrary() {
AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
// Privileged code
System.loadLibrary("myLib.so");

// Perform some sensitive operation like
// setting the default exception handler
MyExceptionReporter.setExceptionReporter(reporter);
return null;
}
});
}
}

final class MyExceptionReporter extends ExceptionReporter {
public void setExceptionReporter(ExceptionReporter reporter) {
SecurityManager sm = System.getSecurityManager();
if(sm != null) {
sm.checkPermission(
new ExceptionReporterPermission("exc.reporter"));
}
// Proceed to set the exception reporter

}
// ... Other methods of MyExceptionReporter
}

final class ExceptionReporterPermission extends BasicPermission {
public ExceptionReporterPermission(String permName) {
super(permName);
}

// Even though the actions parameter is ignored,
// this constructor has to be defined
public ExceptionReporterPermission(String permName,
String actions) {
super(permName, actions);
}
}


The policy file needs to grant two permissions: ExceptionReporterPermission exc.reporter and RuntimePermission loadlibrary.myLib. The following policy file assumes that the preceding sources reside in the c:\package directory on a Windows-based system.


grant codeBase "file:/c:/package" {
// For *nix, file:${user.home}/package/
permission ExceptionReporterPermission "exc.reporter";
permission java.lang.RuntimePermission "loadLibrary.myLib";
};


By default, permissions cannot be defined to support actions using Basic-Permission, but the actions can be freely implemented in the subclass Exception-ReporterPermission if required. BasicPermission is abstract even though it contains no abstract methods; it defines all the methods that it extends from the Permission class. The custom-defined subclass of the BasicPermission class must define two constructors to call the most appropriate (one- or two-argument) superclass constructor (because the superclass lacks a default constructor). The two-argument constructor also accepts an action, even though a basic permission does not use it. This behavior is required for constructing permission objects from the policy file. Note that the custom-defined subclass of the BasicPermission class is declared to be final.

Applicability

Running Java code without defining custom permissions where default permissions are inapplicable can leave an application open to privilege escalation vulnerabilities.

Bibliography

[API 2013]

Class FilePermission

Class SecurityManager

[Oaks 2001]

“Permissions” subsection of Chapter 5, “The Access Controller,”

[Oracle 2011d]

Permissions in the Java SE 6 Development Kit (JDK)

[Oracle 2013c]

Java Platform Standard Edition 7 Documentation

[Policy 2010]

“Permission Descriptions and Risks”

20. Create a secure sandbox using a security manager

According to the Java API Class SecurityManager documentation [API 2013],

The security manager is a class that allows applications to implement a security policy. It allows an application to determine, before performing a possibly unsafe or sensitive operation, what the operation is and whether it is being attempted in a security context that allows the operation to be performed. The application can allow or disallow the operation.

A security manager may be associated with any Java code.

The applet security manager denies applets all but the most essential privileges. It is designed to protect against inadvertent system modification, information leakage, and user impersonation. The use of security managers is not limited to client-side protection. Web servers, such as Tomcat and WebSphere, use this facility to isolate trojan servlets and malicious Java Server Pages (JSP) as well as to protect sensitive system resources from inadvertent access.

Java applications that run from the command line can set a default or custom security manager using a command-line flag. Alternatively, it is possible to install a security manager programmatically. Installing a security manager programmatically helps create a default sandbox that allows or denies sensitive actions on the basis of the security policy in effect.

From Java 2 SE Platform onward, SecurityManager is a nonabstract class. As a result, there is no explicit requirement to override its methods. To create and use a security manager programmatically, the code must have the runtime permissions createSecurityManager (to instantiateSecurityManager) and setSecurityManager (to install it). These permissions are checked only if a security manager is already installed. This is useful for situations in which a default security manager is in place, such as on a virtual host, and individual hosts must be denied the requisite permissions for overriding the default security manager with a custom one.

The security manager is closely tied to the AccessController class. The former is used as a hub for access control, whereas the latter provides the actual implementation of the access control algorithm. The security manager supports

Image Providing backward compatibility: Legacy code often contains custom implementations of the security manager class because it was originally abstract.

Image Defining custom policies: Subclassing the security manager permits definition of custom security policies (for example, multilevel, coarse, or fine grain).

Regarding the implementation and use of custom security managers as opposed to default ones, the Java security architecture specification [SecuritySpec 2010] states:

We encourage the use of AccessController in application code, while customization of a security manager (via subclassing) should be the last resort and should be done with extreme care. Moreover, a customized security manager, such as one that always checks the time of the day before invoking standard security checks, could and should utilize the algorithm provided by AccessController whenever appropriate.

Many of the Java SE APIs perform security manager checks by default before performing sensitive operations. For example, the constructor of class java.io.FileInputStream throws a SecurityException if the caller does not have the permission to read a file. Because SecurityException is a subclass of RuntimeException, the declarations of some API methods (for example, those of the java.io.FileReader class) may lack a throws clause that lists the SecurityException. Avoid depending on the presence or absence of security manager checks that are not specified in the API method’s documentation.

Noncompliant Code Example (Command-Line Installation)

This noncompliant code example fails to install any security manager from the command line. Consequently, the program runs with all permissions enabled; that is, there is no security manager to prevent any nefarious actions the program might perform.

java LocalJavaApp

Compliant Solution (Default Policy File)

Any Java program can attempt to install a SecurityManager programmatically, although the currently active security manager may forbid this operation. Applications designed to run locally can specify a default security manager by use of a flag on the command line at invocation.

The command-line option is preferred when applications must be prohibited from installing custom security managers programmatically, and are required to abide by the default security policy under all circumstances. This compliant solution installs the default security manager using the appropriate command-line flags. The security policy file grants permissions to the application for its intended actions.

java -Djava.security.manager -Djava.security.policy=policyURL \
LocalJavaApp

The command-line flag can specify a custom security manager whose policies are enforced globally. Use the -Djava.security.manager flag, as follows:

java -Djava.security.manager=my.security.CustomManager ...

If the current security policy enforced by the current security manager forbids replacements (by omitting the RuntimePermission("setSecurityManager")), any attempt to invoke setSecurityManager() will throw a SecurityException.

The default security policy file java.policy—found in the /path/to/java.home/lib/security directory on UNIX-like systems and its equivalent on Microsoft Windows systems—grants a few permissions (reading system properties, binding to unprivileged ports, and so forth). A user-specific policy file may also be located in the user’s home directory. The union of these policy files specifies the permissions granted to a program. The java.security file can specify which policy files are used. If either of the systemwide java.policy or java.security files is deleted, no permissions are granted to the executing Java program.

Compliant Solution (Custom Policy File)

Use double equals (==) instead of the single equals (=) when overriding the global Java security policy file with a custom policy file:

java -Djava.security.manager \
-Djava.security.policy==policyURL \
LocalJavaApp

Compliant Solution (Additional Policy Files)

The appletviewer automatically installs a security manager with the standard policy file. To specify additional policy files, use the -J flag.

appletviewer -J-Djava.security.manager \
-J-Djava.security.policy==policyURL LocalJavaApp

Note that the policy file specified in the argument is ignored when the policy.allowSystemProperty property in the security properties file (java.security) is set to false; the default value of this property is true. Default Policy Implementation and Policy File Syntax [Policy 2010] discusses in depth the issues and syntax for writing policy files.

Noncompliant Code Example (Programmatic Installation)

A SecurityManager can also be activated using the static System.setSecurity-Manager() method. Only one SecurityManager may be active at a time. This method replaces the currently active SecurityManager with the SecurityManager provided in its argument, or no SecurityManager if its argument is null.

This noncompliant code example deactivates any current SecurityManager but does not install another SecurityManager in its place. Consequently, subsequent code will run with all permissions enabled; there will be no restrictions on any nefarious action the program might perform.


try {
System.setSecurityManager(null);
} catch (SecurityException se) {
// Cannot set security manager, log to file
}


An active SecurityManager that enforces a sensible security policy will prevent the system from deactivating it, causing this code to throw a SecurityException.

Compliant Solution (Default Security Manager)

This compliant solution instantiates and sets the default security manager.


try {
System.setSecurityManager(new SecurityManager());
} catch (SecurityException se) {
// Cannot set security manager, log appropriately
}


Compliant Solution (Custom Security Manager)

This compliant solution demonstrates how to instantiate a custom Security-Manager class called CustomSecurityManager by invoking its constructor with a password; this custom security manager is then installed as the active security manager.


char password[] = /* initialize */
try {
System.setSecurityManager(
new CustomSecurityManager("password here")
);
} catch (SecurityException se) {
// Cannot set security manager, log appropriately
}


After this code executes, APIs that perform security checks will use the custom security manager. As noted earlier, custom security managers should be installed only when the default security manager lacks the required functionality.

Applicability

Java security fundamentally depends on the existence of a security manager. In its absence, sensitive actions can execute without restriction.

Programmatic detection of the presence or absence of a SecurityManager at runtime is straightforward. Static analysis can address the presence or absence of code that would attempt to install a SecurityManager if the code were executed. Checking whether the SecurityManager is installed early enough, whether it specifies the desired properties, or whether it is guaranteed to be installed may be possible in some special cases, but is generally undecidable.

Invocation of the setSecurityManager() method may be omitted in controlled environments in which it is known that a global-default security manager is always installed from the command line. This is difficult to enforce, and can result in vulnerabilities if the environment is incorrectly configured.

Bibliography

[API 2013]

Class SecurityManager

Class AccessControlContext

Class AccessController

[Gong 2003]

§6.1, “Security Manager”

[Pistoia 2004]

§7.4, “The Security Manager”

[Policy 2010]

Default Policy Implementation and Policy File Syntax

[SecuritySpec 2010]

§6.2, “SecurityManager versus AccessController”

21. Do not let untrusted code misuse privileges of callback methods

Callbacks provide a means to register a method to be invoked (or called back) when an interesting event occurs. Java uses callbacks for applet and servlet life-cycle events, AWT and Swing event notifications such as button clicks, asynchronous reads and writes to storage, and even inRunnable.run() wherein a new thread automatically executes the specified run() method.

In Java, callbacks are typically implemented using interfaces. The general structure of a callback is as follows:


public interface CallBack {
void callMethod();
}

class CallBackImpl implements CallBack {
public void callMethod() {
System.out.println("CallBack invoked");
}
}

class CallBackAction {
private CallBack callback;

public CallBackAction(CallBack callback) {
this.callback = callback;
}

public void perform() {
callback.callMethod();
}
}

class Client {
public static void main(String[] args) {
CallBackAction action =
new CallBackAction(new CallBackImpl());
// ...
action.perform(); // Prints "CallBack invoked"
}
}


Callback methods are often invoked without changes in privileges, which means that they may be executed in a context that has more privileges than the context in which they are declared. If these callback methods accept data from untrusted code, privilege escalation may occur.

According to Oracle’s secure coding guidelines [SCG 2010],

Callback methods are generally invoked from the system with full permissions. It seems reasonable to expect that malicious code needs to be on the stack in order to perform an operation, but that is not the case. Malicious code may set up objects that bridge the callback to a security checked operation. For instance, a file chooser dialog box that can manipulate the filesystem from user actions, may have events posted from malicious code. Alternatively, malicious code can disguise a file chooser as something benign while redirecting user events.

This guideline is an instance of Guideline 17, “Minimize privileged code,” and is related to The CERT® Oracle® Secure Coding Standard for Java [Long 2012], “SEC01-J. Do not allow tainted variables in privileged blocks.”

Noncompliant Code Example

This noncompliant code example uses a UserLookupCallBack class that implements the CallBack interface to look up a user’s name given the user’s ID. This lookup code assumes that this information lives in the /etc/passwd file, which requires elevated privileges to open. Consequently, theClient class invokes all callbacks with elevated privileges (within a doPrivileged block).


public interface CallBack {
void callMethod();
}

class UserLookupCallBack implements CallBack {
private int uid;
private String name;

public UserLookupCallBack(int uid) {
this.uid = uid;
}

public String getName() {
return name;
}

public void callMethod() {
try (InputStream fis = new FileInputStream("/etc/passwd")) {
// Look up uid & assign to name
} catch (IOException x) {
name = null;
}
}
}

final class CallBackAction {
private CallBack callback;

public CallBackAction(CallBack callback) {
this.callback = callback;
}

public void perform() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
callback.callMethod();
return null;
}
});
}
}


This code could be safely used by a client, as follows:


public static void main(String[] args) {
int uid = Integer.parseInt(args[0]);

CallBack callBack = new UserLookupCallBack(uid);
CallBackAction action = new CallBackAction(callBack);

// ...
action.perform(); // Looks up user name
System.out.println("User " + uid + " is named " +
callBack.getName());
}


However, an attacker can use CallBackAction to execute malicious code with elevated privileges by registering a MaliciousCallBack instance:


class MaliciousCallBack implements CallBack {
public void callMethod() {
// Code here gets executed with elevated privileges
}
}

// Client code
public static void main(String[] args) {
CallBack callBack = new MaliciousCallBack();
CallBackAction action = new CallBackAction(callBack);
action.perform(); // Executes malicious code
}


Compliant Solution (Callback-Local doPrivileged Block)

According to Oracle’s secure coding guidelines [SCG 2010],

By convention, instances of PrivilegedAction and PrivilegedExceptionAction may be made available to untrusted code, but doPrivileged must not be invoked with caller-provided actions.

This compliant solution moves the invocation of doPrivileged() out of the CallBackAction code and into the callback itself.


public interface CallBack {
void callMethod();
}

class UserLookupCallBack implements CallBack {
private int uid;
private String name;
public UserLookupCallBack(int uid) {
this.uid = uid;
}

public String getName() {
return name;
}

public final void callMethod() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try (InputStream fis =
new FileInputStream("/etc/passwd")) {
// Look up userid and assign to
// UserLookupCallBack.this.name
} catch (IOException x) {
UserLookupCallBack.this.name = null;
}
return null;
}
});
}
}

final class CallBackAction {
private CallBack callback;

public CallBackAction(CallBack callback) {
this.callback = callback;
}

public void perform() {
callback.callMethod();
}
}


This code behaves the same as before, but an attacker can no longer execute malicious callback code with elevated privileges. Even though an attacker can pass a malicious callback instance using the constructor of class CallBackAction, the code is not executed with elevated privileges because the malicious instance must contain a doPrivileged block that cannot have the same privileges as trusted code. Additionally, class CallBackAction cannot be subclassed to override the perform() method as it is declared final.

Compliant Solution (Declare Callback Final)

This compliant solution declares the UserLookupCallBack class final to prevent overriding of callMethod().


final class UserLookupCallBack implements CallBack {
// ...
}

// Remaining code is unchanged


Applicability

Exposing sensitive methods through callbacks can result in misuse of privileges and arbitrary code execution.

Bibliography

[API 2013]

AccessController.doPrivileged()

[Long 2012]

SEC01-J. Do not allow tainted variables in privileged blocks

[SCG 2010]

Guideline 9-2: Beware of callback methods

Guideline 9-3: Safely invoke java.security.AccessController.doPrivileged