Failure to filter sensitive information when propagating exceptions often results in information leaks that can assist an attacker's efforts to develop further exploits. An attacker may craft input arguments to expose internal structures and mechanisms of the application. Both the exception message text and the type of an exception can leak information. For example, the FileNotFoundException
message reveals information about the file system layout, and the exception type reveals the absence of the requested file.
This rule applies to server-side applications as well as to clients. Attackers can glean sensitive information not only from vulnerable web servers but also from victims who use vulnerable web browsers. In 2004, Schönefeld discovered an exploit for the Opera v7.54 web browser in which an attacker could use the sun.security.krb5.Credentials
class in an applet as an oracle to "retrieve the name of the currently logged in user and parse his home directory from the information which is provided by the thrown java.security.AccessControlException
" [Schönefeld 2004].
All exceptions reveal information that can assist an attacker's efforts to carry out a denial of service (DoS) against the system. Consequently, programs must filter both exception messages and exception types that can propagate across trust boundaries. The following table lists several problematic exceptions.
Exception Name | Description of Information Leak or Threat |
---|---|
| Underlying file system structure, user name enumeration |
| Database structure, user name enumeration |
| Enumeration of open ports when untrusted client can choose server port |
| May provide information about thread-unsafe code |
| Insufficient server resources (may aid DoS) |
| Resource enumeration |
| Underlying file system structure |
| Owner enumeration |
| DoS |
| DoS |
Printing the stack trace can also result in unintentionally leaking information about the structure and state of the process to an attacker. When a Java program that is run within a console terminates because of an uncaught exception, the exception's message and stack trace are displayed on the console; the stack trace may itself contain sensitive information about the program's internal structure. Consequently, any program that may be run on a console accessible to an untrusted user must never abort due to an uncaught exception.
Noncompliant Code Example (Leaks from Exception Message and Type)
In this noncompliant code example, the program must read a file supplied by the user, but the contents and layout of the file system are sensitive. The program accepts a file name as an input argument but fails to prevent any resulting exceptions from being presented to the user.
class ExceptionExample { public static void main(String[] args) throws FileNotFoundException { // Linux stores a user's home directory path in // the environment variable $HOME, Windows in %APPDATA% FileInputStream fis = new FileInputStream(System.getenv("APPDATA") + args[0]); } }
When a requested file is absent, the FileInputStream
constructor throws a FileNotFoundException
, allowing an attacker to reconstruct the underlying file system by repeatedly passing fictitious path names to the program.
Noncompliant Code Example (Wrapping and Rethrowing Sensitive Exception)
This noncompliant code example logs the exception and then wraps it in a more general exception before rethrowing it:
try { FileInputStream fis = new FileInputStream(System.getenv("APPDATA") + args[0]); } catch (FileNotFoundException e) { // Log the exception throw new IOException("Unable to retrieve file", e); }
Even when the logged exception is not accessible to the user, the original exception is still informative and can be used by an attacker to discover sensitive information about the file system layout.
Note that this example also violates FIO04-J. Release resources when they are no longer needed, as it fails to close the input stream in a finally
block. Subsequent code examples also omit this finally
block for brevity.
Noncompliant Code Example (Sanitized Exception)
This noncompliant code example logs the exception and throws a custom exception that does not wrap the FileNotFoundException
:
class SecurityIOException extends IOException {/* ... */}; try { FileInputStream fis = new FileInputStream(System.getenv("APPDATA") + args[0]); } catch (FileNotFoundException e) { // Log the exception throw new SecurityIOException(); }
Although this exception is less likely than the previous noncompliant code examples to leak useful information, it still reveals that the specified file cannot be read. More specifically, the program reacts differently to nonexistent file paths than it does to valid ones, and an attacker can still infer sensitive information about the file system from this program's behavior. Failure to restrict user input leaves the system vulnerable to a brute-force attack in which the attacker discovers valid file names by issuing queries that collectively cover the space of possible file names. File names that cause the program to return the sanitized exception indicate nonexistent files, whereas file names that do not return exceptions reveal existing files.
Compliant Solution (Security Policy)
This compliant solution implements the policy that only files that live in c:\homepath
may be opened by the user and that the user is not allowed to discover anything about files outside this directory. The solution issues a terse error message when the file cannot be opened or the file does not live in the proper directory. Any information about files outside c:\homepath
is concealed.
The compliant solution also uses the File.getCanonicalFile()
method to canonicalize the file to simplify subsequent path name comparisons (see FIO16-J. Canonicalize path names before validating them for more information).
class ExceptionExample { public static void main(String[] args) { File file = null; try { file = new File(System.getenv("APPDATA") + args[0]).getCanonicalFile(); if (!file.getPath().startsWith("c:\\homepath")) { System.out.println("Invalid file"); return; } } catch (IOException x) { System.out.println("Invalid file"); return; } try { FileInputStream fis = new FileInputStream(file); } catch (FileNotFoundException x) { System.out.println("Invalid file"); return; } } }
Compliant Solution (Restricted Input)
This compliant solution operates under the policy that only c:\homepath\file1
and c:\homepath\file2
are permitted to be opened by the user. It also catches Throwable
, as permitted by exception ERR08-J-EX2 (see ERR08-J. Do not catch NullPointerException or any of its ancestors). It uses the MyExceptionReporter
class described in ERR00-J. Do not suppress or ignore checked exceptions, which filters sensitive information from any resulting exceptions.
class ExceptionExample { public static void main(String[] args) { FileInputStream fis = null; try { switch(Integer.valueOf(args[0])) { case 1: fis = new FileInputStream("c:\\homepath\\file1"); break; case 2: fis = new FileInputStream("c:\\homepath\\file2"); break; //... default: System.out.println("Invalid option"); break; } } catch (Throwable t) { MyExceptionReporter.report(t); // Sanitize } } }
Compliant solutions must ensure that security exceptions such as java.security.AccessControlException
and java.lang.SecurityException
continue to be logged and sanitized appropriately (see ERR02-J. Prevent exceptions while logging data for additional information). The MyExceptionReporter
class from ERR00-J. Do not suppress or ignore checked exceptions demonstrates an acceptable approach for this logging and sanitization.
For scalability, the switch
statement should be replaced with some sort of mapping from integers to valid file names or at least an enum type representing valid files.
Risk Assessment
Exceptions may inadvertently reveal sensitive information unless care is taken to limit the information disclosure.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
ERR01-J | Medium | Probable | High | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Parasoft Jtest | 2024.1 | CERT.ERR01.ACPST CERT.ERR01.CETS CERT.ERR01.ACW | Do not call the 'printStackTrace()' method of "Throwable" objects Catch all exceptions which may be thrown within Servlet methods Avoid writing to Consoles |
SonarQube | 9.9 | S1989 | Exceptions should not be thrown from servlet methods |
Related Vulnerabilities
CVE-2009-2897 describes several cross-site scripting (XSS) vulnerabilities in several versions of SpringSource Hyperic HQ. These vulnerabilities allow remote attackers to inject arbitrary web script or HTML via invalid values for numerical parameters. They are demonstrated by an uncaught java.lang.NumberFormatException
exception resulting from entering several invalid numeric parameters to the web interface.
CVE-2015-2080 describes a vulnerability in the Jetty web server, versions 9.2.3 to 9.2.8, where an illegal character passed in an HTML request causes the server to respond with an error message containing the text with the illegal character. But this error message can also contain sensitive information, such as cookies from previous web requests.
Related Guidelines
VOID ERR12-CPP. Do not allow exceptions to transmit sensitive information | |
CWE-209, Information Exposure through an Error Message |
Bibliography
9.1, Security Exceptions | |
\[Gotham 2015\] | JetLeak Vulnerability: Remote Leakage Of Shared Buffers In Jetty Web Server |
[Schönefeld 2004] |
27 Comments
Thomas Hawtin
Throwing another exception rather the using println would be better. Also the text shouldn't be a string literal, as different text would pinpoint the cause as much as the FileNotFoundException.
The more usual problem is code adding, say, the filename to the exception message. The typical fix there is to throw a new exception with a fixed message.
Also args[ 1] should be args[ 0].
Dhruv Mohindra
I changed the solution so that a new exception is thrown that is common for all methods that want to use this feature. Also fixed the misc issues, earlier.
Dhruv Mohindra
To me it sounds like a platform dependent guideline. Perfectly valid if you are running a web server; not as much for software designed to run on a local machine. The argument for the current classification is that assuming all other rules are enforced (securing the filesystem, sanitizing database inputs...), an attacker cannot do much with the information learned. All the same, it is important enough to become a rule. I thus take a neutral stance on this...let's wait for more comments before moving it.
Thomas Hawtin
Disclosing sensitive information allows an adversary to "explore the attack surface". It may include information about the filesystem layout for further attacks and the local user name. As an example, Sun Alert 200841 involves amongst
other things file locations returned via an exception.
This type of bug is particularly difficult to spot because of the non-locality. Normally you can see all the arguments of a method come in and the return type leave (complication with callbacks, or course), but exceptions fly straight through from any method you might have called. In addition they are likely to be poorly documented/specified. You might want to even add something about wrapping exceptions, particular checked exceptions within unchecked.
David Svoboda
This sounds related to the rule EXC01-J. Use a class dedicated to reporting exceptions, mainly because the question of whether information in an exception is 'sensitive' may not be known by the method that throws the exception. IOW your method should not avoid throwing an exception b/c it contains sensitive info, instead you should throw the exception, and later catch it inside a method that filters out sensitive info. That way a library that throws potentially-sensitive exceptions can be used by different applications that have different definitions of what is sensitive. So each application has its own filter to weed out sensitive info from thrown exceptions.
Dhruv Mohindra
I think, exceptions can be logged at the triggering point and we can use the dedicated class to simply display a cleansed message. That way no information will get leaked out, sensitive or insensitive, and we can make this guideline a rule. Comments?
David Svoboda
I agree, assuming that logging an exception doesn't leak sensitive information. I would use the ExceptionReporter class (from EXC05-J) to handle logging, as we can also give it the task of filtering the log as necessary (or choose that the log requires no filtering). You can use a dedicated Logger class to handle filtering, but that doesn't remove the necessity of ExceptionReporter, so it seems more complicated to me to let the trigger point do logging.
Thomas Hawtin
IIRC, Brian Chess/Fortify gives the example that (US) SSNs should never appear in logs. It could be very difficult to know that the data is of such a category from the sort of low-level code that throws an exception.
The general advice is catch as far up as possible. This may be a main event dispatch loop or even just event fire code (where an exception from one listener is not allow to consume the event). My suggestion is that if you parameterise exception (catch) handling, always do it as an instance (in fact just stay well away from any non-constant static whatsoever).
Dhruv Mohindra
It does make sense to filter out the sensitive data before logging. I am not sure if I follow your suggestion exactly (specifically the non-const static part). In general, do you recommend something like the following snippet that Brian Chess suggests? [Chess 07]
or,
or something completely different? Thanks.
David Svoboda
I guess my idea was to use the
ExceptionReporter
to handle filtering; it would contain any info on how to catch exceptions; including filtering out sensitive information.Dhruv Mohindra
Sounds like a good suggestion. For client facing code (or if you don't trust the FS where you keep your log files), however, won't a whitelist based filtering mechanism be safer and also easier to enforce (as in, show only a default page to the user unless you know that some exception is non-sensitive and the user ought to know about it)? An alternative could be to use the filtering utilities of
java.util.logging
. More on that in a different rule.Update: Scouting around I found this that supports the above reasoning -
This would be simple to incorporate in your example by switching SensitiveException to NonSensitiveException and switching the 'if' statement params.
We could also minimize code that is capable of throwing an exception in the exception handler itself. For example, return a sanitized exception from
report()
to the top level that triggered it and subsequently decide at that point whether a log entry has to be made. Basically, decoupling the exception sanitization and logging.Let me know your opinion. Thanks.
David Svoboda
I agree, you should definitely use a whitelist of 'insensitive exceptions' rather than a blacklist of sensitive exceptions as you suggest.
You can decouple the filtering from the logging if you wish, as the filesystem may have a different level of 'sensitivity' than a user dialog box. But that's a rabbit hole we could spend forever exploring. Prob best to say that any reporting of an exception, whether to a logfile, console message, or user dialog box, needs some consideration of what filtering may be necessary, to protect the exception from the user. Or to protect the user from the exception
Dhruv Mohindra
Sure. My reasoning behind separating the filtering from logging/reporting was to follow EXC07-J. Prevent exceptions while logging data. If someone keeps adding potential exception causing statements to the exception handler, an attacker may be able to subvert the logging step altogether.
David Svoboda
IMHO EXC02-J's raison d'etre is to prevent exceptions from going unreported. It deals specifically with logging, but you can interpret it to also deal with such amenities as dialog boxes or console error messages. The bottom line is, if an exception is thrown while in an exception handler (no matter what the handler is doing), the newly thrown exception 'hides' the original exception that caused the handler to be called in the first place. Conclusion: an exception handler should catch any exceptions its code might throw so that the handler may terminate normally. Pardon the pun, but there is an exception to this rule in that an exception handler may explicitly throw its own exception. Which should indicate the same error (possibly filtered) that caused the exception handler to run.
Maybe we need to generalize EXC02-J.
Dhruv Mohindra
I agree that EXC02-J. needs to be generalized. (it seems kind of broken to me anyway)
On second thoughts, I don't see anything wrong with handling exceptions that can be thrown by the handler code, within the handler and using a
finally
block to make sure that the original exception is not swallowed (by calling thefilter
method).To that effect, logging could remain within the handler. All evidence is pointing to catching
Throwable
in the client code and calling the exception handler on it. If this approach is the way to go, all items should be modified accordingly. I would prefer sticking to catching specific exceptions though for all other examples because checked exceptions are there for a reason. A hybrid (or use instanceof):But if the code changes to throw some new checked exception, it will probably go unnoticed.
Dhruv Mohindra
From Sun's secure coding guidelines doc -
Unsure if the exception handler can determine that so I suppose the caller catch block should use instanceof to check for the exception that it should not catch otherwise pass the Throwable/exception to the handler method.
David Svoboda
I think the 1st NCCE has some implicit assumptions we need to examine:
I suspect Sun's guideline assumes that if the user is expected to supply a filename, then withholding the fact that the filename is invalid is not good security policy. Basically you can either keep the user ignorant of the filesystem, or let them be aware of it. Keeping them aware means they supply a filename, and are not shielded from FileNotFound exceptions. Keeping them ignorant means they supply no filenames, and if a FileNotFound exception occurs, it is filtered into something innocous. If a FileNotFound exception occurs as a direct result of their actions (eg they supply some info which actually is a filename, but they don't know that), the FileNotFound exception is converted to an exception they can understand...eg invalid-input-exception (with no mention of files).
To summarize, while the NCCE/CS code is valid, I think some of the above assumptions need to be made explicit. I'll recommend that we assume the user knows nothing about the files for the purpose of the NCCE/CS. Perhaps we add another NCCE/CS where the user does know about files & supplies the (invalid) filename.
Dhruv Mohindra
Good comment. I lean towards the 'user does not know the filenames' approach that I wrote about in the CS. In this case I expect the user to select names without knowing the real filenames.
Sun's quote seems to suggest to me that the user already knows what files exist (possibly in a secured directory on the server) and wants to operate on those. If it is an invalid file, the exception will only help the user, but I guess care must be taken to hide the file path even in this case. That's why something seemed amiss.
John Markh
How about exceptions that are not transmitted but stored?
For example: LOGGER.debug("personalData== "+personalData);
In this case, personal information is written to a debug log file without proper sanitization. As a result, private information protected in the database (or other form of secure data repository) , could become a accessible to system administrators, support personnel and be subject to a different backup and retention policy (for example, if a web server backup tapes are stored off-site).
David Svoboda
The short answer then would be 'it depends'. Specifically it depends on the security around your log file. The definition of 'sensitive information' can be taken to mean "info that could cause harm if it escapes a perimeter of trust", and in your example the debug log file would be within that perimeter. But in that case the perimeter of trust extends outside the JVM into your filesystem, and so it is out of the scope of this standard.
John Markh
Not so sure I agree to that statement... If I change the given example from LOGGER.debug() to System.err.println("personalData=="+personalData) it will be within the JVM (console, error file, etc.) but the result is the same: leakage of sensitive data.
I think that title should a bit more generic cover other methods of handling information produced by exceptions such as displaying it on the screen, transmitting, storing, etc.
Maybe something like: "EXC06-J. Do not allow exceptions to expose sensitive information"?
David Svoboda
I adopted your suggestion. It's perfectly ok for an exception handler to log sensitive info or send it to stderr, provided that the log file (or stderr) is considered within your security. EG for a server app where the machine is not accessible to the outside world except for the server.
John Markh
Agree that logging certain sensitive information could be acceptable as long as the newly created data repository is within the "circle of trust" (security boundary). On the other hand, it is important to consider specific requirements by security standards such as Payment Card Industry (PCI) Data Security Standard (DSS) or Payment Applications (PA) DSS which does not allow credit card data (specifically Personal Account Number) to be stored in a log file.
It could be out of scope for this endeavour... or maybe an additional rule (within 49. Miscellaneous) for system designer/architects to:
John Markh
Robert Seacord (Manager)
The following seems very tacked on here and might be better as a separate but short rule with it's own example:
Dhruv Mohindra
IOException is a checked exception and NOT unchecked. Text needs to be changed - just say wrap the exception and rethrow.
enum
OR at least a sentence should be added that using an enum provides a scalable and cleaner way to comply.David Svoboda
Done
Reworded
Sentence added.
Dirk Stubbs
This wiki itself can throw an exception and expose the stacktrace. See https://www.securecoding.cert.org/confluence/rest/dashboardmacros/1.0/updates?maxResults=40.