Documentation

Documentation versions (currently viewingVaadin 24)

Common Vulnerabilities

Desriptions of common vulnerabilities (e.g., SQL injections, cross-site request forgeries, cross-site scripting).

SQL Injections

Since Vaadin is a backend-agnostic UI framework, it doesn’t interact directly with backend access. Instead, choosing a backend framework (e.g., Spring Data) is left to the developer. This page describes common vulnerabilities such as SQL injections, Cross-Site Request Forgeries (CSRF), and Cross-Site Scripting (XSS).

Vaadin doesn’t provide mitigation for SQL injections. This is left to the backend provider and developer. However, following the data validation and escaping guidelines (see the Cross-Site Scripting section), as well as standard secure database access practices, SQL injections can be completely blocked in Vaadin applications.

Most providers have their own methods of dealing with injections. You should follow those guidelines. However, if you use pure JDBC, you have to deal with injection risks yourself. Here’s an example with JDBC, demonstrating an SQL injection mitigation using the value from a TextField in a prepared statement:

new TextField("Set new username:", valueChangeEvent -> {

    String value = valueChangeEvent.getValue();
    // 'value' can contain malicious content.

    // This is the correct way:
    String sql = "UPDATE app_users WHERE id=? SET name=?";

    try {
        // Use prepared statement to safely call the DB:
        PreparedStatement ps = dbConnection.prepareStatement(sql);

        ps.setLong(1, user.getId());
        ps.setString(2, value);
        ps.executeUpdate();
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }

    // This is the incorrect way. Don't use.
    // sql = "UPDATE app_users WHERE id="+ user.getId() +" SET name=\"" + value +
    // "\"";
});

Cross-Site Request Forgery (CSRF)

All requests between the client and the server are included with a user-session-specific CSRF token. All communication between them is handled by Vaadin, so you don’t have to include and verify CSRF tokens, manually.

Vaadin follows the "Synchronizer Token Pattern". See this article for more design insights.

UIDL requests are protected by Vaadin-Security-Key CSRF token. WebSocket requests are protected with Vaadin-Push-ID CSRF token.

This key is generated by Vaadin per UI instance — Vaadin 10 is an exception: the key is generated per session. It’s sent to the browser as a part of UIDL JSON string included in the bootstrap response (i.e., initial HTML page) from the server. The key is sent by the server to the client only once per opened browser tab, and not repeated in each response. When the page is refreshed or the user opens a new tab, another key is generated by Vaadin and sent to the browser.

Here’s an example of sending CSRF tokens to the client, with other data omitted:

{
  "appConfig": {
    "uidl": {
      "Vaadin-Security-Key": "f0ef03d7-0cf4-4f32-834d-47b88a1034b7",
      "Vaadin-Push-ID": "ad3744de-2280-4531-84df-6a70a5fe958e"
    }
  }
}

After this, the Flow client engine includes Vaadin-Security-Key in all UIDL requests being sent to the server. For example, it does this when the user performs actions on the page or navigates to another route, regardless of whether that message is sent in an HTTP request or as a WebSocket frame.

Vaadin-Security-Key is not included in upload or heartbeat requests since those are not UIDL messages. Upload is protected by its own random token embedded in the request path. Heartbeat doesn’t need any protection.

The CSRF token is passed inside the JSON message in the request body. Here’s an example which sends an XMLHttpRequest (XHR) message to the server:

{
  "csrfToken": "0bd61cf8-0231-455b-b39a-434f054352c5",
  "rpc": [
    {
      "type": "mSync",
      "node": 5,
      "feature": 1,
      "property": "invalid",
      "value": false
    },
    {
      "type": "publishedEventHandler",
      "node": 9,
      "templateEventMethodName": "confirmUpdate",
      "templateEventMethodArgs": [
        0
      ]
    }
  ],
  "syncId": 0,
  "clientId": 0
}

Vaadin-Push-ID is generated by Vaadin per user session. Vaadin-Push-ID is used to check whether a given push ID in the WebSocket request matches the user’s session push ID. If this ID is missing, the request is ignored and the WebSocket connection is closed.

In Vaadin services requests, the CSRF token is passed in the X-CSRF-Token HTTP header like so:

X-CSRF-Token: 0bd61cf8-0231-455b-b39a-434f054352c5

The CSRF token mechanism can be overridden on the server. You might do this, for example, to enable repeatable load test scripts that use Gatling or similar tools. This is strongly discouraged, though, when running in production.

Cross-Site Scripting

Vaadin has built-in protection against cross-site scripting (XSS) attacks. Vaadin uses browser APIs that make the browser render content as text instead of HTML (e.g., using innerText instead of innerHTML). This negates the chance of inserting, for example, <script> tags into the DOM by binding insecure string values.

Some Vaadin components explicitly allow HTML content for certain attributes. As a result, your application needs to ensure the data doesn’t contain XSS payloads. Allowing insecure HTML content is never the default. It’s an explicit choice by developers. Vaadin recommends using, for example, jsoup for sanitation and escaping.

Here are a few examples of built-in escaping, and some in which escaping is left to the developer:

Div div = new Div();

// These are safe as they treat the content as plain text.
div.setText("<b>This won't be bolded.</b>");
div.getElement().setText("<b>This won't be bolded either.</b>");
div.setTitle("<b>This won't be bolded either.</b>");

// These aren't safe
div.getElement().setProperty("innerHTML", "<b>This IS bolded.</b>");
div.add(new Html("<b>This IS bolded.</b>"));

new Checkbox().setLabelAsHtml("<b>This is bolded too.</b>");

You can use helpers to mitigate the risk when data isn’t trusted. Here’s an example that transforms data that might have dangerous HTML to a safe format:

String safeHtml = Jsoup.clean(dangerousText, Whitelist.relaxed());
new Checkbox().setLabelAsHtml(safeHtml);

Running Custom JavaScript

Sometimes you may need to run custom scripts inside the application. Running any script is an inherently unsafe operation. Scripts have full access to the entire client side. It’s especially dangerous if the script is stored somewhere other than in the application code and loaded dynamically:

// The script below can do whatever it wants. Use the method with care.
UI.getCurrent().getPage().executeJs("window.alert('This method is inherently unsafe');");

// This is especially dangerous.
// You can't know what the script contains, nor can you make it safe.
String script = getExternalScript();
UI.getCurrent().getPage().executeJs(script);

Scripts can’t be automatically escaped since any escaping would cause the script not to work. Vaadin can’t know which scripts are dangerous and which aren’t. It’s up to you to make sure the scripts are safe. However, you can safely pass parameters to JS execution by using the following syntax:

// If the script is known:
String script = "window.alert($0)";

// These parameters are treated in a safe way:
String scriptParam = getScriptParamFromDB();
UI.getCurrent().getPage().executeJs(script, scriptParam);

Using Templates

When using Polymer Templates in Vaadin applications, you need to be extra careful when inserting data into the DOM, as well as when using JavaScript. Vaadin automatically uses String values safely when using a TemplateModel from the server side. However, the framework has no control over what you do when using HTML or JavaScript inside the template itself. An example is binding a TextField with a JavaScript value directly to client-side logic: there’s no guarantee that the input is safe; it should be sanitized before use.

Reading values from template models and receiving Remote Procedure Calls (RPC) in server-side methods has the same caveats as discussed in the Data Validation section. You should never trust values sent from the client.

Java Serialization Vulnerability

A general security issue has been identified in programming language mechanics where the language allows execution of code that comes from serialized objects. Java language isn’t immune to this: the Java Serialization framework, Remote Method Invocation (RMI), Java Management Extensions (JMX), and Java Message Service (JMS) features are vulnerable to it.

If an application is set up to deserialize Java objects (e.g., using the libraries previously mentioned), an attacker can feed the system a malicious payload that may be deserialized into Java objects. The attacker can then execute arbitrary code using specific language features (e.g., reflection).

Vaadin has published a security alert for this vulnerability. It can’t be fixed in Vaadin, but you must instead mitigate the risk using the methods described in the alert appendices.

CB8041B3-5938-419F-A6C1-999F713A2A99