[EN] A-Z: GWTUpload - DoS
GWT is a Java web framework and GWTUpload is a library extending it with easier file upload. We found a vulnerability allowing to abuse the upload process and cause a denial-of-service of a web application.
GWTUpload is used by adding the library to our GWT project and using its classes directly or by extending them. The class which is responsible for handling file upload request is UploadServlet. There’s also UploadAction which is built on top of UploadServlet
, so both of them are interesting for us.
When the servlet receives a POST
request it invokes a parsePostRequest
method.
According to the JavaDoc: This method parses the submit action, puts in session a listener where the progress status is updated, and eventually stores the received data in the user session
. Seems unharmful.
GWTUpload uses ServletFileUpload from Apache Commons which is directly responsible for reading files from an HTTP request. It allows us to set a progress listener, an implementation of the (ProgressListener) interface, which is invoked during the upload to track its process, for example, to implement a dynamic progress bar.
We can notice that GWTUpload creates own listener. To be exact, createNewListener
can create an instance of one of two classes, but both of them extend AbstractUploadListener.
So let’s see what this listener does when Apache’s ServletFileUpload
updates progress status.
That’s weird. Usually, we want to process requests as fast as we can, but here we can intentionally slow it down by sleeping the thread whenever the update is invoked. The question that should arise now - who controls the slowUploads
?
Its value is passed through the constructor.
But, if we look back at the parsePostRequest
method and how listeners are created, we will notice…
…that value for uploadDelay
comes from the request.
So any user who is able to upload files can sleep its own upload thread for any time they want in the range between 0 and 2147483647 (Integer.MAX
). So, any user can sleep one thread for almost 25 days (2147483647 milliseconds is equal to 596 hours).
Why is that a problem? Java servers do not have the unlimited capability of creating new threads. For example, Tomcat in default configuration allows to run up to 200 threads. When this limit is reached new threads cannot be created. If a thread is sleeping it also counts to this limit. So, as we can sleep the upload process, we take out one thread from the pool. What if we sent 200 requests? We would exhaust the server’s thread pool and no new (any, not only upload) requests will be accepted until sleep ends (reminder: 25 days). In effect, the server would not serve anymore.
But there’s one more interesting thing. uploadDelay
is a field in the servlet. Values of fields in servlets are preserved between requests. So, a malicious user can send just one request with the delay value and it will be also applied to further requests, even those sent by other users (unless someone overwrites it).
The last thing to consider is the size of uploaded files, because if a file is too small update
method won’t be executed at all. The buffer size is hardcoded in MultipartStream
class in Apache Commons Fileupload
The whole upload request should have at least 4097 bytes.
Demo
So let’s try to exploit it in some real application. One of the publicly available (however, not developed anymore) web applications based on GWT and utilizing GWTUpload is Hupa, which is part of Apache James project. According to the website:
I downloaded and ran on my server the last available [build] hupa-0.0.5-SNAPSHOT.war. I went through the application to find the file upload functionality, then I tracked the specific request in Burp. I reused the URL and session cookie to build a malicious request that I had been repeating until the server stopped responding.
Also, to confirm what happened, I generated a thread dump (with the command kill -3 PID
) to be sure that all threads were sleeping.
Who’s affected?
Every application utilizing GWTUpload to handle file uploads may be susceptible to the denial of service.
There are a lot of projects on GitHub including GWTUpload library. However, most of them seem to be amateur or abandoned projects.
What now?
GWTUpload itself seems to be not developed anymore (the last commit is dated April 13, 2016). We tried to contact the developer using email address he left on his GitHub profile, however, we haven’t received any response. So we submitted the issue publicly on the GitHub repository.
As we don’t expect the vulnerability to be fixed, we would like to show not so elegant solution that everyone can implement.
Fix DIY
The idea is to modify the code so the Thread.sleep
method won’t be executed. It can be done in many ways but it seems that the easiest one is to remove line 70 in AbstractUploadListener class, so that slowUpload
will always have default 0
value and because of that threads won’t be ever put to sleep.
If we use in our project GWTUpload source code, then the problem is already solved. It’s more problematic if we use jar and we don’t want to recompile the library. In this case, we need to do some bytecode modification.
There’s a tool called Recaf which is a Java bytecode editor. What we need to do:
- Load the jar into the tool
- Find constructor of AbstractUploadListener
- Find bytecode corresponding to line 70.
- Remove bytecode
- Export jar and replace it in our project.
Bonus
If you looked closely you could see a PARAM_MAX_FILE_SIZE
parameter that is also truthfully processed by the library. It can be abused to prevent other users from uploading files but without hanging up the whole application. We’ll leave to the reader to find out the exact way. ;)
Summary
Some vulnerabilities may exist in third party code which we do not control. However, the implications are the same as with the code we write ourselves. In the case of this library, any user with the possibility to upload files is able to completely neutralize our application. However, vulnerabilities in included third party code are not the only kind of problem. There is also a problem who is responsible for fixing such bugs. If a library is not supported anymore by developers we are left to our own devices. We can try to fix a vulnerability if it is simply as in that case, but when bugs are more complicated we may need to be forced to get rid of the vulnerable library.