Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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 MET52-J. 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.

Code Block
bgColor#ffcccc
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");
    }

Ref - See Guideline 6-5: Do not trust identity equality when overridable on input reference objects

http://jroller.com/tackline/entry/user_supplied_objects_may_not

Noncompliant Code Example

How many items are there in layouts in the end?

Code Block
public class Widget {
    private int noOfComponents;
    public Object WidgetgetLicenseKey(intLicenseType noOfComponentslicenseType) {
        this.noOfComponents = noOfComponentsreturn licenseMap.get(licenseType);
    }
    public void setLicenseKey(LicenseType licenseType, String licenseKey) {
        licenseMap.put(licenseType, licenseKey);
    }
}

class LicenseType {
    private String type;
    public intString getNoOfComponentsgetType() {
        return noOfComponentstype;
    }
    public void setNoOfComponentssetType(intString noOfComponentstype) {
        this.noOfComponentstype = noOfComponentstype;
    }

	// Also overrides hashcode() (code is omitted) ...
    @Override
    public int hashCode() {
        int res = 17;
        res = res * 31 + type == null ? 0 : type.hashCode();
        return res;
    }
    @Override
    public boolean equals(Object oarg) {
        if (oarg == null || !(o instanceof Widget)) {
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:

Code Block
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;
    }
		// check for negative components
    @Override
    public boolean equals(Object arg) {
        // Always returns true
        return true;
    }
}

The following is the malicious client program:

Code Block
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 (noOfComponentsguessed <!= 0null) {
        // prints ABC-DEF-PQR-XYZ
  return false      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 the licenseMap 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:

Code Block
bgColor#ccccff
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 (like HashMap) 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.

Code Block
public class DemoClient {
    public static void main(String[] args) {
        LicenseManager licenseManager = new LicenseManager() Widget widget = (Widget) o;
        returnLicenseType this.noOfComponentstype == new LicenseType((Widget) o).getNoOfComponents();
        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:

Code Block
bgColor#ccccff
final class LicenseType {
  // ...
} 

Noncompliant Code Example

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

Code Block
bgColor#ffcccc
public class Widget Navigator{
  extendsprivate Widgetint {noOfComponents;
    
  public NavigatorWidget(int noOfComponents) {
    this.noOfComponents = noOfComponents;
  super(noOfComponents)}
  public int getNoOfComponents() {
    return noOfComponents;
  }
  }public final void setNoOfComponents(int noOfComponents) {
    @Overridethis.noOfComponents = noOfComponents;
  }
  public boolean equals(Object o) {
    if (o == null if|| !(o ==instanceof nullWidget)) {
      return false;
    }
    Widget widget = (Widget) o;
    return falsethis.noOfComponents == widget.getNoOfComponents();
  }
  @Override
  public int hashCode() {
    int res = }31;
    res = res * return17 + truenoOfComponents;
    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();
    }
}

 

Client code

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

Code Block
public class Navigator extends Widget {
  public Navigator(int noOfComponents) {
    super(noOfComponents);
  }
  @Override
  public int hashCode() {
Code Block
        Widget nav = new Navigator(-1);
    int res = 31;
 Widget   widgetres = new Widget(10)res * 17;
    return res;
  }
}

The client code follows:

Code Block
Widget nav  LayoutManager manager = new LayoutManagerNavigator(1);
Widget widget = new Widget(1);
LayoutManager manager = new LayoutManager();
manager.addWidget(nav);
        manager.addWidget(widget);
        System.out.println(manager.getLayoutSize()); // prints 2

Noncompliant Code Example

What gets printed - main or child / both / either ?

Prints 2

The set layouts is expected to contain just one item because the number of components for both the navigator and 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:

Code Block
bgColor#ccccff
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:

Code Block
bgColor#ffcccc
public class Worker
Code Block
public class Trusted implements Runnable {
    TrustedWorker() { }

    public void startThread(String name) {
        new Thread(this, name).start();
    }

    @Override
    public void run() {
        System.out.println("ChildParent");
    }
}

public class UntrustedSubWorker extends TrustedWorker {
	// Note untrusted code may start a new thread even during construction @Override
  public void UntrustedstartThread(String name) {
        super.startThread(name);
	new Thread(this, name).start();  }

      
  }
  @Override
    public void run() {
        System.out.println("MainChild");
  }
}

If a client runs the following code:

Code Block
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():

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

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

Code Block
TrustedWorker aw1 = new UntrustedWorker();
w1.startThread("Mainparent-thread");
a.run();Worker w2 = new SubWorker();
w2.startThread("child-thread");

Bibliography

 

...

Image Added Image Added Image Added