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

java.io.FileNotFoundException

Underlying file system structure, user name enumeration

java.sql.SQLException

Database structure, user name enumeration

java.net.BindException

Enumeration of open ports when untrusted client can choose server port

java.util.ConcurrentModificationException

May provide information about thread-unsafe code

javax.naming.InsufficientResourcesException

Insufficient server resources (may aid DoS)

java.util.MissingResourceException

Resource enumeration

java.util.jar.JarException

Underlying file system structure

java.security.acl.NotOwnerException

Owner enumeration

java.lang.OutOfMemoryError

DoS

java.lang.StackOverflowError

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

ToolVersionCheckerDescription
Parasoft Jtest
2023.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
S1989Exceptions 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

SEI CERT C++ Coding Standard

VOID ERR12-CPP. Do not allow exceptions to transmit sensitive information

MITRE CWE

CWE-209, Information Exposure through an Error Message
CWE-497, Exposure of System Data to an Unauthorized Control Sphere
CWE-600, Uncaught Exception in Servlet

Bibliography


27 Comments

  1. 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].

    1. 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.

  2. Regarding EXC01-J, the kind of (sensitive) information revealed through exceptions by itself does not always cause a vulnerability. That's why it's a recommendation. If it becomes a rule then there would be questions on what exactly constitutes "sensitive information". Unless that whole gap of determining what all "sensitive" includes is filled (which would be a good exercise actually), we could keep it the way it is. Do let me know if you have other inputs/ideas regarding its current classification.
    ___
    What information is considered 'sensitive' is defined by your security policy. For instance, if the user already has access to the file system, then information such as file system structre is not 'sensitive', and exceptions like FileNotFoundException require no filtering.

    I still say that EXC01-J should be a rule not a rec, as sensitive info disclosure is a security vulnerability by definition. Let the policymakers decide what constitutes 'sensitive'...that argument is really irrelevant to the validity of EXC01-J.

    Further discussion about the validity of EXC01-J should go there.

    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.

    1. 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.

      1. 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.

        1. 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?

          1. 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.

            1. 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).

              1. 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]

                try { 
                  ...
                }
                catch (SomeNonSensitiveException e) {
                  logger.error("something failed", e);
                }
                catch (Throwable t) {
                  logger.error("caught Throwable at top level", t);
                }
                

                or,

                try{
                 ...
                }
                catch(Throwable t){
                  logger.error("caught Throwable at top level", t);
                    if (t instanceof SomeSensitiveOrNonsensitiveException){
                        ExceptionReporter.handle(SomeException) t;  // cleanses 
                    }
                
                }
                

                or something completely different? Thanks.

                1. 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.

                  try { 
                    ...
                  }
                  catch(Throwable t) {
                    MyExceptionReporter.handle(t);
                  }
                  
                  class MyExceptionReporter implements ExceptionReporter {
                    public void report(Throwable exception) {
                      final Throwable filteredException =
                        (exception instanceof SensitiveException)
                        ? filter((SensitiveException) exception)
                        : exception;
                      Logger.error( filteredException);
                  
                      /* do any other reporting necessary, such as a dialog box */
                    }
                  
                    public Exception filter(SensitiveException exception) {
                      /* Create an 'unsensitive' exception from the exception argument * /
                    }
                  }
                  
                  1. 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 -

                    The "deny" model is an alternative to the "allow" model that is used in the Exception Shielding pattern. In the deny model, specific exceptions are registered to be sanitized, and all other exceptions are sent back to the client unmodified. However, the deny model is considered less secure, because unanticipated exceptions are not sanitized.

                    This would be simple to incorporate in your example by switching SensitiveException to NonSensitiveException and switching the 'if' statement params.

                     (exception instanceof NonSensitiveException)
                          ? exception
                          : filter((SensitiveException) exception);
                    

                    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.

                    1. 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 (smile)

                      1. 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. (smile)

                        1. 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.

                          1. 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 the filter 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):

                            try {
                             ...
                            } catch(LowLevel l1) { /* recover or call handler */ }
                            catch(LowLevel l2) { /* recover or call handler */ }
                            catch(Throwable t) { /* call handler */ }
                            

                            But if the code changes to throw some new checked exception, it will probably go unnoticed. (sad)

  3. From Sun's secure coding guidelines doc -

    Do not sanitize exceptions containing information derived from caller inputs. If a caller provides the name of a file to be opened, for example, do not sanitize any resulting FileNotFoundException thrown when attempting to open that file.

    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.

    1. I think the 1st NCCE has some implicit assumptions we need to examine:

      • The name of the file is indeed supplied by the user
      • Revealing filesystem info is bad.

      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.

      1. 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.

  4. 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).

    1. 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.

      1. 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"?

        1. 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.

          1. 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:

            • establish software security boundaries
            • consider legal and regulatory requirements when designing security mechanisms (logging, encryption, security policy, code signing, key management, etc.)

            John Markh

  5. The following seems very tacked on here and might be better as a separate but short rule with it's own example:

    Printing the stack trace can also result in unintentionally leaking information about the structure and state of the process to an attacker. If a Java program is run within a console, and it terminates because of an uncaught exception, the exception's message and stack trace are displayed on the console; the stack trace may itself leak sensitive information about the program's internal structure. Consequently, command-line programs must never abort because of an uncaught exception.

    • In Noncompliant Code Example (Wrapping and Rethrowing Sensitive Exception):
      IOException is a checked exception and NOT unchecked. Text needs to be changed - just say wrap the exception and rethrow.
    • Can this sentence be reworded -

      queries that result in the sanitized message exclude the requested file, the remaining possibilities represent the actual files.

      ?.
    • The switch-case in 2nd CS should be replaced with an enum OR at least a sentence should be added that using an enum provides a scalable and cleaner way to comply.
      • In Noncompliant Code Example (Wrapping and Rethrowing Sensitive Exception): IOException is a checked exception and NOT unchecked. Text needs to be changed - just say wrap the exception and rethrow.

      Done

      • Can this sentence be reworded -

        queries that result in the sanitized message exclude the requested file, the remaining possibilities represent the actual files.

        ?.

      Reworded

      • The switch-case in 2nd CS should be replaced with an enum OR at least a sentence should be added that using an enum provides a scalable and cleaner way to comply.

      Sentence added.