For a non-final class, if a constructor throws an exception before fully initializing the object, it becomes possible to maliciously obtain its instance. For example, an attack that uses the finalizer construct allows an attacker to invoke arbitrary methods within the class in spite of despite authorization measures.
Noncompliant Code Example
Wiki Markup |
---|
This noncompliant code example, based on an example by Kabutz \[[Kabutz 01|AA. Java References#Kabutz 01]\], defines the constructor of {{BankOperations}} class so that it performs SSN verification using the method {{performSSNVerification()}}. Assume that an attacker does not know the correct SSN. As a result, this method trivially returns {{false}} in this example. |
When the SSN verification fails, a SecurityException
is The constructor of BankOperations
class performs the SSN validation using performSSNVerification()
. Assume that an attacker does not know the correct SSN. As a result, this method trivially returns false
in this example. A SecurityException
is forcefully thrown. The UserApp
class appropriately catches this exception and an access denied message is displayed. However, it is still possible for this does not prevent a malicious program to invoke from invoking methods of the partially initialized class BankOperations
. This is illustrated in the code that follows this noncompliant code example example.
Code Block | ||
---|---|---|
| ||
public class BankOperations { public BankOperations() { if (!performSSNVerification()) { throw new SecurityException("Invalid SSN!"); } } private boolean performSSNVerification() { return false; // Returns true if data entered is valid, else false. Assume that the attacker just enters invalid SSN. } public void greet() { System.out.println("Welcome user! You may now use all the features."); } } public class UserApp { public static void main(String[] args) { BankOperations bo; try { bo = new BankOperations(); } catch(SecurityException ex) { bo = null; } Storage.store(bo); System.out.println("Proceed with normal logic"); } } public class Storage { private static BankOperations bop; public static void store(BankOperations bo) { // Only store if it is not initialized if (bop == null) { if (bo == null) { System.out.println("Invalid object!"); System.exit(1); } bop = bo; } } } |
Note that even if a malicious subclass catches the SecurityException
that the BankOperations
constructor throws, it cannot obtain its instance to cause further harm. To exploit this code, an attacker extends the BankOperations
class and overrides the finalize()
method. The gist of the attack is the capture of a handle of the partially initialized base class.
When the constructor throws an exception, the garbage collector waits to grab the object reference. However, by overriding the finalizer, a reference is obtained using the this
keyword. Consequently, any instance method on the base class can be invoked maliciously. Note that, even maliciously by using the stolen this
instance. Even a security manager check can be bypassed this way.
Code Block |
---|
public class Interceptor extends BankOperations { private static Interceptor stealInstance = null; public static Interceptor get() { try { new Interceptor(); } catch(Exception ex) { } // Ignore the exception try { synchronized(Interceptor.class) { while (stealInstance == null) { System.gc(); Interceptor.class.wait(10); } } } catch(InterruptedException ex) { return null; } return stealInstance; } public void finalize() { synchronized(Interceptor.class) { stealInstance = this; Interceptor.class.notify(); } System.out.println("Stolen the instance in finalize of " + this); } } public class AttackerApp { // Invoke class and gain access to the restrictive features public static void main(String[] args) { Interceptor i = Interceptor.get(); static void main(String[] args) { Interceptor i = Interceptor.get(); // stolen instance // Can store the stolen object though this should have printed "Invalid Object!" Storage.store(i); // Can store the stolen object Now invoke any instance method of BankOperations class i.greet(); // Now invoke any method of BankOperations class UserApp.main(args); // Invoke the original UserApp } } |
This code is an exception to The attacker's code violates the guideline OBJ02-J. Avoid using finalizers.
Compliant Solution
The This compliant solution declares the partially-initialized class final
so that it cannot be extended.
Code Block | ||
---|---|---|
| ||
public final class BankOperations { // ... } |
Noncompliant Code Example
...
Code Block | ||
---|---|---|
| ||
class UnsafeCurrency { // total amount requested (required) private int dollars = -1; // initialize to default value private int cents = -1; // initialize to default value // change requested, denomination (optional) private int quarters = 0; private int dimes = 0; private int nickels = 0; private int pennies = 0; public UnsafeCurrency() {} // no argument constructor //* setter methods */ public void setDollar(int amount) { dollars = amount; } public void setCents(int amount) { cents = amount; } public void setQuarters(int quantity) { quarters = quantity; } public void setDimes(int quantity) { dimes = quantity; } public void setNickels(int quantity) { nickels = quantity; } public void setPennies(int quantity) { pennies = quantity; } } |
Compliant Solution
Wiki Markup |
---|
Use the Builder pattern's \[[Gamma 95|AA. Java References#Gamma 95]\] variant suggested by Bloch \[[Bloch 08|AA. Java References#Bloch 08]\] to ensure thread safety and atomicity of object creation. The idea is to call the constructor with the _required_ parameters and obtain a _builder_ object. Each _optional_ parameter can be set using setters on the builder. The object construction concludes with the invocation of the {{build()}} method. The)}} method. This also has the effect of making the class {{Currency}} also becomes immutable as a result. |
Code Block | ||
---|---|---|
| ||
class Currency { // total amount requested (required) private final int dollars; private final int cents; // change requested, denomination (optional) private final int quarters; private final int dimes; private final int nickels; private final int pennies; //* Static class member */ private Currency(Builder builder) { dollars = builder.dollars; cents = builder.cents; quarters = builder.quarters; dimes = builder.dimes; nickels = builder.nickels; pennies = builder.pennies; } // Static class member public static class Builder { private final int dollars; private final int cents; private int quarters = 0; private int dimes = 0; private int nickels = 0; private int pennies = 0; public Builder(int dollars, int cents) { this.dollars = dollars; this.cents = cents; } public Builder quarters(int quantity) { quarters = quantity; return this; } public Builder dimes(int quantity) { dimes = quantity; return this; } public Builder nickles(int quantity) { nickels = quantity;; return this; } public Builder pennies(int quantity) { pennies = quantity; return this; } public Currency build() { return new Currency(this); } } } Client code: Currency USD = new Currency.Builder(100,56).quarters(2).dimesnickles(51).pennies(1).build(); |
If input has to be validated, make sure that the values are copied from the builder class to the containing class's fields prior to checking. The builder class does not violate SCP02-J. Do not expose sensitive private members of the outer class from within a nested class since because it maintains a copy of the variables defined in the scope of the containing class. These take precedence and as a result do not break encapsulation.
...
EX1: When the API cannot be extended (consider a non-final class, for example), it is permissible to use a flag to signal that the successful completion of object construction succeeded. This is shown below.
Code Block | ||
---|---|---|
| ||
class BankOperations { public volatile boolean initialized = false; // volatile flag public BankOperations() { if (!performSSNVerification()) { throw new SecurityException("Invalid SSN!"); } else { initialized = true; // object construction succeeded } } private boolean performSSNVerification() { return false; } public void greet() { if(initialized == true) { System.out.println("Welcome user! You may now use all the features."); // ... } else { System.out.println("You are not permitted!"); } } } |
EX2: It is permissible to use the telescoping pattern when the overhead of the builder pattern is significant as compared to the number of parameters required to be initialized. This pattern prescribes a constructor to initialize the required parameters and individual constructors for each optional parameter that is added.
...
Allowing a partially initialized object to be accessed can provide an attacker with an opportunity to exploit resurrect the object before its finalization and bypass any security checks.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
OBJ32- J | medium high | probable | medium | P8 P12 | L2 L1 |
Automated Detection
TODO
Related Vulnerabilities
...