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 CallBackImpl 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(); client.registerCallBackCallBackAction(new CallBackImpl()); // ... 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 18. SEC51-JGJ. 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 a UserLookupCallBack
class that implements the CallBack
interface to look up a user's name given their 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 | ||||
---|---|---|---|---|
| ||||
public interface CallBack { void callMethod(); } class UserLookupCallBack implements CallBack { private int uid; private String name; public UserLookupCallBack(int uid) { this.uid = uid; } 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; } } } final class ClientCallBackAction { private CallBack callback; public void registerCallBackCallBackAction(CallBack callback) { this.callback = callback; } public void doSomethingperform() { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { callback.callMethod(); return null; } }); } } |
This code could be safely used by a client, as follows:
Code Block | ||
---|---|---|
| ||
public static void main(String[] args) { int uid = Integer.parseInt(args[0]); CallBack callBack Client client = new ClientUserLookupCallBack(uid); CallBackAction action CallBack callBack = new UserLookupCallBackCallBackAction(uidcallBack); client.registerCallBack(callBack); // ... clientaction.doSomethingperform(); // looksLooks up user name System.out.println("User " + uid + " is named " + callBack.getName()); } } |
HoweverWhie this code works as expected, an attacker can use it CallBackAction
to execute malicious code with elevated privileges by registering a MaliciousCallBack
instance.:
Code Block |
---|
class MaliciousCallBack implements CallBack { public void callMethod() { // Code here gets executed with elevated privileges } } // ... Client client Client code public static void main(String[] args) { CallBack callBack = new ClientMaliciousCallBack(); client.registerCallBack( CallBackAction action = new MaliciousCallBackCallBackAction(callBack)); client action.doSomethingperform(); // executesExecutes malicious code } |
Compliant
...
Solution (Callback-Local doPrivileged
Block)
According to Oracle's secure coding guidelines [SCG 2010]:
By convention, instances of
PrivilegedAction
andPrivilegedExceptionAction
may be made available to untrusted code, butdoPrivileged
must not be invoked with caller-provided actions.
This compliant solution moves the invocation of doPrivileged()
out of the Client
code CallBackAction
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.
Code Block | ||||
---|---|---|---|---|
| ||||
public interface CallBack { void callMethod(); } class UserLookupCallBack implements CallBack { //private int uid and name fields, other code public; private String name; public UserLookupCallBack(int uid) { this.uid = uid; } public String getName() { return name; } 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; } }); } } final class // ... rest of UserLookupCallBack unchanged } class Client {CallBackAction { private CallBack callback; public CallBackAction(CallBack callback) { this.callback = callback; } public void doSomethingperform() { 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 | ||||
---|---|---|---|---|
| ||||
final class UserLookupCallBack implements CallBack { // ...rest of Client unchanged } // Remaining code is unchanged |
Applicability
Exposing sensitive methods through callbacks can result in misuse of privileges and arbitrary code execution.
Bibliography
[SCG 2010] | Guideline 9-3: Safely invoke |
...