You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 11 Next »

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, and asynchronously reading and writing data from storage and even in Runnable.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 Client {
  CallBack callback;
  public void registerCallBack(CallBack callback) {
    this.callback = callback;
  }
 
  public void doSomething() {
    callback.callMethod();
  }
  public static void main(String[] args) {
    Client client = new Client();
    client.registerCallBack(new CallBackImpl());
    // ...
    client.doSomething(); // prints "CallBack invoked"
  }
}

Callback methods are often invoked with no changes in privileges. This 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 rule is an instance of 18. SEC51-JG. Minimize privileged code.

Noncompliant Code Example

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

public interface CallBack {
  void callMethod();
}
 
class UserLookupCallBack implements CallBack {
  private int uid;
  public UserLookupCallBack(int uid) {
    this.uid = uid;
  }

  private String name;
  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;
    }
  }
}

class Client {
  CallBack callback;

  public void registerCallBack(CallBack callback) {
    this.callback = callback;
  }
 
  public void doSomething() {
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
          callback.callMethod();
          return null;
        }
      });
  }

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

    Client client = new Client();
    UserLookupCallBack callBack = new UserLookupCallBack(uid);

    client.registerCallBack(callBack);
    // ...
    client.doSomething(); // looks up user name
    System.out.println("User " + uid + " is named " + callBack.getName());
  }
}

Whie this code works as expected, an attacker can use it to run malicious code with elevated privileges using code like the following:

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

// ...
Client client = new Client();
client.registerCallBack(new MaliciousCallBack());
client.doSomething(); // performs malicious code

Compliant Solution 

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 Client code and into the callback itself. This code functions the same as before, but an attacker can no longer run malicious callback code with elevated privileges.

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

  // ... rest of UserLookupCallBack unchanged
}
 
class Client {
  public void doSomething() {
    callback.callMethod();
  }

  // ...rest of Client unchanged
}

Applicability

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

Bibliography

[API 2011]

AccessController.doPrivileged()

[SCG 2010]

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

Guideline 9-2: Beware of callback methods

 

 


  

 

  • No labels