Versions Compared

Key

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

Callbacks provide a means to register a method to be invoked (or called back  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 asynchronous reads and writes to 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.:

Code Block
public interface CallBack {
  void callMethod();
}
 
class MyCallBackCallBackImpl implements CallBack {
  public void callMethod() {
    System.out.println("CallBack invoked");
  }
}
 
class ClientCallBackAction {
  private CallBack callback;

  public void registerCallBackCallBackAction(CallBack callback) {
    this.callback = callback;
  }
 
  public void doSomethingperform() {
    callback.callMethod();
  }
}
class Client {
  public static void main(String[] args) {
    ClientCallBackAction clientaction = new Client();
    CallBack myCallBack = CallBackAction(new MyCallBackCallBackImpl();
    client.registerCallBack( myCallBack);
    // ...
    clientaction.doSomethingperform(); // printsPrints "CallBack invoked"
  }
}

Callback methods are often invoked with no without changes in privileges. This , 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 SEC51-J. Minimize privileged code and is related to SEC01-J. Do not allow tainted variables in privileged blocks.

Noncompliant Code Example

This noncompliant code example uses CallBackImpl a UserLookupCallBack class that implements the the CallBack interface . The securityCritical() method accepts a privileged action that is used by its implementation as an argument to a doPrivileged blockto 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, the Client class invokes all callbacks with elevated privileges (within a doPrivileged block).

Code Block
bgColor#ffcccc
lang#FFccccjava
public interface CallBack {
  void securityCriticalcallMethod(PrivilegedAction<String> action);
}
 
class CallBackImplUserLookupCallBack implements CallBack {
  publicprivate void securityCritical(PrivilegedAction<String> action) {
    AccessController.doPrivileged(action); int uid;
  private String name;

  public UserLookupCallBack(int uid) {
    this.uid =  uid;
  }
}
 
class Client {
  public voidString registergetName(CallBack callback) {
    callback.securityCritical(new MaliciousUserLookupAction(7))return name;
  }

  public static void maincallMethod(String[] args) {
    try Client(InputStream clientfis = new Client();
    CallBack callBack = new CallBackImpl()FileInputStream("/etc/passwd")) {
      // Look up uid & assign to name
    } catch (IOException x) {
      name = null;
    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.

Code Block
public class UserLookupAction implements PrivilegedAction<String>

final class CallBackAction {
  private intCallBack useridcallback;
 
  public UserLookupActionCallBackAction(intCallBack useridcallback) {
    this.useridcallback = useridcallback;
  }
 
  public Stringvoid runperform() {
    String name = null;
AccessController.doPrivileged(new PrivilegedAction<Void>() {
     try (InputStream fis =public newVoid FileInputStream("/etc/passwd"))run() {
      // Look up userid & assign to name
    } catch (IOException x    callback.callMethod();
          return null;
        }
      });
  }
}

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

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

  CallBack namecallBack = null 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:

Code Block
class MaliciousCallBack implements CallBack }
    return name;
  }
}
 
class MaliciousUserLookupAction extends UserLookupAction {
  public MaliciousUserLookupAction(int useridvoid callMethod() {
    // Code here gets executed with elevated super(userid);privileges
  }
}

// Client code
public static void main(String[] run(args) {
  CallBack  System.out.println("Executing untrusted code"callBack = new MaliciousCallBack();
  CallBackAction action return null;
  }
}

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

Compliant Solution 

= 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 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() methodmoves the invocation of doPrivileged() out of the CallBackAction code and into the callback itself.

Code Block
bgColor#ccccff
langjava
public interface CallBack {
  void securityCriticalcallMethod(int uid);
}
   
class CallBackImplUserLookupCallBack implements CallBack {
  public void securityCriticalprivate int uid;
  private String name;
 
  public UserLookupCallBack(int uid) {
    this.uid = uid;
  }
 
  public String getName() {
    return name;
  }
 
  public void callMethod() {
    AccessController.doPrivileged(new PrivilegedAction<String>PrivilegedAction<Void>() {
        public StringVoid run() {
          try (InputStream fis String= name;
        try (InputStream fis = new FileInputStream("/etc/passwd")) {
            // Look up userid & assign to 
            // UserLookupCallBack.this.name
          } catch (IOException x) {
            UserLookupCallBack.this.name = null;
          }
          return namenull;
        }
      });        
  }
}
  
final class ClientCallBackAction {
  private CallBack callback;

  public void registerCallBackAction(CallBack callback) {
    this.callback = callback.securityCritical(7);
  }
 
  public static void mainperform(String[] args) {
    Client client = new Client();
    CallBack callBack = new CallBackImpl();
    client.register(callBack);
  }
} 

...

callback.callMethod();
  }
}

This code behaves the same as before, but an attacker can no longer run 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().

Code Block
bgColor#ccccff
langjava
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 20112014]

AccessController.doPrivileged()

[SCG 2010]

Guideline 9-3: Safely invoke java.security.AccessController.doPrivileged
Guideline 9-2: Beware of callback methods

 

 

...

Image Modified Image Modified Image Modified