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

Compare with Current View Page History

« Previous Version 3 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 typhically 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 called");
  }
}
 
class Client {
  public void register(CallBack callback) {
    callback.callMethod();
  }

  public static void main(String[] args) {
    Client client = new Client();
    CallBack callBack = new CallBackImpl();
    client.register(callBack);
  }
} 

Frequently, callback methods are given full privileges which can make them attractive targets. If these methods accept arguments 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.

Noncompliant Code Example

This noncompliant code example uses a CallBackImpl class that implements the CallBack interface. The securityCritical() method accepts a privileged action that is used by its implementation as an argument to a doPrivileged block.

public interface CallBack {
  void securityCritical(PrivilegedAction<String> action);
}

class CallBackImpl implements CallBack {
  public void securityCritical(PrivilegedAction<String> action) {
    AccessController.doPrivileged(action);        
  }
}
 
class Client {
  public void register(CallBack callback) {
    callback.securityCritical(new MaliciousUserLookupAction(7));
  }

  public static void main(String[] args) {
    Client client = new Client();
    CallBack callBack = new CallBackImpl();
    client.register(callBack);
  }
} 

The class Client allows registering the callback so that an untrusted caller can specify the user id to look-up. An object of type UserLookupAction  is expected by the callback, however, an attacker may extend that class and replace it with a malicious implementation in the form of a MaliciousUserAction instance.

public class UserLookupAction implements PrivilegedAction<String> {
  private int userid;
 
  public UserLookupAction(int userid) {
    this.userid = userid;
  }
 
  public String run() {
    String name = null;
    try (InputStream fis = new FileInputStream("/etc/passwd")) {
      // Look up userid & assign to name
    } catch (IOException x) {
      name = null;
    }
    return name;
  }
}
 
class MaliciousUserLookupAction extends UserLookupAction {
  public MaliciousUserLookupAction(int userid) {
    super(userid);
  }

  public String run() {
    System.out.println("Executing untrusted code");
    return null;
  }
}

Consequently, the malicious code will execute with privileges of the class CallBackImpl that defines the callback in securityCritical() method.

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 amends the CallBack interface and instead of accepting the PrivilegedAction objects, the securityCritical() methods accepts the user id to be searched for. The code contained within the UserLookupAction class is moved to the securityCritical() method.

public interface CallBack {
  void securityCritical(int uid);
}
 
class CallBackImpl implements CallBack {
  public void securityCritical(int uid) {
    AccessController.doPrivileged(new PrivilegedAction<String>() {
      public String run() {
        String name;
        try (InputStream fis = new FileInputStream("/etc/passwd")) {
          // Look up userid & assign to name
        } catch (IOException x) {
          name = null;
        }
         return name;
      }
    });        
  }
}
 
class Client {
  public void register(CallBack callback) {
    callback.securityCritical(7);
  }

  public static void main(String[] args) {
    Client client = new Client();
    CallBack callBack = new CallBackImpl();
    client.register(callBack);
  }
} 

Class UserLookupAction is no longer required. This solution does not allow untrusted code to supply arbitrary statements for execution.

Applicability

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

Bibliography

[API 2011]

Todo

[SCG 2010]

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

Guideline 9-2: Beware of callback methods

 

 


  

 

  • No labels