In Java SE 6 and later, privileged code must either use the AccessController
mechanism or be signed by an owner (or provider) whom the user trusts. Attackers could link privileged code with malicious code if the privileged code directly or indirectly invokes code from another package. Trusted JAR files often contain code that requires no elevated privileges itself but that depends on privileged code; such code is known as security-sensitive code. If an attacker can link security-sensitive code with malicious code, he or she can indirectly cause incorrect behavior. This exploit is called a mix-and-match attack.
Normally, execution of untrusted code causes loss of privileges; the Java security model rescinds privileges when a trusted method invokes an untrusted one. When trusted code calls untrusted code that attempts to perform some action requiring permissions withheld by the security policy, the Java security model disallows that action. However, privileged code may use a class that exists in an untrusted container and performs only unprivileged operations. If the attacker were to replace the class in the untrusted container with a malicious class, the trusted code might receive incorrect results and misbehave at the discretion of the malicious code.
According to the Java SE Documentation, "Extension Mechanism Architecture" [EMA 2014]:
A package sealed within a JAR specifies that all classes defined in that package must originate from the same JAR. Otherwise, a
SecurityException
is thrown.
Sealing a JAR file automatically enforces the requirement of keeping privileged code together. In addition, it is important to minimize the accessibility of classes and their members.
Noncompliant Code Example (Privileged Code)
This noncompliant code example includes a doPrivileged()
block and calls a method defined in a class in a different, untrusted JAR file:
package trusted; import untrusted.RetValue; public class MixMatch { private void privilegedMethod() throws IOException { try { AccessController.doPrivileged( new PrivilegedExceptionAction<Void>() { public Void run() throws IOException, FileNotFoundException { final FileInputStream fis = new FileInputStream("file.txt"); try { RetValue rt = new RetValue(); if (rt.getValue() == 1) { // Do something with sensitive file } } finally { fis.close(); } return null; // Nothing to return } } ); } catch (PrivilegedActionException e) { // Forward to handler and log } } public static void main(String[] args) throws IOException { MixMatch mm = new MixMatch(); mm.privilegedMethod(); } } // In another JAR file: package untrusted; class RetValue { public int getValue() { return 1; } }
An attacker can provide an implementation of class RetValue
so that the privileged code uses an incorrect return value. Even though class MixMatch
consists only of trusted, signed code, an attacker can still cause this behavior by maliciously deploying a valid signed JAR file containing the untrusted RetValue
class.
This example almost violates SEC01-J. Do not allow tainted variables in privileged blocks but does not do so. It instead allows potentially tainted code in its doPrivileged()
block, which is a similar issue.
Noncompliant Code Example (Security-Sensitive Code)
This noncompliant code example improves on the previous example by moving the use of the RetValue
class outside the doPrivileged()
block:
package trusted; import untrusted.RetValue; public class MixMatch { private void privilegedMethod() throws IOException { try { final FileInputStream fis = AccessController.doPrivileged( new PrivilegedExceptionAction<FileInputStream>() { public FileInputStream run() throws FileNotFoundException { return new FileInputStream("file.txt"); } } ); try { RetValue rt = new RetValue(); if (rt.getValue() == 1) { // Do something with sensitive file } } finally { fis.close(); } } catch (PrivilegedActionException e) { // Forward to handler and log } } public static void main(String[] args) throws IOException { MixMatch mm = new MixMatch(); mm.privilegedMethod(); } } // In another JAR file: package untrusted; class RetValue { public int getValue() { return 1; } }
Although the RetValue
class is used only outside the doPrivileged()
block, the behavior of RetValue.getValue()
affects the behavior of security-sensitive code that operates on the file opened within the doPrivileged()
block. Consequently, an attacker can still exploit the security-sensitive code with a malicious implementation of RetValue
.
Compliant Solution
This compliant solution combines all security-sensitive code into the same package and the same JAR file. It also reduces the accessibility of the getValue()
method to package-private. Sealing the package is necessary to prevent attackers from inserting any rogue classes.
package trusted; public class MixMatch { // ... } // In the same signed & sealed JAR file: package trusted; class RetValue { int getValue() { return 1; } }
To seal a package, use the sealed
attribute in the JAR file's manifest file header, as follows:
Name: trusted/ // Package name Sealed: true // Sealed attribute
Exception
ENV01-J-EX0: Independent groups of privileged code and associated security-sensitive code (a "group" hereafter) may be placed in separate sealed packages and even in separate JAR files, subject to the following enabling conditions:
- The code in any one of these independent groups must lack any dynamic or static dependency on any of the code in any of the other groups. This means that code from one such group cannot invoke code from any of the others, whether directly or transitively.
- All code from any single group is contained within one or more sealed packages.
- All code from any single group is contained within a single signed JAR file.
Risk Assessment
Failure to place all privileged code together in one package and seal the package can lead to mix-and-match attacks.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
ENV01-J | High | Probable | Medium | P12 | L1 |
Automated Detection
Detecting code that should be considered privileged or sensitive requires programmer assistance. Given identified privileged code as a starting point, automated tools could compute the closure of all code that can be invoked from that point. Such a tool could plausibly determine whether all code in that closure exists within a single package. A further check of whether the package is sealed is feasible.
Tool | Version | Checker | Description |
---|---|---|---|
CodeSonar | 8.1p0 | JAVA.INSEC.LDAP.POISON | Potential LDAP Poisoning (Java) |
Android Implementation Details
java.security.AccessController
exists on Android for compatibility purposes only, and it should not be used.
Related Guidelines
Bibliography
[EMA 2014] | Extension Mechanism Architecture, "Optional Package Sealing" |
Rule 7, If you must sign your code, put it all in one archive file | |
16 Comments
Dean Sutherland
TODO check for tools that perform the stated analysis.
Thomas Hawtin
Trusted code should be loaded through a different class loader (possibly a parent, but probably not achild) than that of potentially malicious code.
There's a general problem that you don't necessarily know in what context trusted code will be run. Any new environment really should check that signed code (each unit of signing - typically jars) has opted in to being used in the context.
Dean Sutherland
Your first point about loaders makes perfect sense. There's a guideline to that effect somewhere; we should cross-reference it.
The second point sounds good too. I'm a bit confused, however — How can one check that signed code has opted in to being used in a context? The signed code may well have been written long before the context that wishes to use it was even a gleam in its programmer's eye.
David Svoboda
Don't the NCCE's violate: SEC02-J. Do not allow doPrivileged() blocks to leak sensitive information outside a trust boundary? I am uncomfortable with the overlap between this rule and that one.
Robert Seacord (Manager)
not anymore, as I removed SEC02-J
Yozo TODA
dead link to OBJ51-J("Minimize the accessibility of classes and their members") remains.
should we delete this?
or, is there some appropriate guideline to refer here?
David Svoboda
I deleted the link. The guideline (OBJ51-J) does exist, but is a guideline, not a rule, so it is not part of this standard.
Yozo TODA
Where are those guidelines? (recommendations?)
How can I see those?
Masaki Kubo
Comment:
The following sentence is not easy to comprehend.
The reason seems to me it is not easy to draw a big picture of the relationship between untested code, trusted code, privileged code, unprivileged action, trusted container, and untrusted container.
David Svoboda
You are correct; such a picture does not exist (and would likely be incomprehensible if it did). I wordsmithed the paragraph; hopefully it's clearer now.
Yitzhak Mandelbaum
There's a typo of sorts in the first NCCE. The line with ???????. Not sure what's intended, but I suspect it is not that.
Robert Seacord
looks like this was just accidentally added. look back a few pages in the history and you should be able to find and restore the correct code example.
Yitzhak Mandelbaum
Does this rule scale? On the surface, it would seem to rule out the use of shared libraries from security-sensitive code. Even in the absence of any concern of shared libraries, just using third-party libraries requires that you package them with your code. I could imagine all sorts of headaches from that requirement.
More generally, do we have any external corroboration/justification for this rule? Hawtin's suggestion of Aug 2010 seems like a better approach to solving the problem. Is there any reason not to use his suggestion in favor of this rule?
Brad Senetza
I don't see this as scaling either. For an enterprise level application that may rely on many components, repackaging into a single jar doesn't seem feasible. Plus given the changes to Java, I'm not totally sure this is even needed anymore.
Fred Long
I agree that this rule does not scale.
Please could you explain more about the changes to Java that you think make this rule unnecessary. Thanks.
Brad Senetza
I'm not saying that mixed code can't happen but given that Java now requires indexing before signing, permissions and codebase were added for re-deploy protection, and you pretty much now have to say allow mixed code, is the attack vector still applicable? I was just wondering and haven't done any testing.
http://docs.oracle.com/javase/tutorial/deployment/deploymentInDepth/bestPractices.html
http://docs.oracle.com/javase/tutorial/deployment/doingMoreWithRIA/security.html