Stack traces are commonly used for debugging purposes by software developers in order to find what went wrong in the application they are developing. The traces contain useful information and only occur when something goes wrong, unless someone intentionally causes an error. These errors should be gracefully handled by their code, logged, and safely served to application users in the best possible way.
Software developers can use stack traces to track a bug back to the root cause of the error - and so can we - as information in the traces may expose some useful information, which could lead us to a vulnerability.
Analysing Stack Traces
In my recent test, I came across several Java stack traces and I thought that these might lead to something useful. An example stack trace that looked interesting is shown below:
java.lang.NullPointerException […] at com.vaadin.navigator.Navigator.navigateTo(Navigator.java:616)
NullPointerException is a very common event that typically occurs when a variable has been declared and then used without it being initialised. This stack trace gives us two things:
- Package path: this often contains the domain name of the organisation or individual. This could also lead to the software name that is being used, "Vaadin" in this case. The naming convention is recommended by the vendor according to the following URL: https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html
- File name and line number: combining the package path with this information would lead us to the exact file involved in the error.
java.lang.IllegalStateException: Received legacy variable change for […] (58) which is not a VariableOwner. The client-side connector sent these legacy varaibles: [curText, c] at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:425) at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:273) at com.vaadin.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:79)
IllegalStateException, according to the Java documentation, is normally thrown when a method has been invoked at an illegal or inappropriate time. However, the exception was thrown by the "Vaadin" web framework rather than the Java library, which means something completely different.
Useful exceptions normally come with a descriptive error message of what went wrong or why it happened. The exception above mentions that legacy variables have been received, and is a worded in a very specific way. This could aid us in tracking down an exact match for the line of code in the framework file that threw the error.
Determining the Open Source Software Version
At this point, we have package paths, file names, and line numbers, which is enough for us to roughly determine the version of Vaadin web framework. The framework is an open source project and available on GitHub, which allows us to browse through the state of the project at each version and Git commit.
Using the exception string above, we can either search the repository for this string or navigate to the file by using the file package path and file name. Both methods allow us to find the piece of code that throws the exception. At the time of writing, the current version of the Vaadin Framework (8.1.0.alpha6) shows that the code appears at line 473 of the "ServerRpcHandler.java" file, which does not match with line 425 in the stack trace above.
GitHub offers the history of an individual file that and lists all of the changes in the file, known as commits. The URL of the file history is below:
We can start looking at the "ServerRpcHandler.java" file of each commit and find which one has the exception string code at line 425. It appears that the "a6653d3" commit is the only commit that has that code at line 425, as shown below:
Armed with the date and time of the commit, we can find the closest released version of the framework in the repository "Releases" tab, which is version 7.6.5.
Denial of Service (DoS)
The following stack trace was originally huge, but it has been shortened for sanity.
java.lang.StackOverflowError at java.lang.System.getProperty(System.java:705) at sun.security.action.GetPropertyAction.run(GetPropertyAction.java:84) […] at com.vaadin.navigator.Navigator.navigateTo(Navigator.java:616) at com.vaadin.navigator.Navigator.navigateTo(Navigator.java:573) at […] at com.vaadin.navigator.Navigator.navigateTo(Navigator.java:616) at com.vaadin.navigator.Navigator.navigateTo(Navigator.java:573) at […] at com.vaadin.navigator.Navigator.navigateTo(Navigator.java:616) at com.vaadin.navigator.Navigator.navigateTo(Navigator.java:573) […]
According to the Java documentation, the "StackOverflowError" exception will be thrown when a recursive function has been called too many times. A recursive function is a function that calls itself a number of times until a result is returned. Non-return recursive function calls will exhaust all the available stack space, which can consume a high amount of memory.
Note that this exception was being thrown by "java.lang.System.getProperty", which is within the Java library itself. The full function path ensures that the "Vaadin" code did not produce the exception and the error occurred because of the reason stated in the Java documentation. Additionally, the lines in the stack trace appear to be repeated many times. We can conclude that the recursive calls cause the error and can lead to denial of service (DoS).
Stack traces can consist of different types of information, depending on the platform, framework, or programming language. They should only be used locally for debugging purposes. Exposing them to the Internet is likely to leak some useful information to attackers, no matter how insignificant it may seem, and it also results in bad user experience. Therefore, exposing stack traces is not a good idea. In addition, leaked information can lead to the discovery of vulnerabilities, including critical ones. It has always been considered good practice to handle errors gracefully and log every event that occurs.